2021-08-08 系统装修,清理 proto

在提到原型链时候,很多人会头疼,这里尝试换个思路进行解释。

注意:修改原型的继承关系是非常敏感的操作,Object.setPrototypeOf 会影响所有访问[[prototype]]的代码,可能会产生严重的性能问题。
因此最佳实践是,另起炉灶 Object.create(xx.prototype),创建新对象并执行原型。

约定前提:

  • prototype统一称为原型
  • proto 这个行为称为构造函数原型
  • 在所有书写__proto__的地方统一写成 Object.getPrototypeOf和规范一致
  • 使用 Object.create 替换 Object.setPrototypeOf
  • 默认你了解基本概念,只是混沌不清楚。

    导引

    请看两个demo

    案例1

直接看demo,可以在浏览器里直接看:

  1. const obj = { a: 1 };
  2. console.log(obj.toString()); // [object Object]
  3. Object.getProrotypeOf(obj)===Object.prototype
  4. // 上述的也可以写成这样
  5. Fn.prototype.isPrototypeOf(fn) // true

我们直接创建了一个对象,没有声明toString方法,但是可以直接调用,为何?说明有调用了其他地方的。从哪里来,慢慢解释。

打印 obj,Chrome这样提示,有一个 Object[[Prototype]]的标志(Chrome 低版本会展示 __proto__),视觉差异都是指 getPrototypeOf :

请注意 fn.__proto__是不规范的写法,应该写成 Object.getPrototypeOf(fn)这样的写法。短期来看,用哪个都可以,虽然不规范但是能用。

Object.getPrototypeOf() 方法返回指定对象的原型(内部[[Prototype]]属性的值)。—- MDN

image.png

案例2

  1. const Fn = function(){}
  2. const fn = new Fn()
  3. // 老式写法
  4. console.log(fn.__proto__ === Fn.prototype) // 看第一张图
  5. console.log(Object.getPrototypeOf(fn)===Fn.prototype)
  6. //console.log(fn.__proto__.__proto__ === Object.prototype)
  7. //console.log(fn.__proto__.__proto__.__proto__ === null)

第一个关系图出现了
image.png

从这图延伸来看,new实例化的过程是怎么样的?补全,但少不了这两个步骤:

  • const instance = Object.create(null)
  • Object.setPrototypeOf(instance, Fn.prototype)

有一个章节专门描述new。

但这一张图还不能解释为啥 fn.toString() 有结果,这需要去看 Fn.prototype 上有啥

image.png
说明,Fn.prototype 最起码是从 Object 上拿的。

  1. Object.getPrototypeOf(Fn.prototype) === Object.prototype // true
  2. Object.getPrototypeOf(Object.prototype) === null

image.png

在第一张图的基础上补充。

再看 Fn这个函数,函数本身也可以视为 new Function出来的,因此也可以 Object.getPrototypeOf(Fn)

案例3

  1. var num = 3;
  2. console.log(Object.getPrototypeOf(num)===Number.prototype)
  3. console.log(Object.getPrototypeOf(Number)===Function.prototype)

同理。

最终版长这样:

image.png

原型

请注意,js上的属性可以被覆盖,比如 obj.hasOwnProperty=7,这就覆盖了,同样 Object.prototype.hasOwnProperty也可以被覆盖。

为了明确继承的属性,一般会 Object.create(null)来操作。

原型链

如果访问一个对象上不存在的属性,js会尝试访问原型上的属性、原型的原型以此类推,最终得出undefined对应的原型。这个过程好像一个链条,这是原型链。举例

  1. function Foo(){}
  2. Foo.prototype.nn=function(){}
  3. var f = new Foo()
  4. f.nnn()
  5. f.toString()

实例中,f本身没有nnn属性,它会到构造函数的prototype中寻找,一层层向上查找,直到找到。再比如都没有定义的.toString() 一直找到 Object 上找到,这就叫原型链。找不到就是 undefined

如何判断 f.nn 是不是 f自身的属性?hasOwnProperty

  1. for(const item in f){
  2. if(f.hasOwnProperty(item){}
  3. }

for..in 高级浏览器已经屏蔽了原型中的属性,以防万一还是得使用hasOwnProperty

重写构造函数,不会影响之前创建的实例。

看题

new 的背后

请看代码片段:

  1. function MyClass(name = "") {
  2. this.name = name;
  3. }
  4. MyClass.prototype.say = function () {
  5. console.log(this.name + " says hi.");
  6. };
  7. const instance = new MyClass('dog');
  8. 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

参考高程4 8.3.2

真题

  1. var a = 20;
  2. var test = {
  3. a: 40,
  4. init: () => {
  5. console.log(this.a);
  6. function go() {
  7. console.log(this.a);
  8. }
  9. go.prototype.a = 50;
  10. return go;
  11. }
  12. };
  13. var p = test.init();
  14. p();
  15. new p()
  16. // https://mp.weixin.qq.com/s/0Q9fhHwksHKSrYmB2fb_Wg 年末的大厂前端面试总结(20届双非二本)-终入字节

面向对象,实现继承

说起js的面向对象,继承,始终是个永恒的话题。因为js是基于原型的语言,一直到ES6才正式提出class和extend的概念。

理论上如何基于原型实现继承,可以丢进历史垃圾堆了。但这里还是做个总结,迟早会删除这部分内容。最有价值的意义还是在于深刻理解原型和原型链。

接下来的内容,也就是论证如何实现下面几行内容:

  1. class Animal {
  2. constructor(name) {
  3. this.name = name;
  4. }
  5. walk() {
  6. console.log(this.name + " 正在行走");
  7. }
  8. }
  9. class Dog extends Animal {
  10. constructor(name) {
  11. super(name);
  12. }
  13. walk() {
  14. console.log(this.name + " 正在撒欢");
  15. }
  16. }
  17. const animal = new Animal("aa");
  18. console.log(animal.walk());
  19. // aa 正在行走
  20. const dog = new Dog("dog1");
  21. console.log(dog.walk());
  22. // dog1 正在撒欢
  1. function Animal(name) {
  2. this.name = name;
  3. }
  4. Animal.prototype.walk = function () {
  5. console.log(this.name + " 正在行走");
  6. };
  7. // go on
  8. function Dog(name) {
  9. // 先掉一下,相当于super
  10. Animal.call(this, name);
  11. }
  12. // 原型迁移,
  13. Object.setPrototypeOf(Dog, Animal.prototype);
  14. // constructor 修正
  15. Object.getPrototypeOf(Dog).constructor = Dog;
  16. Dog.prototype.walk = function () {
  17. console.log(this.name + " 正在撒欢");
  18. };
  19. // 试验区
  20. const animal = new Animal("aa");
  21. console.log(animal.walk());
  22. const dog = new Dog("dog1");
  23. console.log(dog.walk());

constructor 不可枚举

-1 参考资料