2021-08-08 系统装修,清理 proto
在提到原型链时候,很多人会头疼,这里尝试换个思路进行解释。
注意:修改原型的继承关系是非常敏感的操作,Object.setPrototypeOf
会影响所有访问[[prototype]]的代码,可能会产生严重的性能问题。
因此最佳实践是,另起炉灶 Object.create(xx.prototype)
,创建新对象并执行原型。
约定前提:
- prototype统一称为原型
- proto 这个行为称为构造函数原型
- 在所有书写
__proto__
的地方统一写成Object.getPrototypeOf
和规范一致 - 使用 Object.create 替换 Object.setPrototypeOf
- 默认你了解基本概念,只是混沌不清楚。
导引
请看两个demo案例1
直接看demo,可以在浏览器里直接看:
const obj = { a: 1 };
console.log(obj.toString()); // [object Object]
Object.getProrotypeOf(obj)===Object.prototype
// 上述的也可以写成这样
Fn.prototype.isPrototypeOf(fn) // true
我们直接创建了一个对象,没有声明toString方法,但是可以直接调用,为何?说明有调用了其他地方的。从哪里来,慢慢解释。
打印 obj
,Chrome这样提示,有一个 Object[[Prototype]]
的标志(Chrome 低版本会展示 __proto__
),视觉差异都是指 getPrototypeOf :
请注意 fn.__proto__
是不规范的写法,应该写成 Object.getPrototypeOf(fn)
这样的写法。短期来看,用哪个都可以,虽然不规范但是能用。
Object.getPrototypeOf() 方法返回指定对象的原型(内部[[Prototype]]属性的值)。—- MDN
案例2
const Fn = function(){}
const fn = new Fn()
// 老式写法
console.log(fn.__proto__ === Fn.prototype) // 看第一张图
console.log(Object.getPrototypeOf(fn)===Fn.prototype)
//console.log(fn.__proto__.__proto__ === Object.prototype)
//console.log(fn.__proto__.__proto__.__proto__ === null)
第一个关系图出现了
从这图延伸来看,new实例化的过程是怎么样的?补全,但少不了这两个步骤:
const instance = Object.create(null)
Object.setPrototypeOf(instance, Fn.prototype)
有一个章节专门描述new。
但这一张图还不能解释为啥 fn.toString() 有结果,这需要去看 Fn.prototype
上有啥
说明,Fn.prototype 最起码是从 Object 上拿的。
Object.getPrototypeOf(Fn.prototype) === Object.prototype // true
Object.getPrototypeOf(Object.prototype) === null
在第一张图的基础上补充。
再看 Fn这个函数,函数本身也可以视为 new Function
出来的,因此也可以 Object.getPrototypeOf(Fn)
案例3
var num = 3;
console.log(Object.getPrototypeOf(num)===Number.prototype)
console.log(Object.getPrototypeOf(Number)===Function.prototype)
同理。
最终版长这样:
原型
请注意,js上的属性可以被覆盖,比如 obj.hasOwnProperty=7
,这就覆盖了,同样 Object.prototype.hasOwnProperty
也可以被覆盖。
为了明确继承的属性,一般会 Object.create(null)
来操作。
原型链
如果访问一个对象上不存在的属性,js会尝试访问原型上的属性、原型的原型以此类推,最终得出undefined对应的原型。这个过程好像一个链条,这是原型链。举例
function Foo(){}
Foo.prototype.nn=function(){}
var f = new Foo()
f.nnn()
f.toString()
实例中,f本身没有nnn属性,它会到构造函数的prototype
中寻找,一层层向上查找,直到找到。再比如都没有定义的.toString()
一直找到 Object 上找到,这就叫原型链。找不到就是 undefined
如何判断 f.nn 是不是 f自身的属性?hasOwnProperty
for(const item in f){
if(f.hasOwnProperty(item){}
}
for..in 高级浏览器已经屏蔽了原型中的属性,以防万一还是得使用hasOwnProperty
。
看题
new 的背后
请看代码片段:
function MyClass(name = "") {
this.name = name;
}
MyClass.prototype.say = function () {
console.log(this.name + " says hi.");
};
const instance = new MyClass('dog');
console.log(instance.say());
通过 new
,执行构造函数得到对象实例。继承了构造函数的原型挂载的方法和属性。
new背后的 技术原理:
- 创建一个空对象,作为对象实例
- 让空对象的原型
__proto__
,指向构造函数的prototype
属性,也就是新对象 Object.setPrototypeOf(obj, Obj.prototype) - 也可以合并操作,直接拿
- 注意,new的时候this指向构造函数,因此让构造函数内部的this指向这个空对象,并执行构造函数的函数逻辑
- 执行逻辑过程中,返回显式返回值
代码 https://gitee.com/xiaoxfa/tech-sharing/blob/master/share-repo/08-prototype/newFun.js
真题
var a = 20;
var test = {
a: 40,
init: () => {
console.log(this.a);
function go() {
console.log(this.a);
}
go.prototype.a = 50;
return go;
}
};
var p = test.init();
p();
new p()
// https://mp.weixin.qq.com/s/0Q9fhHwksHKSrYmB2fb_Wg 年末的大厂前端面试总结(20届双非二本)-终入字节
面向对象,实现继承
说起js的面向对象,继承,始终是个永恒的话题。因为js是基于原型的语言,一直到ES6才正式提出class和extend的概念。
理论上如何基于原型实现继承,可以丢进历史垃圾堆了。但这里还是做个总结,迟早会删除这部分内容。最有价值的意义还是在于深刻理解原型和原型链。
接下来的内容,也就是论证如何实现下面几行内容:
class Animal {
constructor(name) {
this.name = name;
}
walk() {
console.log(this.name + " 正在行走");
}
}
class Dog extends Animal {
constructor(name) {
super(name);
}
walk() {
console.log(this.name + " 正在撒欢");
}
}
const animal = new Animal("aa");
console.log(animal.walk());
// aa 正在行走
const dog = new Dog("dog1");
console.log(dog.walk());
// dog1 正在撒欢
function Animal(name) {
this.name = name;
}
Animal.prototype.walk = function () {
console.log(this.name + " 正在行走");
};
// go on
function Dog(name) {
// 先掉一下,相当于super
Animal.call(this, name);
}
// 原型迁移,
Object.setPrototypeOf(Dog, Animal.prototype);
// constructor 修正
Object.getPrototypeOf(Dog).constructor = Dog;
Dog.prototype.walk = function () {
console.log(this.name + " 正在撒欢");
};
// 试验区
const animal = new Animal("aa");
console.log(animal.walk());
const dog = new Dog("dog1");
console.log(dog.walk());
-1 参考资料
- 《JavaScript高级程序设计》第4版 第八章
- 《前端开发核心知识进阶》第七章
- MDN 继承和原型链
- https://juejin.cn/post/6844904200917221389 由浅入深,66条JavaScript面试知识点