阅读之前可以先看看JS语法细节汇总的第14/15/16条
首先,要搞清楚什么是继承。
继承并不是简简单单的爸爸和儿子的关系,继承最终为的是代码的复用,所以只要是代码复用都可以称之为继承。
我们通常以为儿子能够拿到爸爸的所有就是继承,其实不是的。
//下面的例子,儿子仅仅继承父亲的一个方法,也是继承。
// 这也是组合继承的一个方式,需要谁的方法就从谁那里拿,按需继承
// 很好地保护了原型链的长度和instanceof的准确性,性能不错,十分推荐
function Father () {}
Father.prototype.say = function () { console.log('I am father') }
function Son () {}
Son.prototype.say = Father.prototype.say
var exp = new Son()
exp.say() // I am father
一般来说,一个简单的构造函数和它的实例:
function A (val) {
this.value = val
}
// 所有实例的公用方法,放在原型对象上
A.prototype.say = function () {
console.log('this.value: ' + this.value)
}
var exp1 = new A(1)
var exp2 = new A(2)
exp1.say() // this.value: 1
exp2.say() // this.value: 2
再看
function A (val) {
this.value = val
// 定义在构造函数中,每新建一个实例就新建一个函数
this.test = function () {
console.log('I am NO.' + this.value)
}
}
var exp1 = new A(1)
var exp2 = new A(2)
exp1.test() // I am NO.1
exp2.test() // I am NO.2
比较之后就可以发现,第一种方法比第二种方法可好多了。
所以我们的方法,就可以全部定义在构造函数的原型对象上。
function A (...args) {
// 每个实例都有的私有属性初始化
this.property = args
// 如果是动态生成的方法,也不是不可以定义私有方法
this._method = (function () {
if (...) {
return f1
} else if (...) {
return f2
} else {
return f3
}
})()
}
A.prototype = Object.assign(
A.prototype,
// 一些公共的方法
{
method1,
method2,
...
},
// 一些公共的属性
{
property1,
property2,
...
}
)
var a = Object.assign(
new A(),
// 实例的私有方法
{
method,
...
},
// 实例的私有属性
{
property,
...
}
)
接下来是基于原型链和继承的一堆思考,如果想通了,那可以从我这毕业了。
// 我们都知道原型链,都知道寻找一个对象的属性(方法)是从原型链一层一层往上找,那么过程是怎样的?
function A () {}
var exp = new A()
// 我要取一个属性,怎么寻找?
exp.property
// 过程:exp对象上有没有该属性?没有
// 往上找到它的原型对象
exp.__proto__ === A.prototype
exp.__proto__.property
// 过程:exp.__proto__对象上有没有该属性?没有
// 往上找到它的原型对象
exp.__proto__.__proto__ === A.prototype.__proto__ === Object.prototype
exp.__proto__.__proto__.property
// 过程:exp.__proto__.__proto__对象上有没有该属性?没有
// 往上找到它的原型对象
exp.__proto__.__proto__.__proto__ === A.prototype.__proto__.__proto__ === Object.prototype.__proto__ === null
exp.__proto__.__proto__.__proto__.property
// 过程:exp.__proto__.__proto__.__proto__对象上有没有该属性?没有
// 往上找到它的原型对象,null到头了,没找到,返回undefined
上面看懂了吗?是实例对象,就看__proto__属性;是构造函数,就看prototype属性。
也可以从上面过程看出,属性是如何覆盖的。如果在一开始就找到了,那么就不会往下找了。
永远记得,实例对象的__proto__就是它构造函数的prototype
// 接上面,加深理解,这里的A是作为实例对象而不是构造函数
A.__proto__ === Function.property
A.__proto__.__proto__ === Function.property.__proto__ === Object.prototype
A.__proto__.__proto__.__proto__ === Function.property.__proto__.__proto__ === Object.prototype.__proto__ === null
以上就是原型链面纱下的真实面目,长的真是太可爱了有没有~(^ _ ^)
如果写成Object.getPrototypeOf()的形式,就会看得非常恼火。
接下来理解instanceof的作用机制就好理解多啦!
instanceof运算符的左边是实例对象,右边是构造函数。它会检查右边构造函数的原型对象(prototype),是否在左边对象的原型链上。
// 仍然使用上面的例子
exp instanceof Object // 左边是实例对象,右边是构造函数
// 其实等价于以下语句组合
exp.__proto__ === Object.prototype // 不相等,下一个
exp.__proto__.__proto__ === Object.prototype // 这里相等了,返回true,如果不相等,继续往下走
exp.__proto__.__proto__.__proto__ === Object.prototype // 这里左边已经是null了,返回false
// 左边会一直找直到null
// 如果右边不是构造函数,直接报错
// 如果左边不是对象,或者是null,都返回false
有以下继承的方式,最符合其他语言的继承定义:
继承的私有属性还是私有属性,继承的共有属性(方法)还是共有的属性(方法)
// 第一种
function Father (val) {
this.val = val
}
function Son (val) {
// 这种方式,对下面的语句无顺序要求,和class语法不同
// 但是注意同名属性会覆盖,到底用谁的取决于你
Father.call(this, val * 2)
this.value = val
}
Son.prototype = Object.create(Father.prototype)
Son.prototype.constructor = Son
// 第二种
function Father (val) {
this.val = val
}
function Son (val) {
this.value = val
Father.call(this, val * 2)
}
Son.prototype = new Father() // 这里会带上Father实例的属性和方法(但取值的时候会被覆盖,也就是说多占了资源)
Son.prototype.constructor = Son
// 第三种,有时候,基于某个实例定制化继承也是极好的,尤其适合基于单例模式的继承
function Father (val) {
this.val = val
}
function Son (val) {
this.value = val
}
var example = new Father()
// 然后做一系列针对example的操作
Son.prototype = example
Son.prototype.constructor = Son