JS中对象的继承

阅读之前可以先看看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

参考文档

1、《JavaScript 教程》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值