typescript 对象两个对象怎么只传值不传句柄

1.初步认识TypeScript
时间: 12:18:17
&&&& 阅读:64
&&&& 评论:
&&&& 收藏:0
标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&简介:typescript是C#之父主导的一门语言,本质上是向Javascript语言添加了可选的静态类型和基于面向对象的诸多特性。相当于javascript的超集,其包含es6。由于是和C#之父创造的,所以这里我采用和C#对比的方式学习他们之间的不同点,和主流面向对象语言(C#,Java)中相符的性质将不作记录。
1. 数字类型:C#有一系列限定大小范围的int,short,int16,long等整形,还有float,double等小数类型,而ts(TypeScript简称,为方便,以下皆简称ts)只有一种数字类型代表C#上面提到的全部数字类型:number,
let a:number=0b1010; //二进制
let b:number=0o744; //八进制
let c:number=0xf00d;//十六进制
2. 字符串类型:ts是string,和C#一致,不过ts增加了一种独特的语法,有点类似于C#的StringBuild对象,可以在字符串中嵌入占位符。使用字符串反引号(`)来定义多行文本,和内嵌表达式${expr}
let words:string=`hello,${name}你好,欢迎你来到${address}`;
3. 元组类型:元组类型用来表示已知元素数量和类型的数组,各元素的类型不必相同。这个是ts独有的,感觉该类型不会在实际编程中有很大用处。
4. 任意值类型:是ts针对编程时类型不明确变量使用的一种数据类型,常用于一下三种情况:
4.1 变量的值会动态变化时,比如来之第三方代码库,任意值类型可以让这些变量跳过编译阶段的类型检查。
4.2 改写现有代码时,任意值允许在编译时可选择的包含或移除类型检查。
4.3 定义各种类型数据的数组时。
5. never类型:是其他类型(包括null和undefined)的子类型,代表从不会出现的值,理解这句话很重要,never类型的变量只能被never类型所赋值,在函数中它通常表现为抛出异常,或无法执行到终止点。
如:let y:never=(()=&{throw new Error(‘‘)})();
6. let申明:let是用于声明变量的,和javascript中的var区别是let只能在块级作用域内有效,一般常在函数的作用域定义。并且使用了let定义的变量就具有了C#中变量定义的诸多限制,如不能重复,不能声明之前使用。
6.1 ts使用let声明变量类型有一点需要注意:如果一个类型可能出现null或者undefined,可以用|来支持多种类型。
如:let x:number |
let y:string | null |
7. 结构:是Es6的一个重要特性,其作用就是将声明的一组变量与相同结构的数组或者对象的元素值一一对应,并将变量相对应元素进行赋值。
7.1 数组结构:
如:let input=[1,2];
let [first,second]=
可以得出:first==0,second==2;
也可作用与已声明的变量:[first,second]=[second,first]; //变量交换
7.2 对象结构:
如: let test={x:0,y:10,width:15,height:16};
let {x,y,width,height}=
8. 数参数定义:javascript里,被调函数的每个参数都是可选的,而在ts里面被调函数的每个参数都是必传,在编译时会检查每个函数是否传值。
8.1 可选参数:在参数名旁边加上?号可以是参数变成可选参数,可选参数必须位于必选参数的后面的位置:
如:function max(x:number,y?:number)
调用: max(2);,max(2,4); //正确方式
max(2,4,7); //错误方式
8.2 默认参数:默认参数就是在定义函数参数时直接给该参数赋予一个默认值,这样可以在调用函数时如果不传改默认参数,该参数就是默认值。默认参数不必放在必选参数位置之后。
如: function max(x:number,y=4)
调用:max(2),max(2,4),max(2,undefined)
//正确 如果默认参数定义到了必选参数前面,用户必须显示的传入undefined。
max(2,4,7)
8.3 剩余参数: 当同时需要操作多个参数,或者并不知道会有多少参数传递进来时,就可以使用剩余参数。剩余参数定义方式采用"..."号做外参数名前缀,参数类型(形参)必须使用数组类型,且只能是函数的最后一个参数,
这样调用函数时,可以传入多个实数,在函数体中可以通过形参数组取得所有传入的剩余参数。
9. 箭头函数:ts提供的箭头函数(=&),可以在函数创建时就绑定this,从而解决javascript中的this由于在代码中的不同调用方式而导致的this指代window对象或者undefined。
如:let gif={
gifts:["xzm","panmin","laop","choulp"],
giftPicker:function(){
return this.gifts[1]
调用:let pickGift=gif.giftPicker();
pickGift();
//报错,Cannot read property ‘5‘ of undefined(...)
使用箭头函数:
gifts:["xzm","panmin","laop","choulp"],
giftPicker:function(){
return ()=& {this.gifts[1]
10. 类的构造函数:使用construction来作为构造函数名定义,派生类构造函数必须调用super(),他会调用基类的构造方法。
11. 参数属性:这里其实叫参数访问限定符更合适,有点类似C#的属性名前面的限定符(public,private,protected),只不过ts给他更多的意义,参数属性是通过给构造函数参数添加一个访问限定符,
它可以方便的在一个地方定义并初始化类成员,参数属性是一种语法糖,类似于:
class car{
public wheel:
construction(wheel:number){this.wheel= }
使用参数属性:
class car {
construction(public wheel:number){
this.wheel=
减少了一些原本啰嗦的代码量。
12. 模块概念:Es6引入了模块的概念,感觉他有点像C#的名称空间,Java的包,但是又有些区别,这些区别是javascript的语法原因。
12.1 首先,模块是自声明的,两个模块之间的关系是通过在文件级别上使用import和export来建立。任何包含顶级import或者export的文件都会被当成一个模块。
12.2 其次,模块在其自身的作用域里执行,而不是在全局作用域里,定义在一个模块里的变量,函数和类等,在模块外部是不可见的,除非明确的使用export到处它们,
类似的,如果想使用其他的模块导出的变量,函数,类和接口时,必须先通过import导入它们。
12.3 模块使用模块加载器去导入他的依赖,模块加载器在代码运行时会查找并加载模块间的所有依赖。在Angular中,常用的模块加载器有SystemJS和webpack.
13. 模块导出方式,分为以下三种:
13.1 导出声明:任何模块都能通过export关键字导出。
如:export const x=1;
exprot interface Indentity{}
exprot class Car{}
13.2 导出语句:当需要对导出的模块进行重命名时,可以使用导出语句。
class car{}
export { car };
export { car as BigCar };
13.3 模块包装:当需要修改和扩展已有的模块,并导出供其他模块调用。
//导出原先的验证器,但做了重命名
export { ErpIdentityValidate as RegExpBaseZipCodeValidator } from "./ErpIndentityValidate";
//一个模块可以包裹多个模块,并把新的内容以一个新的模块导出
export * from "./IndentityValidate";
export * from "./ErpIdentityValidate";
14. 模块导入方式:模块导入与模块导出相对应,可以使用import关键字来导入当前模块依赖的外部模块。有如下几种方式:
14.1 导入一个模块: import { EnpIndentityValidate } from "./ErpIdentityValidate";
14.2 别名导入: import { EnpIndentityValidate as eiv } from "./ErpIdentityValidate";
14.3 对整个模块进行别名导入: import * as validate from "./ErpIdentityValidate";
15. 模块的默认导出:模块可以用default关键字实现默认导出的功能,每个模块可以有一个默认导出。
另外,类和函数声明可以直接省略导出名来实现默认导出,默认导出有力于减少调用方调用模块的层数,减少代码的冗余。
15.1 默认导出类示例:
export default class ErpIdentityValidate{};
import validate from "./ErpIdentityValidate";
15.2 默认导出函数示例:
export default function{};
import validate from "./ErpIdentityValidate";
使用导出的函数:
validate();
15.3 默认导出值:
export default "TypeScript";
import name from "./ErpIdentityValidate";
16. 模块设计原则:
16.1 尽可能的在顶层导出:顶层导出可以降低调用方使用的难度,过多的"."操作使得开发者要记住过多的细节,所以尽量使用默认导出(使用者可以直接导入对象)或者
顶层导出(顶层导出方便调用者一目了然模块有哪些可供导入的对象),尤其是单个对象可以采用默认导出方式。
16.2 明确的列出导入的名字:在导入的时候尽可能明确的指定导入对象的名称,这样只要接口不变,调用方式就可以不变,从而降低了导入跟导出的耦合度,做到面向接口编程。
如:import { cat,dog } from "./ErpIdentityValidate";
16.3 使用命名空间导入:
如://MyLargemodule.ts
export class Dog{}
export class Cat{}
export class Tree{}
导入: import * as myLargemodule from "./MyLargemodule";
let x=new myLargemodule.Dog();
16.4 使用模块包装进行扩展:当需要扩展一个模块的功能时,推荐的方案是不要去更改原来的对象,而是导入该对象,再继承该对象,扩展导出一个新的对象。
export class M{}; //ModuleA.ts
import { ModuleA } from "./ModuleA";
export class ModuleB extends ModuleA{}
17. 接口:TypeScript接口的使用方式类似Java,同时还增加了更灵活的接口类型,包括属性,函数,可索引(indexable TypeScript)和类等类型。
17.1 属性类型接口:接口中只定义了属性的接口,实现该接口的方式只需要"形式上"的满足接口的要求即可,
如: interface FullName{
firstName:
secondName:
function printLabel(name:FullName){}
let obj={age:10,firstName:‘xzm‘,secondName:‘panmin‘}
printLabel(obj); //obj对象只需要包含一个firstName和secondName属性,且类型都是string既可。
17.1.2 可选属性,typescript提供了对可能存在的属性进行预定义,并兼容不传值的情况。其定义方式和普通接口没什么大的差别,就是可选属性变量名后添加一个?号。
如:interface FullName{
firstName:
secondName?:
function printLabel(name:FullName){}
let obj={firstName:"xzm"};
printLabel(obj);
17.2 函数类型接口:定义函数类型接口时,需要明确定义函数的参数列表和返回值类型,且参数列表的每个参数都要又参数名称和类型,不需要定义函数名。
如:interface Cat{
(name:string,salt:string):string
let tomcat:C
tomcat=function(name:string,salt:string){return "miaomiao"}
注意:函数的参数名,和类型必须保持一致,同时函数的返回类型也必须保持一致。
17.3 可索引类型接口:用来描述那些可以通过索引得到的类型,它包含一个索引签名(类似数组的下标,字典的key),表示用来索引的类型与返回值类型,即通过特定的索引来得到指定类型的返回值。
这是一个在C#和Java中还没有的特性。索引签名支持字符串和数字两种数据类型,使用这两种类型的最终返回值是一样,即当使用数字类型来索引时,javascript最终也会将其转换成自字符串
类型后再去索引对象。
个人的感觉可索引类型接口,其实有点想指定格式类型的数组,或者字典(Map).
inteface UserArray{
[index:number]:
//索引类型数字类型,近似于数组
interface UserObject{
[index:string]:
//索引类型是字符串类型,相当于字典
let userArray:UserA
let userObject:UserO
userArray=["zhangsan","lisi"];
userObject={"name1":"张三","name2":"李四"};
17.4 类类型接口:基本上和C#,Java里面的传统接口定义并无什么大的差别。类继承接口采用implements关键字。
17.5 接口扩展:接口也可以实现相互扩展,接口继承采用extends关键字。
18. 装饰器:(Decorators)是一种特殊类型的声明,它可以被附加到类声明,方法,,,expression求值后必须是一个函数,
在函数执行的时候装饰器的声明方法会被执行。装饰器是用来给附着的主体进行装饰,添加额外行为的。
个人见解:感觉typescript的装饰器就是Java的注解,C#的特性,算是一种元编程,定义在对象上,用于代码执行前后,做额外的事情,主要提供面向截面编程。
TypeScript官方介绍:装饰器(Decorators)为我们在类的声明及成员上通过元编程语法添加标注提供了一种方式。 Javascript里的装饰器目前处在
建议征集的第一阶段,但在TypeScript里已做为一项实验性特性予以支持。
注意:装饰器是一项实验性特性,在未来的版本中可能会发生改变。
若要启用实验性的装饰器特性,你必须在命令行或tsconfig.json里启用experimentalDecorators编译器选项:
tsc --target ES5 --experimentalDecorators
tsconfig.json文件启用:
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
18.1 装饰器求值:类中不同声明的装饰器将按以下规定的顺序应用:
1.参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰其应用到每个实例成员。
2.参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰其应用到每个静态成员。
3.参数装饰器应用到构造函数。
4.类装饰器应用到类。
18.2 类装饰器:在类声明之前声明,类装饰器应用于类的构造函数,可以用来监视,修改或替换类定义。
重点1:类装饰器表达式会在运行时当做函数被调用,类的构造函数被当做其唯一的参数。
重点2:如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。(也就是说,可以用类装饰器返回一个函数的形式来动态替换类的原本构造函数)。
class Greeter{
constructor(message:string){
this.greeter=
greeter(){
return "hello"+this.
@sealed装饰器定义如下
function sealed(constructor:Function){
Object.seal(constructor);
Object.seal(constructor.protected); ,它将密封该了类的构造函数和原型。
18.3 方法装饰器:声明在一个方法的声明之前,它会被用到方法的属性描述符上(descriptor),可以用来监视,修改或替换方法定义。方法装饰器不能用在声明文件(.d.ts),
重载或者任何外部上下(比如declare的类中)中。
方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
1.target:对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
2.properrtyKey:方法(成员)的名称。
3.descriptor:成员的属性描述符。
注意:如果代码的输出版本小于ES5,属性描述符将会是undefined。
如果方法装饰器的返回一个值,它会被用作方法的属性描述符,如果代码的输出目标小于ES5,返回值会被忽略。
其中descriptor类型为TypedPropertyDescriptor, 在typescript中定义如下:
interface TYpedPropertyDescriptor&T&{
enumerable?:
//是否可遍历
configurable?: //属性描述是否可改变或者属性是否可删除
writable?:
//是否可修改
//属性的值
get?:()=&T;
//属性的访问器函数(getter)
set?:(value:T)=&void
//属性的设置器函数
class TestClass{
testMethod(arg:string){
return "xzm:"+
function log(target:Object,properrtyKey:string,descriptor:TypedPropertyDescriptor&any&){
let origin=descriptor. //通过方法属性描述符的value属性,取得有关方法对象
descriptor.value=function(...args:any[]){
console.log("args:"+JSON.stringify(args));
let result=origin.apply(this,args);
//调用方法
console.log("The result" + result);
return result;
使用代码测试: new TestClass().testMethod("test method descorator");
结果输出如下: agrs:["test method descorator"]
The result-xzm:test method descorator
总结:个人感觉广发证劵团队在这里(揭秘Angular2这本书中)对方法装饰器讲得比较粗,对TypedPropertyDescriptor对象基本一笔带过,
但是示例中却用到了该对象内部的很多东西,使人跟本无从知道方法装饰器具有何作用。
18.4 访问器装饰器:访问器装饰器声明在一个访问器声明之前。访问器装饰器应用于访问器的属性描述符(),并且可以用来监视,修改或替换一个访问器定义。
注意:TypeScript不允许同时装饰一个成员的set或者get访问器。取而代之的是,一个成员的所有装饰必须应用在文档顺序的第一个访问器上。这是因为,
在装饰器应用于一个属性描述符时,它联合了get和set访问器,而不是分开声明的。
访问器装饰器表达式会在运行时当做函数被调用,传入下列3个参数:
1.target:对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
2.properrtyKey:方法(成员)的名称。
3.descriptor:成员的属性描述符。
访问器装饰器基本和方法装饰器一样,除了需要注意上面提到的不允许同时装饰一个成员的set和get访问器以外。
18.5 属性装饰器:属性装饰器声明在一个属性声明之前,属性装饰器表达式会在运行时当做函数被调用,传入下列两个参数:
1.target:对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
2.property:成员(属性)的名字。
注意:属性描述符不会作为参数传入属性装饰器,这于TypeScript是如何初始化属性装饰器有关。因为目前没有办法在顶一个原型对象的成员时描述一个实例的属性,
并且没办法监视或修改一个属性的初始化方法。因此属性描述符只能用来监视类中是否声明了某个名字的属性。
18.6 参数装饰器:声明在一个参数声明之前(用于的类的构造函数或方法声明),参数装饰器表达式会在运行是被当做函数调用。
1.target:对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
2.propertyKey:成员的名字。
3.parameterIndex:参数在函数参数列表中的索引。
注意:参数装饰器只能用来监视一个方法的参数是否被传入。参数装饰器在Angular中被广泛使用,特别是结合reflect-metadata库来支持实验性的Metadata API。
参数装饰器的返回值会被忽略。
18.7 装饰器组合:TypeScript支持多个装饰器同时应用到一个声明上,实现多个装饰器复合使用,语法支持从左到右,或从上到下书写。
在TypeScript里,当多个装饰器应用在一个声明上的时候,会进行如下步骤的操作:
1.从左到右(从上到下)依次执行装饰器函数,得到返回结果。
2.返回的结果会被当做函数,从左到右(从上倒下)依次调用。
19. 范型:基本和C#一致,ts的范型除了可以用于类上定义,还可以用于函数上定义。
20. TypeScript相关
20.1 编译配置文件:tsc编译器有很多命令行参数,都写在命令行上会十分繁琐。tsconfig.json文件正是用来解决这个问题。当运行tsc时,编译器从当前目录向上搜索tsconfig.json
文件来加载配置,类似于package.json文件的搜索方式..
20.2 TypeScript的一些语法糖
1.类型别名:类型别名声名 type sn= number |
//sn就代表number和string类型.
2.使用interface往另一个interface里面添加额外成员.
如:interface Foo{ x:}
interface Foo{ y:}
let a:Foo...;
console.log(a.x+a.y); //ok
3.同上,interface还可以往一个类里面添加额外成员.但是不能用interface为类型别名里添加额外成员.
4.声明合并:是指编译器将针对同一个名字的多个独立声明合并为单一声明.和并后的声明同时拥有原先多个声明的特性.,其实这就包含了上面2,3两条.
注意:接口的非函数成员必须是唯一性,合并时不能出现重复的.对于函数成员,每个同名函数声明都会被当作函数重载,同时后面接口的函数比前面接口的函数具有更高的优先级.
5.名称空间合并:与接口合并相似,同名的命名空间也会合并其成员.命名空间可以与其他类型的声明进行合并,只要命名空间的定义符合将要合并类型的定义,合并后包含两者的声明类型.
class Album { label:Album.AlblumLabel }
namespace Ablum{
export class AlbumLabel{};
必须导出AlblumLabel类,好让合并的类能访问.合并的结果是一个类并带有一个内部类.
function buildLabel(name:string):string{
return buildLabel.prefix+name+buildLabel.
namespace buildLabel{
export let prefix="";
export let suffix="hello,";
6.非法合并:目前,类不能与其他类或变量合并.
7.全局声明:declare关键字可以将对象声明为全局.
&标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&原文:/xuzimian/p/6362218.html
教程昨日排行
&&国之画&&&& &&&&&&
&& &&&&&&&&&&&&&&
鲁ICP备号-4
打开技术之扣,分享程序人生!ionic2页面回传值,关于Typescript的Promise承诺 - 简书
ionic2页面回传值,关于Typescript的Promise承诺
刚入门,没深究~
-。-经典: 来自谷歌的说明,简单直观
Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 方法和 reject 方法。如果异步操作成功,则用 resolve 方法将 Promise 对象的状态,从「未完成」变为「成功」(即从 pending 变为 resolved);如果异步操作失败,则用 reject 方法将 Promise 对象的状态,从「未完成」变为「失败」(即从 pending 变为 rejected)。
then()有两个参数,一个成功案例的回调,另一个是失败的情况。两者都是可选的,因此您可以为成功或失败的情况添加回调。
这是一个很好的例子,如果能够获取到对应url的xml请求,则req.status=200,在if中可以执行你想做出的操作,并执行resolve(req.response),其中req.response是请求得到的结果,如果找不到则返回req.status=404,并reject()返回错误。在ionic2中,页面回传值的方法:同上所描述那般做出一个承诺
将该方法作为一个参数传入push的页面中注意:最开始我本来是采用如下图方式的,但是很不幸,出现了问题,问题所在就是关于这个this的作用域问题。这里要用到ES6的箭头函数 Arrow Functions。普通function函数和箭头函数的行为有一个微妙的区别,箭头函数没有它自己的this值,箭头函数内的this值继承自外围作用域。
回传值,将需要传过去的指丢入contactsCallback中,即将数据从该页面传到上一个页面中,并且then后,通过是执行了resolve()还是reject()来识别是否成功,如果成功,pop掉当前页面,否则输出错误。
参考文章: 了解Promise一直以来,JavaScript处理异步都是以callback的方式。近几年随着JavaScript开发模式的逐渐成熟,CommonJS规范顺势而生,其中就包括提出了Promise规范,Promise完全改变了js异步编程的写法,让异步编程变得十分的易于理解。1.什么是Promise所谓Promise,字面上可以理解为“承诺”,就是说A调用B,B返回一个“承诺”给A,然后A就可以在写计划的时候这么写:当B返回结果给我的时候,A执行方案S1,反之如果B因为什么原因没有给到A想要的结果,那么A执行应急方案S2,这样一来,所有的潜在风险都在A的可控范围之内了。Promise规范如下:一个promise可能有三种状态:等待(pending)、已完成(fulfilled)、已拒绝(rejected)一个promise的状态只可能从“等待”转到“完成”态或者“拒绝”态,不能逆向转换,同时“完成”态和“拒绝”态不能相互转换promise必须实现then方法(可以说,then就是promise的核心),而且then必须返回一个promise,同一个promise的then可以调用多次,并且回调的执行顺序跟它们被定义时的顺序一致then方法接受两个参数,第一个参数是成功时的回调,在promise由“等待”态转换到“完成”态时调用,另一个是失败时的回调,在promise由“等待”态转换到“拒绝”态时调用。同时,then可以接受另一个promise传入,也接受一个“类then”的对象或方法,即thenable对象。
存在 即合理天极传媒:天极网全国分站
您现在的位置:
& &&C++箴言:避免返回对象内部构件的句柄
C++箴言:避免返回对象内部构件的句柄
blog 09:39
     
  假设你正在一个包含矩形的程序上工作。每一个矩形都可以用它的左上角和右下角表示出来。为了将一个 Rectangle 对象保持在较小状态,你可能决定那些点的定义的域不应该包含在 Rectangle 本身之中,更合适的做法是放在一个由 Rectangle 指向的辅助的结构体中:
class Point {  // class for representing points public:  Point(int x, int y);  ...  void setX(int newVal);  void setY(int newVal);  ...};struct RectData {  // Point data for a Rectangle P // ulhc = " upper left-hand corner" P // lrhc = " lower right-hand corner"};class Rectangle { ... private:  std::tr1::shared_ptr&RectData& pD // see Item 13 for info on}; // tr1::shared_ptr  由于 Rectangle 的客户需要有能力操控 Rectangle 的区域,因此类提供了 upperLeft 和 lowerRight 函数。可是,Point 是一个用户定义类型,所以,在典型情况下,以传引用的方式传递用户定义类型比传值的方式更加高效的观点,这些函数返回引向底层 Point 对象的引用:
class Rectangle { public:  ...  Point& upperLeft() const { return pData-& }  Point& lowerRight() const { return pData-& }  ...};  这个设计可以编译,但它是错误的。实际上,它是自相矛盾的。一方面,upperLeft 和 lowerRight 是被声明为 const 的成员函数,因为它们被设计成仅仅给客户提供一个获得 Rectangle 的点的方法,而不允许客户改变这个 Rectangle。另一方面,两个函数都返回引向私有的内部数据的引用――调用者可以利用这些引用修改内部数据!例如: Point coord1(0, 0);
Point coord2(100, 100);const Rectangle rec(coord1, coord2); // rec is a const rectangle from// (0, 0) to (100, 100)rec.upperLeft().setX(50); // now rec goes from// (50, 0) to (100, 100)!  请注意这里,upperLeft 的调用者是怎样利用返回的 rec 的内部 Point 数据成员的引用来改变这个成员的。但是 rec 却被期望为 const!  这直接引出两条经验。第一,一个数据成员被封装,但是具有最高可访问级别的函数还是能够返回引向它的引用。在当前情况下,虽然 ulhc 和 lrhc 被声明为 private,它们还是被有效地公开了,因为 public 函数 upperLeft 和 lowerRight 返回了引向它们的引用。第二,如果一个 const 成员函数返回一个引用,引向一个与某个对象有关并存储在这个对象本身之外的数据,这个函数的调用者就可以改变那个数据(这正是二进制位常量性的局限性的一个副作用)。  我们前面做的每件事都涉及到成员函数返回的引用,但是,如果它们返回指针或者迭代器,因为同样的原因也会存在同样的问题。引用,指针,和迭代器都是句柄(handle)(持有其它对象的方法),而返回一个对象内部构件的句柄总是面临危及对象封装安全的风险。就像我们看到的,它同时还能导致 const 成员函数改变了一个对象的状态。  我们通常认为一个对象的“内部构件”就是它的数据成员,但是不能被常规地公开访问的成员函数(也就是说,它是 protected 或 private 的)也是对象内部构件的一部分。同样地,不要返回它们的句柄也很重要。这就意味着你绝不应该有一个成员函数返回一个指向拥有较小的可访问级别的成员函数的指针。如果你这样做了,它的可访问级别就会与那个拥有较大的可访问级别的函数相同,因为客户能够得到指向这个拥有较小的可访问级别的函数的指针,然后就可以通过这个指针调用这个函数。  无论如何,返回指向成员函数的指针的函数是难得一见的,所以让我们把注意力返回到 Rectangle 类和它的 upperLeft 和 lowerRight 成员函数。我们在这些函数中挑出来的问题都只需简单地将 const 用于它们的返回类型就可以排除:
class Rectangle {public:...const Point& upperLeft() const { return pData-& }const Point& lowerRight() const { return pData-& }...};  通过这个修改的设计,客户可以读取定义一个矩形的 Points,但他们不能写它们。这就意味着将 upperLeft 和 upperRight 声明为 const 不再是一句空话,因为他们不再允许调用者改变对象的状态。至于封装的问题,我们总是故意让客户看到做成一个 Rectangle 的 Points,所以这是封装的一个故意的放松之处。更重要的,它是一个有限的放松:只有读访问是被这些函数允许的,写访问依然被禁止。  虽然如此,upperLeft 和 lowerRight 仍然返回一个对象内部构件的句柄,而这有可能造成其它方面的问题。特别是,这会导致空悬句柄:引用了不再存在的对象的构件的句柄。这种消失的对象的最普通的来源就是函数返回值。例如,考虑一个函数,返回在一个矩形窗体中的 GUI 对象的 bounding box:
class GUIObject { ... };const Rectangle // returns a rectangle byboundingBox(const GUIObject& obj); // see Item 3 for why// return type is const  现在,考虑客户可能会这样使用这个函数:
GUIObject * // make pgo point to... // some GUIObjectconst Point *pUpperLeft = // get a ptr to the upper&(boundingBox(*pgo).upperLeft()); // left point of its// bounding box  对 boundingBox 的调用会返回一个新建的临时的 Rectangle 对象。这个对象没有名字,所以我们就称它为 temp。于是 upperLeft 就在 temp 上被调用,这个调用返回一个引向 temp 的一个内部构件的引用,特别是,它是由 Points 构成的。随后 pUpperLeft 指向这个 Point 对象。到此为止,一切正常,但是我们无法继续了,因为在这个语句的末尾,boundingBox 的返回值―― temp ――被销毁了,这将间接导致 temp 的 Points 的析构。接下来,剩下 pUpperLeft 指向一个已经不再存在的对象;pUpperLeft 空悬在创建它的语句的末尾!  这就是为什么任何返回一个对象的内部构件的句柄的函数都是危险的。它与那个句柄是指针,引用,还是迭代器没什么关系。它与是否受到 cosnt 的限制没什么关系。它与那个成员函数返回的句柄本身是否是 const 没什么关系。全部的问题在于一个句柄被返回了,因为一旦这样做了,你就面临着这个句柄比它引用的对象更长寿的风险。  这并不意味着你永远不应该让一个成员函数返回一个句柄。有时你必须如此。例如,operator[] 允许你从 string 和 vector 中取出单独的元素,而这些 operator[]s 就是通过返回引向容器中的数据的引用来工作的――当容器本身被销毁,数据也将销毁。尽管如此,这样的函数属于特例,而不是惯例。  Things to Remember  ?避免返回对象内部构件的句柄(引用,指针,或迭代器)。这样会提高封装性,帮助 const 成员函数产生 cosnt 效果,并将空悬句柄产生的可能性降到最低。###adv###
(作者:fatalerror99责任编辑:方舟)
欢迎在新浪微博上关注我们
办公软件IT新闻整机

我要回帖

更多关于 ajax传值json对象 的文章

 

随机推荐