此篇文章为本人学习过程中总结的相关笔记,若有错误或不恰当之处,欢迎指正。
JavaScript是一门面向对象语言,但是它不同于其它面向对象语言,它的面向对象是通过原型链实现的。
prototype
在JavaScript中,每一个函数,都有一个prototype属性,属性值为一个普通的对象,这个prototype对象中有一个默认的属性constructor,指向函数本身。
我们先来看一下JavaScript中基于原型链的继承是怎么工作的
function Klass(x) {
this.x = x;
}
Klass.prototype.y = 2;
Klass.prototype.z = {a: 1};
console.log(Klass.prototype);
// 这时,Klass.prototype属性是这样的
// {
// y: 2,
// z: {a: 1},
// __proto__: Object.prototype
// }
let klass1 = new Klass(1);
let klass2 = new Klass();
klass1.x; // 1
klass1.y; // 2
klass1.z; // {a: 1}
klass2.x; // undefined
klass2.y; // 2
klass2.z; // {a: 1}
上述代码中,klass1的x属性值为传入的参数1,klass2的x属性因为参数为undefined被赋值为undefined,这个没有问题,但是它们的y属性和z属性是哪里来的呢?是从原型链上找到的,即从Klass.prototype对象上拿到的。下面来验证一下。
klass1.z === klass2.z; // true
klass1.z === Klass.prototype.z; // true
klass1.z.a; // 3
klass2.z.a; // 3
Klass.prototype.z.a; // 3
// 说明两个实例的z属性和Klass.prototype.z是同一个引用
klass1.y = 3;
klass1.y; // 3
klass2.y; // 2
上面的代码中,为什么klass1和klass2的y属性不相同了呢?我们继续看看现在的klass1和klass2是什么。
console.log(klass1); // Klass {x: 1, y: 3}
console.log(klass2); // Klass {x: undefined}
klass1的y属性值为3的原因就在上述代码中了,因为klass1.y = 3;这条赋值语句,动态为klass1对象添加了一个值为3的y属性。当我们通过klass1.y取其y属性时,对象本身存在y属性,就不会继续沿着原型链向上查找了。
此时,klass1的y属性为其自身的y属性,klass2的y属性为其原型链上(即Klass.prototype)的y属性。
__proto__
__proto__为对象的原型,所有的对象均有__proto__属性。注意:这里的__proto__并非是一个标准属性,只是某些浏览器暴露出来的属性。
上文说到,当访问对象的属性时,若对象本身没有这个属性,便会沿着原型链去查找,这个原型链就是通过__proto__链接起来的。实例的__proto__属性指向构造函数的prototype,即实例的原型指向构造函数的prototype属性。
obj.__proto__ === Klass.prototype; // true
我们来看一个栗子:
function Pet(name, age) {
this.name = name;
this.age = age;
}
Pet.prototype = {
constructor: Pet,
eat (){
console.log(`${this.name} is eating.`);
},
legs: 4,
};
function Dog(...param) {
Pet.call(this, ...param);
}
Dog.prototype = Object.create(Pet.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.speak = function () {
console.log(`${this.name} is speaking wang.`);
};
let doge = new Dog('doge');
function Monkey(...param) {
Pet.call(this, ...param);
}
Monkey.prototype = Object.create(Pet.prototype);
Monkey.prototype.constructor = Monkey;
Monkey.prototype.legs = 2;
let jaxssson = new Monkey('jaxssson');
doge.speak(); // doge is speaking wang.
doge.eat(); // doge is eating.
console.log(doge.legs); // 4
jaxssson.eat(); // jaxssson is eating.
console.log(jaxssson.legs); // 2
把上面代码的原型链画出来,大致如下。访问实例属性时,沿原型链查找,直到Object.prototype为止。
顺便一提,通过不同方式的创建对象,__proto__的指向会有不同。上面是通过new操作符声明实例,所以实例的__proto__指向构造函数的prototype属性。除此之外,还常有以下两种情况。
// 通过字面量创建
let obj = {};
obj.__proto__ === Object.prototype; // true
// 通过Object.create创建
let obj2 = Object.create(obj);
obj2.__proto__ === obj; // true
第一种通过字面量创建的对象,是Object的一个实例。 第二种通过Object.create创建的对象,会与参数中的对象建立原型链联系,将obj2的__proto__指向obj。
之前见到过一个有趣的现象
Function instanceof Object; // true
Object instanceof Function; // true
关于instanceof,不是很了解的可以去看一下我上一篇文章:传送门
我们先来分析一下出现上面现象的原因:
首先,在JavaScript中,非原始类型皆对象,构造函数也不例外,所以Function instanceof Object返回true是没什么问题的。
Function.prototype.__proto__ === Object.prototype; // true
然后,Object是一个构造函数,既然它是函数,那它肯定是Function类的实例,所以Object instanceof Object返回true也是合理的。
Object.__proto__ === Function.prototype; // true
分析起来是可以的,但是总觉得是怪怪的。按照上面的思路,Function是一个构造函数,所有的构造函数又都是Function的实例,那么Function是Function的实例?我们来验证一下。
Function.__proto__ === Function.prototype; // true
Function instanceof Function; // true
好吧,确实就是这么回事,感觉有点乱,是时候理一下Function和Object之间的关系了。