写作不易,Star是最大鼓励,感觉写的不错的可以给个Star⭐,请多多指教。Github地址

原型

  1. 函数的prototype属性
    • 每个函数都有一个prototype属性,它默认指向一个Object空对象(即原型对象)
    • 原型对象中有一个属性constructor,指向函数对象
  2. 操作原型对象
    • 可以通过prototype属性找到原型对象,并可以给该对象添加属性和方法(通常是添加方法)
    • 作用:构造函数的所有实例对象自动拥有构造函数原型中的属性和方法
console.log(Date.prototype, typeof Date.prototype);
function fn() {}
console.log(fn.prototype);
// 给原型对象添加方法==>一般给实例使用
fn.prototype.sayName = function() {}
console.log(fn.prototype); // 默认指向一个Object空对象(空对象即没有自定义的属性和方法)

// 原型对象中有一个属性constructor,它指向函数对象
console.log(Date.prototype.constructor === Date); // true
console.log(fn.prototype.constructor === fn); // true
1
2
3
4
5
6
7
8
9
10

显式原型和隐式原型

  1. 每个函数都有一个prototype属性,即显式原型对象
  2. 每个实例对象都有一个__proto__属性,称为隐式原型对象
  3. 对象的隐式原型的值等于其对应构造函数的显式原型的值

总结:

  • 函数的prototype属性:在定义函数时自动添加的,默认值是一个Object空对象
  • 对象的__proto__属性:创建对象时自动添加的,默认值为构造函数的prototype属性值
  • 我们可以直接操作显式原型,但不能直接操作隐式原型(ES6之前)

特别注意:原型链是基于隐式原型来实现的。

function Person() { // 内部语句:this.prototype = {}

}
console.log(Person.prototype);
let p = new Person(); // 内部语句:this.__proto__ = Person.prototype
console.log(p.__proto__);
// Person.prototype和p.__proto__都是引用类型变量,保存的是原型对象的地址值。
console.log(Person.prototype === p.__proto__); // true

Person.prototype.sayName = function() {
    console.log('sayName()');
}

p.sayName();
1
2
3
4
5
6
7
8
9
10
11
12
13
14

需要注意:属性在查找的时候,实际上通过的隐式原型来向上查找的,而不是显式原型。

原型链

  1. 原型链(图解)
  • 访问一个对象的属性时
    • 先在自身属性中查找,找到则返回
    • 如果没有找到,沿着__proto__这条链继续向上查找,找到返回
    • 如果最终没有找到,返回undefined
  • 别名:隐式原型链
  • 作用:查找对象的属性和方法
  1. 构造函数/原型/实体对象的关系(图解)
console.log(Object); // ƒ Object() { [native code] } 是一个构造函数名称,存在栈内存中
console.log(Object.prototype);
console.log(Object.prototype.__proto__); // null
function Person(name, age) {
    this.name = name;
    this.age = age;
    this.sayAge = function() {
        console.log(this.age);
    }
}
Person.prototype.sayName = function() {
    console.log(this.name);
}
let p = new Person('lisi', 12);
p.sayAge(); // 12
p.sayName(); // lisi
console.log(p.toString()); // [object Object]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

牢牢记住:每个实例对象都有一个隐式原型属性__proto__,每个函数都有一个显式原型属性prototype。

特别注意:每个函数都是通过new Function()产生的,因此每个函数都有一个隐式原型属性(proto)指向Function.prototype。并且所有函数的__proto__都是一样,均指向Function.prototype。

特殊情况

  • Function.__proto__和Function.prototype均指向Function.prototype。Function既可以作为实例对象(new Function()产生),也可以作为构造函数。即所谓的鸡生蛋蛋生鸡。
  • Object构造函数也是new Function()产生的,因此有Object.__proto__ === Function.prototype。
  • 原型链的尽头是null。
/*
1. 函数的显式原型指向的对象默认是空的Object实例对象(但是Object构造函数不满足)
*/
console.log(Person.prototype instanceof Object); // true
console.log(Object.prototype instanceof Object); // false
console.log(Function.prototype instanceof Object); // true

// Function.prototype是Object构造函数的实例对象,因为有实例对象隐式原型__proto__等于其构造函数的显式原型prototype。
console.log(Function.prototype.__proto__ === Object.prototype); // true
/*
2. 所有函数都是Function的实例(包括Function自身)
*/
console.log(Function.__proto__ === Function.prototype); // true
/*
3. 原型链的尽头null
*/
console.log(Object.prototype.__proto__); // null
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

对象属性读取和设置

原型链属性的问题:

  1. 读取对象的属性值时会自动到原型链中查找。
  2. 设置对象的属性值时,不会查找原型链,如果当前对象中没有该属性,直接添加该属性并设置其值。
  3. 方法一般定义在原型中,属性一般通过构造函数定义在对象自身上
function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.sayName = function() {
    console.log(this.name);
}
let p1 = new Person('lisi', 12);
p1.sayName();
console.log(p1);

let p2 = new Person('wangwu', 13);
p2.sayAge = function() {
    console.log(this.age);
}
p2.sayName();
p2.sayAge();
console.log(p2);
console.log(p1.__proto__ === p2.__proto__); // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

instanceof

  1. instanceof是如何判断的?
  • 表达式:A instanceof B(A是实例对象,B是构造函数)
  • 如果B函数的显式原型对象在A对象的原型链上,返回true,否则返回false
  1. Function是通过new自身产生的实例
// 题目1
function Person() {}
let p1 = new Person();
console.log(p1 instanceof Person); // true
console.log(p1 instanceof Object); // true

// 题目2
// 任何函数都是new Function()产生的,Object构造函数也是一样
console.log(Object instanceof Function); // true
console.log(Object.__proto__ === Function.prototype); // true
console.log(Object instanceof Object); // true

// 任何函数都是new Function()产生的,Function构造函数也是一样
console.log(Function instanceof Function); // true
// Function本身也是Function构造函数的实例
console.log(Function.__proto__ === Function.prototype); // true

// Function也是对象
console.log(Function instanceof Object); // true

function Animal() {}
console.log(Object instanceof Animal); // false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

面试题

题目1

function Person() {}
Person.prototype.a = 1;
let p1 = new Person();
// 重写原型,后续的实例对象即实例p2的隐式原型属性将指向新的Person.prototype。而实例p1的隐式原型属性依旧指向原来的原型对象。
// Person.prototype是一个引用类型变量,将指向新的一块堆内存空间。
Person.prototype = {
    a: 2,
    b: 3
}
let p2 = new Person();
console.log(p1.a, p1.b, p2.a, p2.b); // 1 undefined 2 3
1
2
3
4
5
6
7
8
9
10
11

题目2

解析具体参考

var F = function() {};
Object.prototype.a = function() {
    console.log('a()');
}
Function.prototype.b = function() {
    console.log('b()');
}
var f = new F();
f.a(); // a()
f.b(); // Uncaught TypeError: f.b is not a function
F.a(); // a()
F.b(); // b()

Object.a(); // a()
Object.b(); // b()
Function.a(); // a()
Function.b(); // b()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

解决上述问题,需要先梳理清楚实例f和F的原型链: 先简单介绍一下,实例化一个对象(new)到底做了什么?

function Base() {
    this.a = function () {
        console.log('我是Base中的a');
    }
}

Base.prototype.b = function () {
    console.log('我是Base prototype上的b');
}

var obj = new Base();
// 实际上做了以下几件事
// var obj = {};
// obj.__proto__ = Base.prototype;
// Base.call(obj);
// 第一行,我们创建了一个空对象obj
// 第二行,我们将这个空对象的__proto__成员指向了Base函数对象prototype成员对象
// 第三行,我们将Base函数中this上的成员赋值给obj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

展开f的原型链:

// 实例的隐式原型指向构造函数的显式原型
f.__proto__ === F.prototype;
F.prototype.__propto === Object.prototype;

// 因为prototype本质也是对象,继承自Object,所以F.prototype.__proto__ === Object.prototype,即
f.__proto__.__proto__ === Object.prototype;

// 在js中把Object.prototype.__proto_直接赋值成null,即null是原型链的尽头
f.__proto__.__proto__.__proto__ === null;
1
2
3
4
5
6
7
8
9

我们发现:f的原型链中,根本没有Function.prototype什么事,所以答案出来了,f.a()输出a,f.b会报错。

我们再展开F的原型链:

F.__proto__ === Function.prototype;

// 因为prototype本质也是对象,继承自Object,所以Function.prototype.__proto__ === Object.prototype
F.__proto__.__proto__ === Object.prototype;

f.__proto__.__proto__.__proto__ === null;
1
2
3
4
5
6

Function.prototype和Object.prototype都在F的原型链上,都会有输出,因此,F.a()输出a,F.b()输出b。

console.log(f.__proto__.__proto__ === Object.prototype); // true
console.log(f.__proto__.__proto__ === Function.prototype); // false
1
2

解释:

Object.a(); // a()
Object.b(); // b()
Function.a(); // a()
Function.b(); // b()
1
2
3
4

我们来展开Object的原型链:

// Object是函数,继承自Function
Object.__proto__ === Function.prototype;

Object.__proto__.__proto__ === Object.prototype;

Object.__proto__.__proto__.__proto__ === null;
1
2
3
4
5
6

因此,Object.a其实是访问到Object.__proto__.__proto__时,才从Object.prototype上找到相应的a()。

同样展开Function的原型链:

// Function本身也是函数,因此继承自己
Function.__proto__ === Function.prototype;

Function.__proto__.__proto__ === Object.prototype;

Function.__proto__.__proto__.__proto__ === null;
1
2
3
4
5
6

为了引导出万能公式,我再次发出了灵魂拷问:

// 在刚才的前提下,即
Object.prototype.a = function() {
    console.log('a');
}
Function.prototype.b = function() {
    console.log('b');
}

var c = 1;
console.a(); // a
console.b(); // console.b is not a function
c.a(); // a
c.b(); // c.b is not a function
console.log.a(); // a
console.log.b(); // b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

这时,学会展开原型链的同学已经明白答案了,这里直接上万能公式:在js中,万物皆对象,只要在Object.prototype上写的方法,万物都能访问到;而Function.prototype上的方法,只有能通过()方式调用的函数,才能访问到。

因此,我们只需要查看这个对象可不可以通过函数()的形式调用,就能确定他是否能访问Function.prototype。再次回顾之前的问题,f仅仅是个对象,f()是会报not is a function错误的,而F()是可以调用的函数。

Object.prototype.a = function () {
    console.log('我是Object中的a')
}
Object.prototype.b = function(){
    console.log('我是Object中的b')
}
Function.prototype.a = function () {
    console.log('我是Function中的a')
}
Function.prototype.b = function () {
    console.log('我是Function中的b')
}
function F() {
    this.a = function () {
        console.log('我是F中的a')
    }
}
F.prototype.a = function () {
    console.log('我是F的prototype中的a')
}
var f = new F();

f.a(); // '我是F中的a'
f.b(); // '我是Object中的b'
F.a(); // '我是Function中的a'
F.b(); // '我是Function中的b'
Object.a(); // '我是Function中的a'
Object.b(); // '我是Function中的b'
Function.a(); // '我是Function中的a'
Function.b(); // '我是Function中的b'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

参考文档

  1. 深度解析原型中的各个难点
  2. JavaScript深入之从原型到原型链
  3. 深入理解javascript原型和闭包系列

评 论: