手机版

向我学习使用javascript原型的注意事项

时间:2021-09-22 来源:互联网 编辑:宝哥软件园 浏览:

1.将方法保存在原型上。

在没有原型的情况下编码JavaScript是完全可行的,例如:

函数User(name,password hash){ this . name=name;this . password hash=password hash;this . tostring=function(){ return '[User ' this . name ']';};this . checkpassword=function(password){ return hash(password)==this . password hash;};} var u1=新用户(/*.*/);var u2=新用户(/*.*/);var u3=新用户(/*.*/);创建用户类型的多个实例时,会出现一个问题:不仅名称和密码哈希属性存在于每个实例中,而且toString和checkPassword方法在每个实例中都有一个副本。如下图所示:

然而,当在原型上定义toString和checkPassword时,上面的图片看起来是这样的:

现在在User.prototype对象上定义了ToString和checkPassword方法,这意味着这两个方法只有一个副本存在,并且由所有User实例共享。

您可能认为将一个方法作为副本放在每个实例上会节省方法查询的时间。(当一个方法在prototype上定义时,它将首先在实例本身上寻找该方法,如果没有找到,它将继续在prototype上寻找。)

但是,在现代的JavaScript执行引擎中,方法的查询得到了极大的优化,所以查询时间几乎是不必要的,所以将方法放在原型对象上节省了大量的内存。

第二,使用闭包保存私有数据。

从语法上讲,JavaScript对象系统不鼓励使用信息隐藏。因为在使用this.name,this.passwordHash时,这些属性的默认访问级别是公共的,可以通过obj.name,obj.passwordHash在任何位置访问这些属性。

在ES5环境中,还提供了一些方法来更方便地访问对象的所有属性,例如Object.keys()、Object.getOwnPropertyNames()。因此,一些开发人员使用一些约定来定义JavaScript对象的私有属性,例如,最典型的是使用下划线作为属性的前缀,告诉其他开发人员和用户不应该直接访问该属性。

但这样做并不能从根本上解决问题。其他开发人员和用户仍然可以直接访问带下划线的属性。当您真正需要私有属性时,可以使用闭包来实现它们。

从某种意义上说,在JavaScript中,闭包对变量的访问策略和对象的访问策略是两个极端。默认情况下,闭包中的任何变量都是私有的,它们只能在函数内部访问。例如,用户类型可以实现如下:

函数User(name,password hash){ this . tostring=function(){ return '[User ' name ']';};this . checkpassword=function(password){ return hash(password)==password hash;};}此时,name和passwordHash不会保存为实例的属性,而是由局部变量保存。然后,根据闭包的访问规则,实例上的方法可以访问它们,但不能在其他地方。

使用这种模式的缺点是,所有使用局部变量的方法都需要在实例本身上定义,但是这些方法不能在原型对象上定义。正如第34项所讨论的,这样做的问题是会增加内存消耗。但是,在某些特殊场合,在实例上定义方法是可行的。

第三,实例状态只保存在实例对象上。

类型的原型和该类型的实例之间存在“一对多”的关系。然后,有必要确保与实例相关的数据不会被错误地保存在原型上。例如,对于实现树结构的类型,将其子节点保存在该类型的原型上是不正确的:

功能树(x){ this . value=x;} tree . prototype={ children :[],//应该是实例状态!addChild:函数(x){ this . children . push(x);} };var left=new Tree(2);left . addchild(1);left . addchild(3);var right=新树(6);right . addchild(5);right . addchild(7);var top=新树(4);top.addChild(左);top.addChild(右);top .儿童;//[1,3,5,7,左,右]在原型上保存状态时,会集中保存所有实例的状态,这在上面的场景中显然是不正确的:原本属于每个实例的状态被误共享了。如下图所示:

正确的实现应该是这样的:

功能树(x){ this . value=x;this . children=[];//实例状态} tree . prototype={ addchild : function(x){ this . children . push(x);} };此时,实例状态存储如下:

因此,当属于实例的状态被共享给原型时,可能会出现问题。在需要将状态属性保存在原型上之前,必须确保该属性可以共享。

一般来说,当一个属性是不可变的(无状态)时,它可以保存在原型对象上(例如,方法可以因此保存在原型对象上)。当然,有状态属性也可以放在原型对象上,这取决于特定的应用场景,例如用于记录类型实例数量的变量。如果用Java语言做类比,可以存储在原型对象上的变量就是Java中的类变量(用static关键字修改)。

第四,避免继承标准类型。

ECMAScript标准库不大,但提供了数组、函数、日期等一些重要类型。在某些情况下,您可以考虑继承这些类型中的一种来实现特定的功能,但是不鼓励这种做法。

例如,为了操作目录,目录类型可以继承数组类型,如下所示:

函数Dir(路径,条目){ this.path=pathfor (var i=0,n=entries.lengthI n;i ) {此[i]=条目[I];} } dir . prototype=object . create(array . prototype);//扩展Array var Dir=new Dir('/tmp/mysite ',['index.html ',' script.js ',' style . CSS ']);dir.length//0但是可以发现dir.length的值是0,而不是预期的3。

出现这种现象的原因是长度属性仅在对象是真正的数组类型时才起作用。

在ECMAScript标准中,定义了一个名为[[class]]的不可见内部属性。这个属性的值只是一个字符串,所以不要被误导以为JavaScript也实现了自己的类型系统。因此,对于Array类型,该属性的值为“Array”;对于函数类型,该属性的值为“函数”。下表显示了ECMAScript定义的所有[[类]]值:

当对象的类型确实是Array时,length属性的特殊之处在于length的值将与对象中索引属性的数量一致。例如,对于数组对象arr,arr[0]和arr[1]意味着对象有两个索引属性,因此length的值是2。当添加arr[2]时,长度值将自动同步到3。同样,当长度值设置为2时,arr[2]将自动设置为undefined。

但是,当继承数组类型并创建实例时,实例的[[类]]属性不是数组,而是对象。因此,长度属性无法正常工作。

在JavaScript中,还提供了一种查询[[类]]属性的方法,即使用Object.prototype.toString方法:

var dir=new Dir('/',[]);object . prototype . ToString . call(dir);//'[Object Object]' Object . prototype . tostring . call([]);//'[对象数组]'因此,最好使用组合而不是继承:

函数Dir(路径,条目){ this.path=paththis.entries=entries//数组属性} dir . prototype . foreach=function(f,this arg){ if(type of this arg==' undefined '){ this arg=this;} this.entries.forEach(f,this arg);};上面的代码将不再使用继承,而是将一些函数委托给内部条目属性,该属性的值是Array类型的对象。

在ECMAScript标准库中,大多数构造函数依赖于像[[class]]这样的内部属性值来实现正确的行为。对于继承这些标准类型的子类型,不能保证它们的行为是正确的。因此,不要继承ECMAScript标准库中的类型,如数组、布尔、日期、函数、数字、正则表达式、字符串。

以上是对使用原型注意事项的几点总结,希望能帮助大家正确使用原型。

版权声明:向我学习使用javascript原型的注意事项是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。