先来看这么一道题
class Cat{
constructor(){
console.log('cat');
}
}
Cat.prototype.constructor = () => {
console.log('dog');
}
let Tom = new Cat(); //输出是什么?
答案是cat。这个问题很奇怪,因为平常根本不会有这种写法,但不妨碍我们来理解js中面向对象编程的实现方式。要搞清楚输出为什么是是cat就要理解原型(prototype)、构造函数(constructor)、实例(instance)、类(class)之间的关系。
构造函数
js中通过构造函数生成新对象,如
function Dog(name, color){
this.name = name;
this.color = color;
}
let Pluto = new Dog('Pluto', 'black');
或者是用class
class Dog {
constructor(name, color){
this.name = name;
this.color = color;
}
}
let Pluto = new Dog('Pluto', 'black');
class是一个语法糖,其本质上还是一个函数
typeof Dog //"function"
原型
在生成新对象时发生了什么呢?这里就要说到prototype了。prototype是每个函数都有的属性,它定义了由该函数创建的实例所基于的对象,而构造函数定义了在创建每个实例的时候需要执行的内容。画个图来表示。
创建的实例时,构造函数先生成一个新对象,将
this指向这个新对象,新对象的原型(浏览器环境下是__proto__)指向构造函数的原型,然后执行函数。
执行构造函数时定义的属性实际上是定义在实例上的,因此不同实例之间各有一份,互不影响。
Pluto.hasOwnProperty('color') //true
Dog.hasOwnProperty('color') //false
Dog.prototype.hasOwnProperty('color') //false
如果希望所有实例共享一个属性,则应该定义在原型上
Dog.prototype.bark = () => {
console.log('bark');
}
//class中的属性也是定义在原型上的,上面的写法等同于下面
class Dog{
constructor(name, color){
//...
}
bark(){
console.log('bark');
}
}
要注意的是,实例本身是没有constructor属性的,调用constructor时实际调用的是原型的constructor。
Pluto.hasOwnProperty('constructor') //false
Pluto.__proto__.hasOwnProperty('constructor') //true
另外,所有对象都有构造函数,构造函数本身也是一个对象,也有构造函数。
继承
js中的继承,就是把子类(构造函数)的原型指向父类的一个实例,并把原型的构造函数指向自己。extends关键字做的也是同样的事。
let Husky = function() {};
Husky.prototype = new Dog();
Husky.prototype.constructor = Husky;
原型链
读取对象的属性时,先查找对象本身的属性,如果没有,就到它的原型以及原型的原型去找。如果直到最顶层的null还是找不到,则返回undefined。这条查找链上的所有对象构成原型链。
instanceof 运算符
A instanceof B即是查找B.prototype是否在A的原型链上
let Coco = new Husky();
Coco instanceof Dog; //true
Husky instanceof Dog //false
开头的问题
要搞懂这个问题现在应该已经很简单了。声明Cat的时候,是这样的:
修改Cat.prototype.constructor的时候,其实是改变了原型的constructor属性的指向。
执行new Cat()的时候,执行的还是Cat构造函数,但是新实例的constructor属性却指向新函数。
因此new Cat()的时候,输出的是cat。
还有一点要注意的是,如果用Object.create来继承的话,新对象只是简单的把原型指向父对象,不会有新的构造函数。
let Rita = Object.create(Tom);
Rita.__proto__ === Tom; //true
Rita.__proto__.hasOwnProperty('constructor'); //false
Rita.constructor === Tom.__proto__.constructor; //true