本网站(662p.com)打包出售,且带程序代码数据,662p.com域名,程序内核采用TP框架开发,需要联系扣扣:2360248666 /wx:lianweikj
精品域名一口价出售:1y1m.com(350元) ,6b7b.com(400元) , 5k5j.com(380元) , yayj.com(1800元), jiongzhun.com(1000元) , niuzen.com(2800元) , zennei.com(5000元)
需要联系扣扣:2360248666 /wx:lianweikj
Javascript的New、Apply、Bind、Call知多少
xufei · 358浏览 · 发布于2021-12-06 +关注

Javascript中的apply、call、bind方法是前端代码开发中相当重要的概念,并且与this的指向密切相关。本篇文章我们将深入探讨这个关键词的作用,并尝试进行手动编写复现。

1 写在前面

Javascript中的apply、call、bind方法是前端代码开发中相当重要的概念,并且与this的指向密切相关。本篇文章我们将深入探讨这个关键词的作用,并尝试进行手动编写复现。

阅读文章前,我们带着几个问题进行研究:

  • 用什么样的思路可以new关键字?

  • apply、bind、call这三个方法有什么区别?

  • 怎样手动实现一个apply、bind和call?

2 new关键词

new关键词的作用是执行一个构造函数,返回一个实例对象,根据构造函数的情况来确定是否可以接受参数的传递。

2.1 new的原理

使用new进行实例化对象,其步骤是:

  1. 创建一个新的空对象,即{}

  2. 将该对象构造函数的作用域赋给新对象,this指向新对象(即将新对象作为this的上下文)

  3. 执行构造函数中的代码,为这个新对象添加属性

  4. 如果该对象构造函数没有返回对象,则返回this

function Person(){ 
 this.name = "yichuan" 
} 

const p = new Person(); 
console.log(p.name);//"yichuan"

    我们可以看到当使用new进行实例化时,可以将构造函数的this指向新对象p。当不使用new时,此时构造函数的this指向window。

    function Person(){ 
     this.name = "yichuan" 
    } 
    
    const p = Person(); 
    console.log(p);//undefined 
    console.log(name);//"yichuan"   window.name 
    console.log(p.name);//"yichuan" is undefined

      当我们在构造函数中直接返回一个和this无关的对象时,使用new关键字进行实例化对象,新生成的对象就是构造函数返回的对象,而非构造函数的this的对象。

      function Person(){ 
       this.name = "yichuan"; 
        return {age:18}; 
      } 
      
      const p = new Person(); 
      console.log(p);//{age:18} 
      console.log(p.name);//"undefined" 
      console.log(p.age);/18

        此外,当构造函数返回的不是一个对象,而是基础数据类型的值时,使用new创建新对象,会将构造函数返回的值以对象形式给新对象。

        function Person(){ 
         this.name = "yichuan"; 
          return "onechuan"; 
        } 
        
        const p = new Person(); 
        console.log(p);//{name:"yichuan"} 
        console.log(p.name);//"yichuan"

          new关键词执行之后总是会返回一个对象,要么是实例,要么是return语句指定的对象。

          2.2 手写new的实现

          new被调用后大致做了哪些事情?

          1. 让实例可以访问私有属性

          2. 让实例可以访问构造函数原型(constructor.prototype)所在原型链上的属性

          3. 构造函数返回的最后结果是引用数据类型

          function new_object(ctor,...args){ 
           //先要判断ctor是否为一个函数 
            if(typeof ctor !== "function"){ 
             throw "ctor must be a function"; 
            } 
            //创建一个空对象 
            const obj = new Object(); 
            //将实例obj可以访问到ctor原型所在原型链的属性 
            obj.__proto__ = Object.create(ctor.prototype); 
            //将构造函数的this指向实例对象obj 
            const res = ctor.apply(obj,...args); 
            //确保最后new返回的是一个对象 
            const isObject = typeof res === "object" && typeof res!== null; 
            const isFunction = typeof res === "function"; 
            return isObject || isFunction ? res : obj; 
          }

            当然,我们还可以进行优化以下:

            function new_object() { 
              // 1、获得构造函数,同时删除 arguments 中第一个参数 
              const ctor = [].shift.call(arguments);//其实这里是借用了数组的shift方法 
              // 2、创建一个空的对象并链接到原型,obj 可以访问构造函数原型中的属性 
              const obj = Object.create(ctor.prototype); 
              // 3、绑定 this 实现继承,obj 可以访问到构造函数中的属性 
              const ret =ctor.apply(obj, arguments); 
              // 4、优先返回构造函数返回的对象 
              return ret instanceof Object ? ret : obj; 
            };

              3 apply、bind以及call

              apply、bind和call是挂载Function对象上的三个方法,调用这三个方法的必须是一个函数。

              3.1 apply

              apply() 方法调用一个具有给定 this 值的函数,以及作为一个数组(或类似数组对象)提供的参数。apply()方法可以改变函数this的指向,且立即执行函数。

              注意:Chrome 14 以及 Internet Explorer 9 仍然不接受类数组对象。如果传入类数组对象,它们会抛出异常。

              func.apply(thisArg, [param1,param2,...]);

                在使用apply时,会将func的this指向改变为指向thisArg,然后以[param1,param2,...]参数数组作为参数输入。

                func(["red","green","blue"]); 
                func.apply(newFun, ["red","green","blue"]);

                  我们可以看到都执行func时,第一个func函数的this指向的是window全局对象,而第二个func函数的this指向的是newFun。

                  Function.prototype.apply = function (context, arr) { 
                      context = context ? Object(context) : window;  
                      context.fn = this; 
                     
                      let result; 
                     //判断有没有参数数组输入 
                      if (!arr) { 
                          result = context.fn(); 
                      } else { 
                          result = context.fn(...arr); 
                      } 
                     //此处也可以使用eval进行处理 
                     // const result = eval("context.fn(...arr)"); 
                         
                      delete context.fn 
                      return result; 
                  }

                    3.2 bind

                    bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

                    bind(thisArg,param1,param2,...);

                      其实,bind的实现思路基本和apply一致,但是在最后实现返回结果时,bind不需要直接执行,而是以返回函数的形式返回结果,之后再通过执行这个结果即可。

                      先分析下bind的特性:首先是指定新对象this指向,再传入参数返回一个定义的函数,最后使用柯里化进行调用。同样的,我们也可以根据这些特性进行手动封装一个bind函数:

                      Function.prototype.bind = function(context){ 
                       //先要判断调用bind函数的是不是函数,需要抛出异常 
                        if(typeof this !== "function"){ 
                         throw new Error("this bind function must be userd to function"); 
                        } 
                        //存储this的指向 
                        const self = this; 
                        //context是新对象this指向的目标对象,而参数就是在第一个参数之后的参数 
                        const args = Array.prototype.slice.call(arguments,1); 
                         
                        //创建一个空对象 
                        const fun = function(){} 
                         
                        //返回一个函数 
                        const funBind = function(){ 
                         //返回所有的参数给bind函数 
                          const bindArg = Array.prototype.slice.call(arguments); 
                          //将传入的参数合并成一个新的参数数组,作为self.apply()的第二个参数 
                          return self.apply(this instanceof fun ? this : context,  args.concat(bindArgs)); 
                          /**********************说明************************************/ 
                        } 
                         
                        //空对象的原型指向绑定函数的原型 
                        fun.prototype = this.prototype; 
                        //空对象的实例赋值给 funBind.prototype 
                        funBind.prototype = new fun(); 
                        return funBinf; 
                      }

                        补充说明:

                        • this instanceof fun返回为true时,表示的是fun是一个构造函数,其this指向实例,直接将context作为参数输入

                        • this instanceof fun返回为false时,表示的是fun是一个普通函数,其this指向顶级对象window,将绑定函数的this指向context对象

                        当然,我们也可以写成这种形式:

                        Function.prototype.bind = function(context,...args){ 
                         //先要判断调用bind函数的是不是函数,需要抛出异常 
                          if(typeof this !== "function"){ 
                           throw new Error("this bind function must be userd to function"); 
                          } 
                          //存储this的指向 
                          const self = this; 
                           
                          const fBind = function(){ 
                           self.apply(this instanceof self ? this: context, args.concat(Array.prototype.slice.call(arguments)));  
                          } 
                          if(this.prototype){ 
                           fBind.prototype = Object.create(this.prototype); 
                          } 
                          return fBind; 
                        }

                          注意:Object.create()是es2015语法引入的新特性,因此在IE<9的浏览器是不支持的。

                          3.3 call

                          call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined。

                          function.call(thisArg, param1, param2, ...)

                            注意:该方法的语法和作用与 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组。

                            call函数的实现:

                            Function.prototype.call = function(context,...args){ 
                              //将函数设置为对象的属性 
                             context = context || window; 
                              context.fn = this; 
                               
                              //执行函数 
                              const result = eval("context.fn(...args)"); 
                              //删除对象的这个属性 
                              delete context.fn; 
                              return result; 
                            }

                              4 参考文章

                              • 《解析 bind 原理,并手写 bind 实现》

                              • 《解析 call/apply 原理,并手写 call/apply 实现》

                              • 《Javascript核心原理精讲》

                              5 写在最后

                              在这篇文章中,我们知道apply、bind、call的区别在于:

                              • apply、call改变了this指向后,会立即进行调用函数,返回的是执行结果

                              • bind在改变this指向后,返回的是一个函数,需要另外再进行调用一次

                              • bind、call传递的第一个参数都是this将要指向的对象,后面都是一个一个参数的形式输入

                              • apply传递的第一个参数也是this将要指向的对象,后面传递的第二个参数是一个参数数组


                              相关推荐

                              PHP实现部分字符隐藏

                              沙雕mars · 1325浏览 · 2019-04-28 09:47:56
                              Java中ArrayList和LinkedList区别

                              kenrry1992 · 908浏览 · 2019-05-08 21:14:54
                              Tomcat 下载及安装配置

                              manongba · 970浏览 · 2019-05-13 21:03:56
                              JAVA变量介绍

                              manongba · 962浏览 · 2019-05-13 21:05:52
                              什么是SpringBoot

                              iamitnan · 1086浏览 · 2019-05-14 22:20:36
                              加载中

                              0评论

                              评论
                              分类专栏
                              小鸟云服务器
                              扫码进入手机网页