Hello, 各位勇敢的小伙伴, 大家好, 我是你们的嘴强王者小五, 身体健康, 脑子没病.
本人有丰富的脱发技巧, 能让你一跃成为资深大咖.
一看就会一写就废是本人的主旨, 菜到抠脚是本人的特点, 卑微中透着一丝丝刚强, 傻人有傻福是对我最大的安慰.
欢迎来到
小五
的随笔系列
之大话原型链
.
前言
提起它就头痛,永远在似懂非懂间徘徊,这就是笔者对原型链的感受;而突然有一天你无意间发觉它是 “ 怎么来的 ” 的时候,你会发现,呦,原型链不过如此。
本文为链表在 JavaScript 中的实际应用,如若想回忆链表,其传送门如下:小五的算法系列 - 链表,各位看官根据自身需求自取。
此文以笔者愚见所著,如若有误,欢迎留言指正。
何为原型链
原型链,其本质不过一个链表罢了,不信你看 👇
🐳 __proto__
即为链表的 next 指针,链表尾部指针指向 null
原型链,不断寻找其构造函数的原型 ($prototype$) 的过程,不信你看 👇
class Person {};
let person = new Person();
🐳 __proto__ -> constructor.prototype
👺 原型链本质就是链表,__proto__即为next指针,指向其构造函数的原型
原型链的查找即为链表的查找,如 instanceof;
原型链的添加即为链表的添加,如 new;
链表的操作就是在拨弄指针,原型链也一样,知道如何拨弄指针,原型链也就通了;
🐢 坊间有一传闻,叫万物皆对象;就像下图,无论千变万化,链表的尾端始终为 Object.prototype
我们可以将 Object.prototype 看作万物的起源,代号 001
obj 的__proto__
指向其构造函数 Object 的原型 001
🚶 我们沿着原型链继续向前走,走到红框的位置,以 str -> String.prototype -> Object.prototype -> null
为例,图示如下
这里的str无论是基本类型还是实例类型均为上图流程,什么,你说 instanceof ?,先卖个关子,后文见分晓。
let str1 = 'apple';
str1 instanceof Object // false
let str2 = new String('apple');
str2 instanceof Object // true
说完实例,我们来说说其构造函数,也就是类;不同的类各司其职,一同完善我们的 JavaScript 世界;
这些被直接创造的类,其 __proto__
均指向 Function.prototype,我们也为其取个代号为 002
可能有人不理解 Object.__proto__ === Function.prototype
和 Function.__proto__ === Function.prototype
,我们这么去想,__proto__
指向的是其构造函数的原型,而所有的类都是通过 Function 构造的,包括 Object 和 Function
“new” 一个对象
👀 “new” 会发生什么呢? 不妨一起来看下
先创建一个 Person 类
function Person (name) {
this.name = name;
this.action = function () {
console.log('打 🏓️ 乒乓球');
}
}
let person = new Person('黄刀小五');
new 的过程就类似于在链表头部插入元素, __proto__
指向其构造函数的原型。
function new (P, ...args) {
let obj = {};
obj.__proto__ = P.prototype;
P.call(obj, ...args); // 用于改变this指向, 使其指向P
return obj;
}
let person = new(Person, '黄刀小五'); // 输出同上
Object.create()
官方定义:Object.create()
方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
这么直白的描述,照着实现就好了
Object.Create = function (obj) {
let newObj = {};
newObj.__proto__ = obj;
return newObj;
}
作为一名视力良好的公民,可以发现,Object.create()
创造出来的对象,其 __proto__
指向其入参 obj
,而非 Object.prototype
,其链式结构如下:
createObj -> obj -> Object.prototype -> null
谈谈继承
千万不要被下面这一堆奇怪的名字吓到,它们只是大招的分解动作而已,跟随它们一步一步探索继承的演变过程吧。
我们来建一个 Teacher 类,并构建一个 Student 类来继承它。
function Teacher (name) {
this.name = name;
this.info = {};
this.action = function () {
console.log('做练习题');
}
};
🐳 原型链继承
原型链继承的思想就是改写 Student 的原型,使其等于 Teacher 的实例,以达到继承的效果,代码如下:
function Student () {};
Student.prototype = new Teacher();
此时遇到个很尴尬的问题,Teacher 类是接收参数的,我该如何传递过去呢 ❓ 传递不了参数,此其罪1也。
Teacher 中有一 info 对象代表基本信息,我创建两个学生实例,如下,分别更改其 info 中的 age 会发生什么?
student1.info.age = 12;
student2.info.age = 13;
console.log(student1.info.age, student2.info.age); // 13 13
会指向的同一地址,值被改变,此其罪2也。
🐳 构造函数继承
核心思想就是在子类的构造函数中调用父类的构造函数,以达到继承父类的效果
function Student (...args) {
Teacher.call(this, ...args);
}
我们在 Teacher 的原型上追加一个方法 eat
Teacher.prototype.eat = function () {
console.log(`${this.name} eat apple`);
}
在 student 实例中调用下
const student = new Student('小五');
student.eat(); // student.eat is not a function
无法访问父类原型中的属性与方法,此其之罪也
🐳 组合继承
说白了就是上面两种不堪用呀,我们缝合一下,就成了组合继承。
function Student (...args) {
Teacher.call(this, ...args);
}
Student.prototype = new Teacher();
Student.prototype.constructor = Student; // 重新赋值constructor, 否则其constructor为Teacher
看似完美,可以达到继承的效果;实则调用了两次父类构造函数,造成不必要的损耗
🐳 原型继承
一个浅拷贝,其本质就是一个Object.create()
,我们换一种等同的写法实现下
function create (o) {
let P = function () {};
P.prototype = o;
return new P();
}
缺点同原型链继承,不再赘述
🐳 寄生式继承
所谓寄生式就是复制而已,复制后进行扩展,我们在原型继承的基础上为其添加 study 方法。
function createStudent (o) {
let clone = create(o);
clone.study = function () {};
return clone;
}
🐳 寄生组合式继承
接下来有请我们的终极boss闪亮登场,还记不记得上文组合继承的问题了,其会调用两次父类构造函数;那我们这次不直接指向 Teacher 的实例,而是复制一份 Teacher 的原型,完美解决上述问题。
function Student (...args) {
Teacher.call(this, ...args);
}
Student.prototype = Object.create(Teacher.prototype);
Student.prototype.constructor = Student;
手写 instanceof
在讲原型链的时候说到,str instanceof Object -> false
,因为在 instanceof 的实现上,非 object 类型均返回 false;若 object 类型,则为链表的查找;是不是 so easy,我们来一起实现下。
const instanceof = (L, P) => {
if (typeof L !== 'object') return false;
let current = L.__proto__;
while (current) {
if (current === P.prototype) return true;
current = current.__proto__;
}
return false;
}
instanceof('str', String) // false
instanceof({}, Object) // true
小试牛刀
题目来源:2019 面试准备 - JS 原型与原型链、2020 年我碰到的原型链的面试题
👺 No.1
Function.prototype.a = () => {
console.log(1);
}
Object.prototype.b = () => {
console.log(2);
}
function A() {};
const a = new A();
a.a();
a.b();
A.a();
A.b();
解析:
答案:
a.a(); // Error
a.b(); // 2
A.a(); // 1
A.b(); // 2
👺 No.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); // 1
console.log(b.m); // undefined
console.log(c.n); // 2
console.log(c.m); // 3
👺 No.3
function A() {};
A.prototype.n = 0;
A.prototype.add = function () {
this.n += 1;
}
a = new A();
b = new A();
a.add();
console.log(b.add());
解析:
执行 add() 时,沿原型链找到 n = 0,this.n += 1,此时,a 获取属性 n,其值为1,b 同理
答案:
console.log(b.add()); // 1
参考🔗链接
注意:本文归作者所有,未经作者允许,不得转载