彻底搞懂原型(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

相关推荐
却尘40 分钟前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare41 分钟前
浅浅看一下设计模式
前端
Lee川44 分钟前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix1 小时前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人1 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl1 小时前
OpenClaw 深度技术解析
前端
崔庆才丨静觅1 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人1 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼2 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端
布列瑟农的星空2 小时前
前端都能看懂的Rust入门教程(三)——控制流语句
前端·后端·rust