js原型

理解类, 实例, 原型

区分prototype__proto__

用代码实现: new, Object.create

类与实例

定义构造函数就是定义的是对象的”类”, 调用构造函数生成的对象就是”实例”

创建实例

现有类定义如下:

1
2
3
function Person(name) {
this.name = name
}

创建实例可以有以下方式:

1.new

1
let p = new Person('levy')

2.Object.create

1
2
p = Object.create(Person.prototype)
p.name = 'levy'

3.__proto__

1
p = {'__proto__': Person.prototype, name: 'levy'}

解析:

除了null(undefined不是对象)/Object.prototype, js每一个对象都有一个原型, 对象从原型继承属性与方法

对象对应的构造函数的prototype属性, 就是对象的原型

而大多数浏览器的 ES5 实现之中,每一个对象都有__proto__属性,指向其原型, 所以用代码来表示就是

1
2
3
let obj = {}
obj.__proto__ == Object.prototype

所以上面第3个代码片断是最本质(当然不是最优)的表示方式

总结: __proto__是对象的属性, prototype是构造函数的属性

构造函数

任何js函数都可以做构造函数, 并且都拥有prototype属性(除了调用Function.bind返回的函数), 因为使用new调用构造函数时, 构造函数的prototype属性会作为新对象的原型

构造函数的prototype属性是个对象, 它预设了一个不可枚举的constructor属性, 它指向构造函数, 即:

1
2
3
let F = function() {}
F.prototype.constructor == F

模拟实现newObject.create

Object.create可以看成是屏蔽了new关键字的实现, 可以自己模拟一下实现:

1
2
3
4
5
Object.create = function(parent) {
function F = () {}
F.prototype = parent
return new F()
}

如果更暴力一点, 还可以这样:

1
2
3
Object.create = function(parent) {
return {'__proto__': parent}
}

new 调用的原理是:

  1. 创建一个新对象, 其原型为构造函数的prototype属性
  2. 把这个对象作为this值, 传相应的参数调用构造函数, 构造函数就可以用this初始化对象属性值
  3. 返回这个新创建的对象

所以可以自己写个New函数来模拟new的实现:

1
2
3
4
5
6
7
8
9
10
11
// 模拟实现表达式 new fn(x)
function New(fn) {
let o = {'__proto__': fn.prototype}
return function() {
fn.apply(o, arguments)
return o
}
}
// 最终表达式
New(fn)(x)

当然上述实现很粗糙, 因为:

  1. new 后面的构造函数, 如果不需要参数的话, 构造函数不需要带括号, 即可以这样new fn

  2. 构造函数一般不返回值, 返回的不是对象值也将会被忽略, 但如果构造函数确实返回了一个对象, 则该对象作为new表达式的返回值, 而不是调用构造函数后新建并初始化了的对象

总结: 把原理性的文字改造成代码, 是一种很好的学习方式

参考资料

JavaScript权威指南(第六版)

ES6入门

http://blog.vjeux.com/2011/javascript/how-prototypal-inheritance-really-works.html

Fork me on GitHub