在 Javascript 里实现类、继承这种面向对象的特性,离不开对象的原型。最早接触 js 的时候,不求甚解,一般是参照网上抄来的方法这样实现的:
var Animal = function (name) {
this.name = name || 'NO_NAME'
}
Animal.prototype.hello = function () {
console.log('Hello, this is ' + this.name)
}
var Cat = function (name, age) {
this.name = name
this.age = age
}
Cat.prototype = new Animal()
var kitty = new Cat('Kitty', 1)
kitty.hello()
js 的对象可以理解成一个键值对应关系的集合。对象的原型(prototype)
是一个特殊的键值,通过它可以帮助实现一些面向对象的特性。对于一个普通的对象定义,例如 var obj = {}
, 它也有原型, 只不过值是 undefined
。
可以通过 obj.__proto__
访问或者原型对象,但是这个访问方式并不是规范内的,仅仅是为了兼容性才保留支持。也可以通过 Object.getPrototypeOf()
访问以及 Object.setPrototypeOf()
设置原型对象,但应该也是要尽量避免的。
对于一个 js 对象,当通过 .
或者 []
尝试访问它的方法或者属性的时候,会先检查这个属性或者方法是否挂载在自身上;如果没有,就向上查找它的原型对象,依次类推。
如果到了原形链的最末端仍然没有此属性或方法,才会返回 undefined
表示没有找到。
通过以上的对象访问过程,可以推测出以下几点:
-
访问某对象的一个不存在的属性,或者是从原型链上继承来的属性,理论上说应该是相对而言比较慢的,因为这种情况会向上搜索原型链上查找。但是奇怪的是经过了一些测试,发现区别不明显,具体原因还有待研究。
var benchmark = require('benchmark') var suite = new benchmark.Suite() function Parent() { this.delta = 10; }; function ChildA(){} ChildA.prototype = new Parent() function ChildB(){} ChildB.prototype = new ChildA() function ChildC(){} ChildC.prototype = new ChildB() function ChildD(){} ChildD.prototype = new ChildC() function ChildE(){} ChildE.prototype = new ChildD() function nestedFn() { var child = new ChildE() var counter = 0; for(var i = 0; i < 1000; i++) { for(var j = 0; j < 1000; j++) { for(var k = 0; k < 1000; k++) { counter += child.delta; } } } } function cachedFn () { var child = new ChildE() var counter = 0 var delta = child.delta for(var i = 0; i < 1000; i++) { for(var j = 0; j < 1000; j++) { for(var k = 0; k < 1000; k++) { counter += delta; } } } } var tm = {} function measure(id, action) { var now = Date.now() action() tm[id] = Date.now() - now console.log(id, tm[id]) } measure('nested-visit', nestedFn) measure('cached-visit', cachedFn) suite.add('nested-visit', nestedFn) .add('cached-visit', cachedFn) .on('complete', function () { console.log('Fastest is ' + this.filter('fastest').map('name')) }) .on('cycle', function (event) { console.log(String(event.target)) }) .run({async: true})
-
对象的原型也是一个对象,可以拥有属性和方法,可以在运行时访问或修改。比如扩展 Array.forEach 的实现,通常直接挂载在 Array 对象的原型上:
Array.prototype.forEach = function(cb) { for (var i = 0; i < this.length; i ++) { cb(this[i], i) } }
-
把构造函数作为普通函数调用,通过返回新建对象,可以避免使用关键字
new
:var Animal = function (name) { // function prototype is Object var a = Object.create(Animal.prototype) a.name = name return a } var animal = Animal()