写作不易,Star是最大鼓励,感觉写的不错的可以给个Star⭐,请多多指教。Github地址。
原型
- 函数的prototype属性
- 每个函数都有一个prototype属性,它默认指向一个Object空对象(即原型对象)
- 原型对象中有一个属性constructor,指向函数对象
- 操作原型对象
- 可以通过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
2
3
4
5
6
7
8
9
10
显式原型和隐式原型
- 每个函数都有一个prototype属性,即显式原型对象
- 每个实例对象都有一个__proto__属性,称为隐式原型对象
- 对象的隐式原型的值等于其对应构造函数的显式原型的值
总结:
- 函数的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();
2
3
4
5
6
7
8
9
10
11
12
13
14
需要注意:属性在查找的时候,实际上通过的隐式原型来向上查找的,而不是显式原型。
原型链
- 原型链(图解)
- 访问一个对象的属性时
- 先在自身属性中查找,找到则返回
- 如果没有找到,沿着__proto__这条链继续向上查找,找到返回
- 如果最终没有找到,返回undefined
- 别名:隐式原型链
- 作用:查找对象的属性和方法
- 构造函数/原型/实体对象的关系(图解)
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]
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
对象属性读取和设置
原型链属性的问题:
- 读取对象的属性值时会自动到原型链中查找。
- 设置对象的属性值时,不会查找原型链,如果当前对象中没有该属性,直接添加该属性并设置其值。
- 方法一般定义在原型中,属性一般通过构造函数定义在对象自身上
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
instanceof
- instanceof是如何判断的?
- 表达式:A instanceof B(A是实例对象,B是构造函数)
- 如果B函数的显式原型对象在A对象的原型链上,返回true,否则返回false
- 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
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
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()
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
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;
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;
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
2
解释:
Object.a(); // a()
Object.b(); // b()
Function.a(); // a()
Function.b(); // b()
2
3
4
我们来展开Object的原型链:
// Object是函数,继承自Function
Object.__proto__ === Function.prototype;
Object.__proto__.__proto__ === Object.prototype;
Object.__proto__.__proto__.__proto__ === null;
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;
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
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'
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