跟我学习javascript的prototype使用注意事项


Posted in Javascript onNovember 17, 2015

一、在prototype上保存方法

不使用prototype进行JavaScript的编码是完全可行的,例如:

function User(name, passwordHash) { 
 this.name = name; 
 this.passwordHash = passwordHash; 
 this.toString = function() { 
  return "[User " + this.name + "]"; 
 }; 
 this.checkPassword = function(password) { 
  return hash(password) === this.passwordHash; 
 }; 
} 

var u1 = new User(/* ... */); 
var u2 = new User(/* ... */); 
var u3 = new User(/* ... */);

当创建了多个User类型的实例时,就存在问题了:不仅是name和passwordHash属性在每个实例上都存在,toString和checkPassword方法在每个实例上都有一份拷贝。就像下图表示的那样:

跟我学习javascript的prototype使用注意事项

但是,当toString和checkPassword被定义在prototype上时,上图就变成下面这个样子了:

跟我学习javascript的prototype使用注意事项

toString和checkPassword方法现在定义在了User.prototype对象上,也就意味着这两个方法只存在一份拷贝,并被所有的User实例共享。

也许你会认为将方法作为拷贝放在每个实例上,会节省方法查询的时间。(当方法定义在prototype上时,首先会在实例本身上寻找方法,如果没有找到才会去prototype上继续找)

但是在现代的JavaScript执行引擎中,对方法的查询进行了大量优化,所以这个查询时间几乎是不需要考虑的,那么将方法放在prototype对象上就节省了很多内存。

二、使用闭包来保存私有数据

JavaScript的对象系统从其语法上而言并不鼓励使用信息隐藏(Information Hiding)。因为当使用诸如this.name,this.passwordHash的时候,这些属性默认的访问级别就是public的,在任何位置都能够通过obj.name,obj.passwordHash来对这些属性进行访问。

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

但是这样做,并不能从根本上解决问题。其他开发人员和用户还是能够对带有下划线的属性进行直接访问。对于确实需要私有属性的场合,可以使用闭包进行实现。

从某种意义而言,在JavaScript中,闭包对于变量的访问策略和对象的访问策略是两个极端。闭包中的任何变量默认都是私有的,只有在函数内部才能访问这些变量。比如,可以将User类型实现如下:

function User(name, passwordHash) { 
 this.toString = function() { 
  return "[User " + name + "]"; 
 }; 
 this.checkPassword = function(password) { 
  return hash(password) === passwordHash; 
 }; 
}

此时,name和passwordHash都没有被保存为实例的属性,而是通过局部变量进行保存。然后根据闭包的访问规则,实例上的方法可以对它们进行访问,而在其它地方则不能。

使用这种模式的一个缺点是,利用了局部变量的方法都需要被定义在实例本身上,不能讲这些方法定义在prototype对象上。正如在Item34中讨论的那样,这样做的问题是会增加内存的消耗。但是在某些特别的场合下,即使将方法定义在实例上也是可行的。

三、实例状态只保存在实例对象上

一个类型的prototype和该类型的实例之间是”一对多“的关系。那么,需要确保实例相关的数据不会被错误地保存在prototype之上。比如,对于一个实现了树结构的类型而言,将它的子节点保存在该类型的prototype上就是不正确的:

function Tree(x) { 
 this.value = x; 
} 
Tree.prototype = { 
 children: [], // should be instance state! 
 addChild: function(x) { 
  this.children.push(x); 
 } 
}; 

var left = new Tree(2); 
left.addChild(1); 
left.addChild(3); 

var right = new Tree(6); 
right.addChild(5); 
right.addChild(7); 

var top = new Tree(4); 
top.addChild(left); 
top.addChild(right); 

top.children; // [1, 3, 5, 7, left, right]

当状态被保存到了prototype上时,所有实例的状态都会被集中地保存,在上面这种场景中显然是不正确的:本来属于每个实例的状态被错误地共享了。如下图所示:

跟我学习javascript的prototype使用注意事项

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

function Tree(x) { 
 this.value = x; 
 this.children = []; // instance state 
} 
Tree.prototype = { 
 addChild: function(x) { 
  this.children.push(x); 
 } 
};

此时,实例状态的存储如下所示:

跟我学习javascript的prototype使用注意事项

可见,当本属于实例的状态被共享到prototype上时,也许会产生问题。在需要在prototype上保存状态属性前,一定要确保该属性是能够被共享的。

总体而言,当一个属性是不可变(无状态)的属性时,就能将它保存在prototype对象上(比如方法能够被保存在prototype对象上就是因为这一点)。当然,有状态的属性也能够被放在prototype对象上,这要取决于具体的应用场景,典型的比如用来记录一个类型实例数量的变量。使用Java语言作为类比的话,这类能够存储在prototype对象上的变量就是Java中的类变量(使用static关键字修饰)。

四、避免继承标准类型

ECMAScript标准库不大,但是提供了一些重要的类型如Array,Function和Date。在一些场合下,你也许会考虑继承其中的某个类型来实现特定的功能,但是这种做法并不被鼓励。

比如为了操作一个目录,可以让目录类型继承Array类型如下:

function Dir(path, entries) { 
 this.path = path; 
 for (var i = 0, n = entries.length; i < n; i++) { 
  this[i] = entries[i]; 
 } 
} 
Dir.prototype = Object.create(Array.prototype); 
// extends Array 

var dir = new Dir("/tmp/mysite", ["index.html", "script.js", "style.css"]); 
dir.length; // 0

但是可以发现,dir.length的值是0,而不是期待中的3。

发生这种现象的原因在于:只有当对象是真正的Array类型时,length属性才会起作用。

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

那么当对象的类型确实是Array时,length属性的特别之处就在于:length的值会和该对象中被索引的属性个数保持一致。比如对于一个数组对象arr,arr[0]和arr[1]就表示该对象有两个被索引的属性,那么length的值就是2。当添加了arr[2]的时候,length的值会被自动同步成3。同样地,当设置length值为2时,arr[2]会被自动设置成undefined。

但是当继承Array类型并创建实例时,该实例的 [[class]] 属性并不是Array,而是Object。因此length属性不能正确的工作。

在JavaScript中,也提供了用于查询 [[class]] 属性的方法,即使用Object.prototype.toString方法:

var dir = new Dir("/", []); 
Object.prototype.toString.call(dir); // "[object Object]" 
Object.prototype.toString.call([]); // "[object Array]"

因此,更好的实现方法是使用组合而不是继承:

function Dir(path, entries) { 
 this.path = path; 
 this.entries = entries; // array property 
} 
Dir.prototype.forEach = function(f, thisArg) { 
 if (typeof thisArg === "undefined") { 
  thisArg = this; 
 } 
 this.entries.forEach(f, thisArg); 
};

以上代码将不再使用继承,而是将一部分功能代理给内部的entries属性来实现,该属性的值是一个Array类型对象。

ECMAScript标准库中,大部分的构造函数都会依赖内部属性值如 [[class]] 来实现正确的行为。对于继承这些标准类型的子类型,无法保证它们的行为是正确的。因此,不要继承ECMAScript标准库中的类型如:
Array, Boolean, Date, Function, Number,RegExp,String

以上就是对使用prototype的几点注意事项进行总结,希望可以帮助大家正确的使用prototype。

Javascript 相关文章推荐
event.keyCode键码值表 附只能输入特定的字符串代码
May 15 Javascript
ASP.NET jQuery 实例9  通过控件hyperlink实现返回顶部效果
Feb 03 Javascript
JavaScript实现简单图片滚动附源码下载
Jun 17 Javascript
JavaScript获取当前网页最后修改时间的方法
Apr 03 Javascript
jquery实现的美女拼图游戏实例
May 04 Javascript
JavaScript实现Flash炫光波动特效
May 14 Javascript
Vue2实现组件props双向绑定
Dec 02 Javascript
javaScript实现鼠标在文字上悬浮时弹出悬浮层效果
Apr 12 Javascript
vue使用中的内存泄漏【推荐】
Jul 10 Javascript
原生js实现form表单序列化的方法
Aug 02 Javascript
Node.js开发之套接字(socket)编程入门示例
Nov 05 Javascript
jquery css实现流程进度条
Mar 26 jQuery
js弹出对话框方式小结
Nov 17 #Javascript
跟我学习javascript的prototype,getPrototypeOf和__proto__
Nov 17 #Javascript
Jquery 垂直多级手风琴菜单附源码下载
Nov 17 #Javascript
JavaScript代码实现禁止右键、禁选择、禁粘贴、禁shift、禁ctrl、禁alt
Nov 17 #Javascript
跟我学习javascript的undefined与null
Nov 17 #Javascript
跟我学习javascript的arguments对象
Nov 16 #Javascript
JavaScript函数学习总结以及相关的编程习惯指南
Nov 16 #Javascript
You might like
php实现文件下载代码分享
2014/08/19 PHP
php中Socket创建与监听实现方法
2015/01/05 PHP
Yii2使用自带的UploadedFile实现的文件上传
2016/06/20 PHP
JS脚本defer的作用示例介绍
2014/01/02 Javascript
简介可以自动完成UI的AngularJS工具angular-smarty
2015/06/23 Javascript
Angular2 父子组件数据通信实例
2017/06/22 Javascript
禁止弹窗中蒙层底部页面跟随滚动的几种方法
2017/12/07 Javascript
微信小程序实现留言板功能
2018/11/02 Javascript
微信小程序实现弹出层效果
2020/05/26 Javascript
JS集合set类的实现与使用方法示例
2019/02/01 Javascript
微信小程序 组件的外部样式externalClasses使用详解
2019/09/06 Javascript
微信小程序静默登录的实现代码
2020/01/08 Javascript
vue-socket.io接收不到数据问题的解决方法
2020/05/13 Javascript
Python性能优化的20条建议
2014/10/25 Python
Python时间的精准正则匹配方法分析
2017/08/17 Python
Python内建函数之raw_input()与input()代码解析
2017/10/26 Python
Python使用Django实现博客系统完整版
2020/09/29 Python
django js实现部分页面刷新的示例代码
2018/05/28 Python
从请求到响应过程中django都做了哪些处理
2018/08/01 Python
Python matplotlib学习笔记之坐标轴范围
2019/06/28 Python
Python scipy的二维图像卷积运算与图像模糊处理操作示例
2019/09/06 Python
python 装饰器功能与用法案例详解
2020/03/06 Python
丝芙兰香港官网:Sephora香港
2018/03/13 全球购物
TOWER London官网:鞋子、靴子、运动鞋等
2019/07/14 全球购物
信息技术专业大学生个人的自我评价
2013/10/05 职场文书
高中体育教学反思
2014/01/24 职场文书
告诉你怎样写创业计划书
2014/01/27 职场文书
个人自我评价范文
2014/02/05 职场文书
《和田的维吾尔》教学反思
2014/04/14 职场文书
付款委托书范本
2014/10/05 职场文书
镇党政领导班子民主生活会思想汇报
2014/10/11 职场文书
2014年帮扶工作总结
2014/11/26 职场文书
农村党支部承诺书
2015/04/30 职场文书
趣味运动会通讯稿
2015/07/18 职场文书
读完《骆驼祥子》的观后感!
2019/07/05 职场文书
SpringBoot整合JWT的入门指南
2021/06/29 Java/Android