前言
想了想,有些东西挤在一个博客里可能并不好,还不如拆出来,也比较好回顾
参考文章: https://juejin.cn/post/7007416743215759373
基础知识
prototype
和__proto__
两者是什么
prototype
: 显式原型- 是构造函数的一个属性, 用于定义实例共享的方法和属性
- 它指向原型对象,该对象上的属性和方法会被实例继承
__proto__
: 隐式原型- 是每个对象都有的内部属性
- 它指向创建该对象的构造函数的
prototype
,用于建立原型链
两者的关系
- 构造函数的
prototype
和其实例的__proto__
指向同一个地方,这个地方叫做原型对象 - 原型对象是实现继承的核心
function Person(name, age) { this.name = name; this.age = age; }
const person1 = new Person('小明', 20); const person2 = new Person('小红', 30);
console.log(Person.prototype === person1.__proto__); console.log(Person.prototype === person2.__proto__);
|
什么是构造函数
- 可构造函数是可以用
new
操作符调用的函数,用于创建对象 - 箭头函数不能作为构造函数
function Person(name, age) { this.name = name this.age = age }
const person1 = new Person('小明', 20) const person2 = new Person('小红', 30)
|
验证prototype
和__proto__
的关系
- 构造函数的
prototype
定义实例可以继承的方法 - 实例的
__proto__
指向构造函数的prototype
function Person(name, age) { this.name = name; this.age = age; }
Person.prototype.sayName = function() { console.log(this.name); };
const person1 = new Person('小明', 20); const person2 = new Person('小红', 30);
console.log(Person.prototype); console.log(person1.__proto__); console.log(Person.prototype === person1.__proto__); console.log(Person.prototype === person2.__proto__);
person1.sayName(); person2.sayName();
|
构造函数与对象的关系
构造函数
- 构造函数本质上是函数,用来创建对象。定义构造函数有以下几种常见方式
function fn1(name, age) { console.log(`我是${name}, 我今年${age}岁`); } fn1('abc', 10);
const fn2 = function(name, age) { console.log(`我是${name}, 我今年${age}岁`); }; fn2('abc', 10);
const arrowFn = (name, age) => { console.log(`我是${name}, 我今年${age}岁`); }; arrowFn('abc', 10);
|
构造函数的本质
- 所有函数的本质都是由
Function
构造函数创建的 - 上述三种写法等同于以下代码
const fn1 = new Function('name', 'age', 'console.log(`我是${name}, 我今年${age}岁`)'); const fn2 = new Function('name', 'age', 'console.log(`我是${name}, 我今年${age}岁`)'); const arrowFn = new Function('name', 'age', 'console.log(`我是${name}, 我今年${age}岁`)');
|
构造函数与原型的关系
- 每个构造函数的
prototype
和其实例的__proto__
都指向同一个原型对象 - 同时,所有函数本身都是
Function
构造函数的实例
function fn1(name, age) { console.log(`我是${name}, 我今年${age}岁`); } const fn2 = function(name, age) { console.log(`我是${name}, 我今年${age}岁`); }; const arrowFn = (name, age) => { console.log(`我是${name}, 我今年${age}岁`); };
console.log(Function.prototype === fn1.__proto__); console.log(Function.prototype === fn2.__proto__); console.log(Function.prototype === arrowFn.__proto__);
|
创建对象的方式
在开发中,创建对象有以下几种方式
- 构造函数创建对象: 使用
new
调用构造函数创建对象
function Person(name, age) { this.name = name; this.age = age; } const person1 = new Person('abc', 10); console.log(person1);
|
const person2 = { name: 'abc', age: 10 }; console.log(person2);
|
const person3 = new Object(); person3.name = 'abc'; person3.age = 10; console.log(person3);
|
Object.create
创建对象: 创建一个具有指定原型的空对象
const person4 = Object.create({}); person4.name = 'abc'; person4.age = 10; console.log(person4);
|
字面量与new Object
的关系
- 字面量和
new Object
创建的对象本质上都是Object
构造函数的实例
const person2 = { name: 'abc', age: 10 }; const person3 = new Object(); person3.name = 'abc'; person3.age = 10;
console.log(Object.prototype === person2.__proto__); console.log(Object.prototype === person3.__proto__);
|
Function
和Object
的关系
function fn() {} console.log(Function.prototype === fn.__proto__);
|
const obj = {}; console.log(Object.prototype === obj.__proto__);
|
- 特殊关系
Object
是一个函数,因此它是Function
的实例Function
本身也是一个函数,因此它是自己的实例
console.log(Function.prototype === Object.__proto__); console.log(Function.prototype === Function.__proto__);
|
深入解析
原型链的起点与终点
原型的本质
Person.prototype
是构造函数Person
的原型对象Function.prototype
是构造函数Function
的原型对象- 所有的原型对象都是对象,因此本质上它们是通过
new Object()
创建的
原型链上的关系
Person.prototype
和Function.prototype
都是Object
构造函数的实例- 因此,它们的
__proto__
都指向Object.prototype
function Person() {}
console.log(Person.prototype.__proto__ === Object.prototype); console.log(Function.prototype.__proto__ === Object.prototype);
|
什么是原型链
- 原型链是通过
__proto__
属性连接起来的对象链 - 当访问一个对象的属性时,如果该对象没有这个属性,会通过
__proto__
向上查找,直到null
为止
原型链的终点
- 原型链的终点是
null
,这是JavaScript
中的对象链的终止点 - 任何对象的
__proto__
最终都会指向Object.prototype
,而Object.prototype.__proto__ === null
原型链的实际应用(继承与查找)
原型继承的机制
- 原型继承允许实例对象使用构造函数原型对象上的方法或属性
- 实例对象会通过
__proto__
链接到构造函数的prototype
,实现属性和方法的继承
function Person(name) { this.name = name; }
Person.prototype.sayName = function() { console.log(this.name); };
const person = new Person('abc'); person.sayName();
|
person
实例对象的__proto__
指向Person.prototype
- 调用
person.sayName()
时,JavaScript
引擎会通过__proto__
查找到原型对象上的sayName
方法
属性查找规则
- 优先查找实例对象本身的属性
- 如果实例对象中没有该属性,会沿着原型链向上查找,直到
null
- 如果在原型链中也未找到,返回
undefined
function Animal(type) { this.type = type; } Animal.prototype.sayType = function() { console.log(this.type); };
const dog = new Animal('dog'); console.log(dog.type); console.log(dog.hasOwnProperty('type')); dog.sayType(); console.log(dog.__proto__.hasOwnProperty('sayType'));
|
高级特性
instanceof
的机制
- 判断
B.prototype
是否在对象A
的原型链上 instanceof
通过检查A.__proto__
是否等于B.prototype
或者沿着原型链向上查找直到null
,来判断结果
function Person(name) { this.name = name; }
const person = new Person('abc');
console.log(Person instanceof Function); console.log(Person instanceof Object); console.log(person instanceof Person); console.log(person instanceof Object);
|
自定义实现instanceof
function myInstanceof(A, B) { let proto = A.__proto__; while (proto) { if (proto === B.prototype) return true; proto = proto.__proto__; } return false; }
console.log(myInstanceof(person, Person)); console.log(myInstanceof(person, Object)); console.log(myInstanceof(person, Function));
|
constructor
的作用
constructor
是每个对象的一个属性,指向创建该对象的构造函数
prototype
与constructor
的互指关系- 构造函数的
prototype
上有一个constructor
属性,指向该构造函数本身 Fn.prototype.constructor === Fn
function Fn() {} console.log(Fn.prototype.constructor === Fn);
|
- 实例的
constructor
属性- 实例对象通过
__proto__
继承prototype
,因此可以访问constructor
instance.constructor === Fn
const instance = new Fn(); console.log(instance.constructor === Fn);
|
原型链练习
原型链练习#1
var F = function() {};
Object.prototype.a = function() { console.log('a'); };
Function.prototype.b = function() { console.log('b'); };
var f = new F();
f.a(); f.b();
F.a(); F.b();
|
f.a(); f.b();
F.a(); F.b();
|
解析
f.a()
f
是F
的实例,而F
是通过构造函数创建的f
的原型链如下
f.__proto__ === F.prototype F.prototype.__proto__ === Object.prototype Object.prototype.a() 定义了方法
|
- 因此,
f
可以通过原型链访问Object.prototype.a
,输出'a'
f.b()
f
的原型链中没有定义b
方法b
是定义在Function.prototype
上的,而f
并不是函数,因此无法访问Function.prototype.b
- 结果: 报错
f.b is not a function
F.a()
F
是函数,函数是Function
构造函数的实例F
的原型链如下
F.__proto__ === Function.prototype Function.prototype.__proto__ === Object.prototype
|
- 因此,
F
可以通过原型链访问到Object.prototype.a
,输出'a'
F.b()
F
是函数,b
方法定义在Function.prototype
上- 因为
F.__proto__ === Function.prototype
,所以F
可以直接访问到b
方法 - 结果: 输出
'b'
原型链示意图
f.__proto__ → F.prototype → Object.prototype → null
|
F.__proto__ → Function.prototype → Object.prototype → null
|
原型链练习#2
var A = function() {}; A.prototype.n = 1; var b = new A(); A.prototype = { n: 2, m: 3 }; var c = new A();
console.log(b.n); console.log(b.m);
console.log(c.n); console.log(c.m);
|
console.log(b.n); console.log(b.m);
console.log(c.n); console.log(c.m);
|
解析
A.prototype.n = 1
A.prototype
初始化时,包含属性n
,值为1
var b = new A()
, 创建实例b
b.__proto__
指向当前的A.prototype
,即{ n: 1 }
- 通过
b.n
可以访问A.prototype.n
,值为1
b
的原型链为
b.__proto__ === A.prototype A.prototype === { n: 1 }
|
重新赋值A.prototype
A.prototype
被重新赋值为一个新的对象{ n: 2, m: 3 }
- 注意: 这不会影响已经创建的实例
b
,因为b.__proto__
指向的是旧的A.prototype
A.prototype === { n: 2, m: 3 }
|
var c = new A()
, 创建实例c
c.__proto__
指向当前的A.prototype
,即{ n: 2, m: 3 }
- 通过
c.n
和c.m
可以访问新A.prototype
上的n
和m
原型链示意图
A.prototype → { n: 2, m: 3 }
|
c.__proto__ → { n: 2, m: 3 }
|
原型链练习#3
var foo = {}, F = function() {}; Object.prototype.a = 'value a'; Function.prototype.b = 'value b';
console.log(foo.a); console.log(foo.b);
console.log(F.a); console.log(F.b);
|
console.log(foo.a); console.log(foo.b);
console.log(F.a); console.log(F.b);
|
解析
foo.a
foo.__proto__ === Object.prototype Object.prototype.a = 'value a'
|
- 因此,
foo.a
可以通过原型链访问到Object.prototype.a
,结果为'value a'
foo.b
foo
的原型链中没有定义b
方法或属性foo.__proto__ === Object.prototype
中没有b
属性- 因此,
foo.b
返回undefined
F.a
F.__proto__ === Function.prototype Function.prototype.__proto__ === Object.prototype Object.prototype.a = 'value a'
|
F
可以通过原型链访问到Object.prototype.a
,结果为'value a'
F.b
F
是一个函数,而b
是定义在Function.prototype
上
F.__proto__ === Function.prototype Function.prototype.b = 'value b'
|
- 因此,
F.b
可以直接访问到Function.prototype.b
,结果为'value b'
原型链示意图
foo.__proto__ → Object.prototype → null
|
F.__proto__ → Function.prototype → Object.prototype → null
|
原型链练习#4
function A() {} function B(a) { this.a = a; } function C(a) { if (a) { this.a = a; } } A.prototype.a = 1; B.prototype.a = 1; C.prototype.a = 1;
console.log(new A().a); console.log(new B().a); console.log(new C(2).a);
|
console.log(new A().a); console.log(new B().a); console.log(new C(2).a);
|
解析
new A().a
- 使用
new A()
创建实例 A
的构造函数中没有定义a
属性- 查找
a
时,沿原型链访问A.prototype.a
,值为1
- 输出:
1
new B().a
- 使用
new B()
创建实例 B
的构造函数有this.a = a;
,此时a
的值为undefined
(没有传入参数)- 因此,
this.a
被赋值为undefined
- 因为实例自身的
a
属性已经定义了,优先于原型链上的属性 - 输出:
undefined
new C(2).a
- 使用
new C(2)
创建实例 C
的构造函数中有if (a) { this.a = a; }
,传入参数a = 2
,条件为true
- 因此,
this.a
被赋值为2
- 实例自身的
a
属性覆盖了原型链上的C.prototype.a
- 输出:
2
原型链示意图
instance → A.prototype → Object.prototype → null
|
instance → B.prototype → Object.prototype → null
|
instance → C.prototype → Object.prototype → null
|
原型链练习#5
console.log(123['toString'].length + 123)
|
答案: 123
是数字,数字本质是new Number()
,数字本身没有toString
方法,则沿着__proto__
去function Number()
的prototype
上找,找到toString
方法,toString
方法的length
是1
,1 + 123 = 124
,至于为什么length
是1
,参考下面文章
参考文章: https://juejin.cn/post/7003369591967596552
console.log(123['toString'].length + 123)
|
附录
参考文章: https://juejin.cn/post/7007416743215759373