原型

全貌

  • User构造函数实例化了一个User实例,所以User构造函数的prototype原型(对象) == User实例的__proto__隐式原型,它们构成了这样的三角关系。

  • User构造函数本质上是new Function()而来的一个实例,所以User构造函数、Function构造函数和Function原型(对象)之间也有这样的三角关系。

  • Function原型(对象)也是个对象呀,所以是new Object()而来,那么Function原型(对象)是Object构造函数的一个实例,则Function原型(对象)、Object构造函数和Object原型(对象)也构成了这样的三角关系。

  • 特殊的点

    • Object原型(对象)的隐式原型是null,也就是说Object.prototype.__proto__ null
    • Object构造函数本质上也是new Function()而来的一个实例,那么,其原型对象就是Function原型(对象),即Object.prototype == Function.prototype
    • 在JS中,万物皆对象,Function构造函数本质上也是对象,但它比较特殊,它的隐式原型是它的原型(对象),也就是Function.__proto__ == Function.prototype

构造函数

构造函数和普通函数本质上没什么区别,只不过使用了new关键字创建对象的函数,被叫做了构造函数。构造函数的首字母一般是大写,用以区分普通函数,当然不大写也不会有什么错误。等价于类class

1
2
3
4
5
6
7
8
9
10
function Person(name, age) {
this.name = name;
this.age = age;
this.species = '人类';
this.say = function () {
console.log("Hello");
}
}

let person = new Person('xiaoming', 20);
1
2
3
4
5
6
7
8
9
10
11
12
class Person {
constructor(name, age){
this.name = name;
this.age = age;
this.species = '人类';
this.say = function () {
console.log("Hello");
}
}
}

let person = new Person('xiaoming', 20);

以上两种写法都可以用来创建对象,效果等价。


使用new操作符调用构造函数,做了如下几步操作

  1. 创建一个新的对象;
  2. 将新对象的隐式原型(_proto_)指向构造函数的原型(对象)(prototype);
  3. 将构造函数的 this 指向新对象;
  4. 执行构造函数的代码;
  5. 如果构造函数返回非空对象,则返回此对象,否则,则返回新建的对象;

构造函数和普通函数在本质上并没有区别,构造函数首字母大写只是一个约定成俗的规范而已。

使用 new 操作符调用普通函数也可以创建实例,所以,正确的说,使用 new 操作符调用函数,可使该函数成为构造函数。直接调用 person 函数的话,由于是全局环境下调用,所以 person this 绑定的是 window

原型(对象)(prototype)

在js中,每一个函数类型的数据(构造函数)都有一个叫做prototype的属性,这个属性指向的是一个对象,就是所谓的原型(对象),它是一个对象。对于原型(对象) 来说,它有个constructor属性,指向它的构造函数

二者互相指

原型(对象)最主要的作用就是用来存放实例对象的 公有属性 公有方法

把这些公有的属性和方法放在原型对象里共享,避免重复创建相同的属性和方法造成浪费:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Person(name, age) {
this.name = name;
this.age = age;
}

Person.prototype.species = '人类';
Person.prototype.say = function () {
console.log("Hello");
}

let per1 = new Person('xiaoming', 20);
let per2 = new Person('xiaohong', 19);

console.log(per1.species); // 人类
console.log(per2.species); // 人类

per1.say(); // Hello
per2.say(); // Hello


console.log(per1.constructor); // Person()
// 这个constructor是原型对象的属性,在这里能被实例对象使用,是因为到实例对象的__proto__上找去了
// 这就要说到原型链了。

获取对象的(隐式)原型

  • Object 对象的一个方法 isPrototypeOf 可以确定对象和(隐式)原型是否有关系console.log(Person.prototype.isPrototypeOf(person1)); // true
  • Object 对象的一个方法 getPrototypeOf 可以获取对象的(隐式)原型console.log(Object.getPrototypeOf(person1) === Person.prototype); // true

重写对象的(隐式)原型

  • Object 对象提供一个方法 setPrototypeOf 重写对象(隐式)原型
1
2
3
4
5
6
7
8
9
10
let father = {
name: 'father'
}
let child = {
childName: 'child'
}
Object.setPrototypeOf(child, father);
console.log(child.childName); // child
console.log(child.name); // father
console.log(Object.getPrototypeOf(child) === father); // true
  • Object 对象提供方法 create 可以在创建对象的时候指定对象的(隐式)原型。
1
2
3
4
5
6
7
8
let father = {
name: 'father'
}
let child = Object.create(father)
child.childName = 'child'
console.log(child.childName); // child
console.log(child.name); // father
console.log(Object.getPrototypeOf(child) === father); // true

遮蔽原型

实例不能重写原型的属性,但是实例可以定义一个属性来遮蔽原型上的同名属性。

Object 的原型提供了一个方法 hasOwnPrototype 用于判断属性是在(隐式)原型还是实例上,在实例上,返回 true ,在原型上,返回 false

1
2
3
4
5
6
7
8
9
10
11
12
let Person = function () {};
Person.prototype.name = 'proto';
let person1 = new Person();
person1.name = 'Tom';
console.log(person1.name); // Tom
// 引擎会先在实例中找 name 属性,因为实例有 name 属性,所以就输出 Tom。
console.log(person1.hasOwnPrototype(name)); // true
delete person1.name;
// 使用 delete 操作符可以删除实例的属性
console.log(person1.name); // proto
// 引擎会在实例中查找属性 name ,找不到时,引擎会在实例的原型中找。
console.log(person1.hasOwnPrototype(name)); // false

隐式原型(__proto___)

隐式原型是利用 __proto__ 属性查找原型,这个属性 指向当前对象的构造函数的原型对象这个属性是对象类型数据的属性,所以 实例对象可以使用

1
2
console.log(per1.__proto__ === Person.prototype); // true
console.log(per2.__proto__ === Person.prototype); // true

形象的比喻

  • 构造函数$\longrightarrow$雕刻师,其prototype指向原型(对象)
  • 原型(对象) $\longrightarrow$设计图/设计原型它也有 __proto__ $\longrightarrow$次稿设计图/设计原型,初稿设计图/设计原型。 最最早的初稿是一张白纸,也就是说Object.prototype.__proto__null
  • 通过构造函数new出来的实例对象$\longrightarrow$一个个雕塑作品,其__proto__指向指向原型(对象)

原型链

简介

原型链的核心就是依赖对象的__proto__的指向,当自身不存在的属性时,就一层层的扒出创建对象的构造函数,直至到Object时,就没有 __proto__ 指向了。

不仅实例有一个__proto__属性,原型对象也有一个__proto__属性指向它的原型,从中继承方法和属性,一层一层,以此类推,直到Object.prototype = null,s通俗来讲:原型链就是多个对象通过__proto__的方式连接了起来。

instanceof

instanceof就是基于 原型链 判断变量的类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function myInstanceof(left, right) {
if (typeof left !== 'object' || left === null) return false;
let pro = Object.getPrototypeOf(left);
while(true) {
if(pro === null) return false;
if(pro === right.prototype) return true;
pro = Object.getPrototypeOf(pro);
}
}

// 测试函数
myInstanceof(12, Number) // false
myInstanceof(new Number('123'), Number) // true
myInstanceof([], Array) // true

总结

  • Object是所有对象的爸爸或者祖先,所有对象都可以通过_proto_找到它;
  • Function是所有函数的爸爸,所有的函数都可以通过_proto_找到它;
  • 函数的prototype是一个对象,叫原型(对象),包含一些属性和方法;
  • 对象的隐式原型 __proto__ 属性指向其构造函数的原型(对象), __proto__ 将多个对象和隐式原型连接起来,叫做原型链;