JavaScript 继承
一般的面向对象的语言对继承的处理方式都是 class-based,即基于类的继承。一般是说子类继承了父类,继承的主体是类。而类的对象之间的继承体现于类的继承中。
以 Java 为例
public class A{} // 父类 A
public class B extends A {} // 子类 B 继承了父类 A
在 JavaScript 的世界中则并非如此。JS 通过原型链的方式来实现继承,是基于对象的继承。下例中 b 对象继承了 a 对象的所有属性和方法。
function People(sex) {
this.sex = sex;
this.say = function () {
console.log(this.sex);
}
}
let a = new People("man"); // 实例化 a 对象
let b = Object.create(a); //b 对象继承 a 对象,继承了 a 的属性和方法
console.log(b.sex); //man
b.say(); //man
在这里我们可以说,a 对象是 b 对象的原型。这样的继承方法不需要类,继承在对象之间完成。
原型继承机制
对象进行属性查找时,优先查找自己的属性,如果没有查找到再去查找对象的原型上的属性。如果原型对象依旧没有找到,那么递归查找指导原型根部,如果依旧没有找到那么返回undefined。
st=>start: 对象 a
op=>operation: 查找属性 p
cond=>condition: 是否有属性 p
sub1=>subroutine: 转到对象 a 的继承的原型对象
io=>inputoutput: 返回属性 p
e=>end: 结束
st->op->cond
cond(yes)->io->e
cond(no)->sub1(right)->op
这就是原型链的思想,层层往上一级链条上查找。
ES5 中,可以通过 Object.getPrototypeOf(obj) 来获取obj 对象的原型,在 Chrome 浏览器中也可以使用非标准的obj.__proto__。
JS 中定义一个类的是通过声明一个函数来实现,这个函数也称为构造器。JS 中的构造器有一个特殊的属性(函数也是对象,所以也有属性) ———— prototype。此 prototype 用来定义通过构造器构造出来的对象的原型,构造器内部的代码用来给对象初始化。如下
function People(name) {
this.name = name;
}
console.log(People.prototype);
//{constructor: People} People.prototype 的构造器为 People
People.prototype.say = function() {
console.log(this.name);
}
People.prototype.sex = "female";
let p1 = new People("soda");
p1.constructor //People
p1.sex //female
p1.say(); //soda
我们可以看出 p1 对象获得了 People.prototype 的属性和方法。实际上,通过 new People(),p1 以People的 prototype 为原型来创建了一个新对象,并获得了该原型的全部属性和方法。
p1 = new People() // 等价于
p1 = Object.create(People.prototype) // 用 People 的 prototype 作为原型来创建一个新对象
People.apply(p1, "soda") // 执行构造器用来初始化,构造器中的 this 指向 p1
p1的原型是 People.prototype,p1 是People构造 (new) 出来的。
为了让 p1.constructor 能构正确执行 p1 的构造器,一个构造器默认的 prototype 上已经存在 constructor 属性,并且指向构造器本身。
⚠️ People.prototype并非 People 的原型,而是 People 构造的对象的原型,即 p1 的原型。People本身是一个 Function 类型,你可以把它理解为一个 Function 构造出来的对象,它的原型是 Object.getPrototypeOf(People) 或People.__proto__。 因为 People 是一个函数对象,所有函数都构造自 Function,原型是 Function.prototype。People.prototype 是 People 构造出来的实例的原型,不是 People 的原型。
People.__proto__ === Function.prototype
//true
探究 constructor
function Foo() {}
let foo = new Foo();
foo.constructor === Foo.prototype.constructor //true
foo.constructor === Foo // true
Foo.constructor === Object.constructor // true
Object.constructor === Function.constructor // true
Function.constructor === Function //true
在这个样例我们可以得知三个信息,
对于
foo对象而言,Foo函数是其构造器,foo是Foo函数构造的对象。对于一切函数
fn而言,Function是它们的构造器,fn是Function函数构造的对象,函数也是一种特殊的对象。Function的构造器是Function本身。Function既是对象,又是函数。
那么我们可以推断出什么?
因为所有的函数都有同一个构造器,所以所有的函数都有同一个原型,这个原型就是 Function 类的原型Function.prototype
即 Object.getPrototypeOf(fn) === Function.prototype, 所有的函数都是一个类。
Function 的双重性
Function.__proto__ === Function.prototype // true
Object.getPrototypeOf(Function) === Function.prototype // true
等式的左边的 Function 作为对象,求 Function 对象继承的原型
等式的右边的 Funciton 作为构造器,求 Function 类的原型
而对于一般的函数来说则不能这么比较
function Flower(){}
let flower = new Flower()
flower.__proto__ === Flower.prototype //true
Flower.__proto__ === Function.prototype //true
所以 Function 是一种特殊的函数,也是一种特殊的对象。
- 它的构造器是它的自身。
- 它与所有的函数继承同一个原型。
- 所有的函数都由
Function来构造。
Object 与 Function
聊完 Function 再来聊聊 Object,JavaScript 原生提供Object 对象。
function Foo(){}
let foo = new Foo();
let obj = new Object();
foo.__proto__.__proto__ === Object.prototype //true
foo.__proto__.__proto__ === obj.__proto__ //true
可以看出,所有的对象都继承自 Object 对象。
Object是所有 Object 对象的构造器,而根据 Function 的有趣性质,我们可以获得下面的结果
Object instanceof Function // true
Function instanceof Object // true
首先,我们需要弄清楚 instanceof 的原理
instance instanceof People // 等价于
function instanceOf(instance, prototype) {
var proto = Object.getPrototype(instance); // 取对象原型
if(proto === null) return false; // 空
if(proto === prototype) return true; // 原型匹配
return instanceOf(proto, prototype); // 递归检查原型的原型
}
instance(instance, People.prototype);
JavaScript 中的继承概念归根到底是原型的继承,那么 instanceof 实际上就是将 instance 的原型与构造器的 prototype 进行递归检查。
Object是一个构造器,一个函数,而函数也是一个对象,这个对象由 Function 这个构造器构造,所以
Object.__proto__ === Function.prototype 上式成立
Function是一个函数,一个对象,而所有的对象都是继承于 Object 对象的原型,
Function.__proto__ === Object.prototype 下式成立
总结
- JavaScript 的继承是通过原型链继承来实现的
- 所有的函数都是对象,它们的构造器是
Function Function是自身的构造器- 所有的对象的最终原型是
Object.prototype,即所有的对象都继承于Object对象