彻底搞懂原型(3)——原型链的应用与__proto__

前言

在上一节中我们了解了原型链的相关知识。那么这一节来谈谈原型链的一些具体应用和对象的非标准属性__proto__。

原型链的应用

在构造函数的rototype属性中定义方法

将方法定义在构造函数的prototype的属性上,创建的对象通过原型链就能使用该方法,这大大节省了空间。详细的解释与例子在上一节也有提到,这里就不再赘述了。

彻底搞懂原型(2)------原型链是什么 - 掘金 (juejin.cn)

in操作符与hasOwnPrototype

它们都是用来检测对象上是否有某个属性。不同的是in操作符还会在原型链上查找。而hasOwnPrototype只会在当前的对象上查找属性。

js 复制代码
     let a = {};
     
      a.__proto__.name = "aaa";
     

      console.log("name" in a);  // true
      console.log(a.hasOwnProperty("name")); // false

所以vscode,for-in快捷语句中会配上hasOwnPrototype明确表示只在当前对象上查找属性。

js 复制代码
 for (const key in object) {
        if (Object.hasOwnProperty.call(object, key)) {
          const element = object[key];
          
        }
      }

如果细心一点的,可能会发现。Object.prototype上才有hasOwnProperty方法呀,为什么vscode会直接来个Object.hasOwnProperty,看上去hasOwnProperty就好像是一个Object的静态方法了。

这肯定是从Object原型链上找到的。盘一下这个原型链

js 复制代码
Object.__proto__===Function.prototype
Object.__proto__.__proto__ === Function.prototype.__proto__ === Object.prototype

通过原型链找到Object.prototype,所以找到了hasOwnProperty的方法。

注:但是注意Object.prototype中有一个方法在调用时不能省去prototype。

toString(),因为在Function.prototype中正好有一个同名方法,而它又在原型链下层,遮蔽了上层的Object.prototype的toString()

call方法

之前听向军老师说过,call方法就是当自己以及自己的原型链上没有某种方法,但是别的类型的原型链上有,那么就借用一下。这个说法挺有趣,通俗易懂。

它的实现原理就是把某个方法,临时挂在某个对象上。然后用obj点的方式调用一下,当用obj.的方式调用方法,就正好改变了该方法的this指向,this就指向了obj。调用后再从对象上删除该方法。确实就像向别人借东西,用完后就还回去了。

所以call方法的第一个参数,就是该参数要去借别人的方法,所以方法挂在这个对象上,也是绑定this的对象。因为就是该对象用点的方式调用了方法。

之前使用过:

js 复制代码
Object.prototype.toString.call({})
Object.prototype.hasOwnProperty.call(obj,key)

其实它们都是一个道理,将Object.prototype.toString,Object.prototype.hasOwnProperty方法分别挂在{},obj上,并调用。

举例:

js 复制代码
<body>
    <ul>
      <li>1</li>
      <li>2</li>
      <li>3</li>
    </ul>

    <script>
      const lis = document.getElementsByTagName("li");
      console.log(lis);
    </script>
  </body>

可以看到它是没有forEach方法的,一旦使用就会报错。但是Array构造函数有啊。Array构造函数创建的数组,通过原型链就能拿到Array.prototype.forEach方法。那借用一下。

js 复制代码
 Array.prototype.forEach.call(lis, (res) => {
        console.log(res);
      });

手写call函数

写一个mycall函数来实现call方法的功能 思路:

  1. 参数有两个。需要一个对象,需要一个方法。
  2. 对象要添加该方法
  3. 执行方法
  4. 删除对象上的方法
js 复制代码
function mycall(obj, fn, ...args) {
        obj.getway = fn;
        let res = obj.getway(...args);
        delete obj.getway;
        return res;
      }
      
      mycall(lis, Array.prototype.forEach, (res) => {
        console.log("res", res);
      });

就是这么简单,我们似乎就实现了call方法的功能。该方法还不够好,没有检验参数类型,临时key值用了一个getway的自定义的名字。再改进改进。

js 复制代码
 function mycall2(fn, obj, ...args) {
        if (typeof fn !== "function") {
          throw new TypeError("第一个参数必须是一个函数");
        }
      
 // 检查 context 是否为对象,如果未提供,则设置为全局对象(浏览器中为 window)
        if (typeof obj !== "object" || typeof obj === null) {
          obj = typeof window === "undefined" ? global : window;
        }
       
 // 用Symbol创建唯一的键以避免与现有属性冲突,可以把Symbol看作对字符串类型的扩展,它是不会重复的字符串
        const keyTemp = Symbol("keyTemp");
        obj[keyTemp] = fn;
        const result = obj[keyTemp](...args);

        delete obj[keyTemp];
        return result;
      }

      mycall2(Array.prototype.forEach, lis, (res) => {
        console.log("res", res);
      });

原型的标准用法

我们之前一直用__proto__来表示对象的原型,这是浏览器厂商定义的非标准属性,我们可以用__proto__来获取和设置原型。

那么标准的获取对象原型的方式就是使用

Object.getPrototypeOf(obj)获取对象原型

Object.setPrototypeOf(obj,xxx)来设置原型

js 复制代码
      let a = {};
      let b = {};

      a.__proto__ = b;  //非标准设置a对象的原型
      Object.setPrototypeOf(a, b); //标准设置a对象的原型     
     

之前我们用__proto__来写原型链,现在换成setPrototypeOf看看效果

原型标准形式写instanceof功能

js 复制代码
      function myInstance(obj, contructor) {
        let obj__proto__ = Object.getPrototypeOf(obj);
        const constructorPortotype = contructor.prototype;

        if (obj__proto__ === null) {
          return false;
        } else if (obj__proto__ === constructorPortotype) {
          return true;
        } else {
          return myInstance(obj__proto__, contructor);
        }
      }

那么今后尽量使用标准方法来获取原型

__proto__究竟是什么

__proto__是对象的访问器,getter与setter

它实现的代码:

js 复制代码
let a = {};     

      Object.defineProperty(a, "__myproto__", {
        get() {
          return Object.getPrototypeOf(this);
        },
        set(value) {
        // 原型的值要是对象类型
          if (typeof value === "object" && value !== null) {
            Object.setPrototypeOf(this, value);
          }
        },
      });
      console.log(a);

当我们获取a对象的原型时,我们就走get方法,当设置a对象的原型时,就走set方法

总结

这一节,说明了原型链的三个具体应用,分别是在

  1. 构造函数的prototype属性上定义方法以节省空间
  2. 对象的遍历两种方法:in操作符会攀升原型链,hasOwnProperty不会
  3. call方法就是将借用的方法挂在第一个参数上并手写了mycall函数

还说了对象原型的标准方法:

  • 获取原型用Object.getPrototypeOf()
  • 设置原型用Object.setPrototypeOf()

最后还使用对象原型的标准方法说明了非标准原型__proto__是对象的访问器getter与setter

相关推荐
一个很帅的帅哥17 分钟前
axios(基于Promise的HTTP客户端) 与 `async` 和 `await` 结合使用
javascript·网络·网络协议·http·async·promise·await
dream_ready1 小时前
linux安装nginx+前端部署vue项目(实际测试react项目也可以)
前端·javascript·vue.js·nginx·react·html5
编写美好前程1 小时前
ruoyi-vue若依前端是如何防止接口重复请求
前端·javascript·vue.js
flytam1 小时前
ES5 在 Web 上的现状
前端·javascript
喵喵酱仔__1 小时前
阻止冒泡事件
前端·javascript·vue.js
GISer_Jing1 小时前
前端面试CSS常见题目
前端·css·面试
某公司摸鱼前端1 小时前
如何关闭前端Chrome的debugger反调试
javascript·chrome
八了个戒1 小时前
【TypeScript入坑】什么是TypeScript?
开发语言·前端·javascript·面试·typescript
不悔哥2 小时前
vue 案例使用
前端·javascript·vue.js
anyup_前端梦工厂2 小时前
Vuex 入门与实战
前端·javascript·vue.js