一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第19天,点击查看活动详情
她是我老母,这里的老母其实是老婆的意思,对很多初学者来说原型链是比较难的知识点之一,学了忘、忘了又学,学了好几遍还是云里雾里。依小编浅见,其原因在于prototype这个词让人很困惑。明明是构造函数和原型对象一起创造了对象,为什么当初要使用prototype这个词呢?这很容易让人误解原型对象是构造函数的原型。其实他们的地位是平等的, 小编认为它们的关系应该是夫妻之间的关系。
另外,为了便于理解和记忆,本文把constructor比喻成母亲,但是这个属性是保存在原型对象上的,正如前面所写,构造函数和原型对象应该是夫妻关系,这让我想到了经典小品,白云黑土里面的台词-她是我老母这句台词,这也正是本文题目的来由。
1.名词解释
prototype :显式原型,本文称之为构造函数的老公,只有函数对象(箭头函数除外)有这个属性,普通对象没有这个属性。
constructor:构造函数,本文称之为new出来对象的母亲,原型对象的老母(即老婆), 原型对象都有这个属性。子对象可以通过原型对象查找到这个属性。
__proto__:隐式原型,本文称之为new出来对象的父亲,每个对象都有这个属性。
因为函数在js里面即是函数又是对象,所以上面的三个属性,函数对象全部拥有。而普通对象只有constructor和__proto__属性。js里面函数即是函数又是对象,这句话要多读几次,否则很难理解原型链。
2.prototype属性
prototype是函数独有的属性,构造函数和原型对象共同生下对象这个孩子,prototype用来标记构造函数的老公是谁。
函数在创建的时候,会默认同时创建该函数的prototype对象,请看如下代码:
function Person(name, age) {
this.name = name;
this.age = age;
}
console.log(Person.prototype)
运行结果:
可以看到构造函数的
prototype属性对象包含两个属性一个是__proto__,用于保存自己父亲是谁,另一个是constructor(即构造函数本身),用于今后告诉自己的儿子(new出来的对象)母亲是谁。
3.constructor属性
constructor属性是保存在原型对象上的,用于指向构造函数,即原型对象的老婆(小品里面的老母),new出来的对象上没有该属性,但是可以通过原型对象找到constructor,即new出来的对象的母亲。
function Person(name, age) {
this.name = name;
this.age = age;
}
let p = new Person('小明', 28);
console.log(p);
运行结果:
可以看出,
new出来的对象上并没有constructor属性,但是可以通过原型对象来获取这个属性。
4.__proto__属性和原型链
__proto__属性用于指向new出来的对象的原型对象即父亲,
new出来的对象的__proto__和构造函数的prototype属性指向同一个地方,儿子的父亲即母亲的老公。
function Animal() {
this.name = 'father';
}
let animal = new Animal();
console.log(animal.__proto__ == Animal.prototype);//打印出true
原型对象可以通过__proto__找到原型的原型,通过__proto__形成的链就叫原型链。
构造函数上的方法不是共享的。
function Cat() {
this.run = function() {
console.log('run');
}
}
let c1 = new Cat();
let c2 = new Cat();
console.log(c1.run === c2.run); // false 可见构造函数上的方法是不共享的
而原型链上的方法和属性是子对象共享的,从而节约内存,提高性能。
function Cat() {
}
Cat.prototype.run = function() {
console.log("吃饭");
}
let c1 = new Cat();
let c2 = new Cat();
console.log(c1.run === c2.run); // true 说明原型对象上的方法是共享的
5.构造函数链
光是知道原型链还不够,还存在一个构造函数链,通俗的理解,我们可以把原型链理解成父系族谱,构造函数链,我们可以理解为母系族谱。
function Foo() {...};
let f1 = new Foo();
对象f1 的母亲是Foo()函数, 那么Foo()函数,作为对象,它的母亲是谁呢?答案是Function()函数对象,那Function()的母亲又是谁呢?答案是它自己。这里就到了构造函数链的尽头。Function()非常的特殊,下面你会看到。
6.以这种语言的思维,做几道题目
function Foo(){};
let f1 = new Foo();
let f2 = new Foo();
console.log(f1.__proto__ == Foo.prototype); // 小孩1的父亲是母亲的老公
console.log(f2.__proto__ == Foo.prototype); // 小孩2的父亲是母亲的老公
Foo.prototype.__proto__ == Object.prototype; // 母亲的老公的父亲是Object.prototype
Object.prototype.__proto__ == null; // Object.prototype的父亲是null
Foo.prototype.constructor == Foo; // 老婆的老公的老婆是自己
Foo.__proto__ == Function.prototype; //构造函数的父亲是Function.prototype
Function.prototype.__proto__ == Object.prototype; //Function的老公的父亲是Object.prototype
//以下体现了Function()的特殊之处,乱了,记忆即可
Function.__proto__ == Function.prototype //Function的父亲也是Function的老公
Function.prototype.constructor == Function; //Function的老公的老妈是Function
7.重点来了,一张非常重要的图
以下面两行代码为例:
function Foo() {...};
let f1 = new Foo();
这张图并不难懂,需要注意的是Function()函数, 它的隐式原型也是它的prototype,用本文的语言翻译就是它的父亲也是它的老公, 同时它的构造函数是它自己,也就是它的母亲是它自己。
另外,null对象也比较特殊,没有父亲母亲,也没有老婆,只有儿子Object.prototype.
8.总结
爷爷的爷爷...(原型的原型...)的终点是null,外婆的外婆...(构造函数对象的构造函数...)的终点Function(),以它们为起点构成了原型链这一家族。
用父亲和母亲的概念来解释原型链,可能不是特别的严谨,但是把过程说的更加的清晰明白便于理解和记忆是本文的目的。