【JS】手写显示绑定改变this指向的方法call、apply、bind | 笔记整理

关于修改this指向,除了默认绑定以外,有隐式绑定、显示绑定、通过new绑定到构造的实例对象、通过箭头函数继承外层的作用域的几种方法。

隐式绑定

在对象中写一个函数,当函数作为对象一个的方法调用的时候(person.greet()),函数中的this指向该调用该方法的实例对象。

javascript 复制代码
const person = {
  name: "Alice",
  greet: function() {
    console.log(`Hello, ${this.name}!`);
  }
};
​
person.greet(); // 输出: "Hello, Alice!"(this 指向 person)

但是会产生隐式丢失的问题

ini 复制代码
const greet = person.greet;
geert(); //此时this指向全局对象,会输出hello,undefiend

显示绑定

call

  • 语法:function.prototype. call(thisArg,arg1,arg2,...)
  • 作用:改变函数this指向传入的参数thisArg,预设参数arg1arg2,并立即调用
  • 用途:借用方法、指定上下文
javascript 复制代码
function sayName() {
  console.log(this.name);
}
​
const obj = { name: 'Alice' };
sayName.call(obj); // Alice
手写call

参数:context-希望函数内部this指向的对象,...args-需要预设的参数

ini 复制代码
Function.prototype.myCall = function(context,...args){

    //首先判断有没有传入context参数,没有的话默认为全局对象window
    if(!context || context === null){
    context = window;
    }
    
    //创建唯一的key值,作为构造的context内部方法名
    let fn = Symbol();
    context[fn] = this;  //指向调用call的函数
    
    //执行函数并保存返回结果
    const result = context[fn](...args);
    delete context[fn]; 
    //在context对象上访问一个名为fn的属性,它是一个函数
    
    return result;
}

注:在执行完函数并保存返回结果后,需要清理临时添加到对象上的方法context[fn],释放内存,同时避免和以后可能用到的属性重名。

apply

  • 语法:function.prototype.apply(thisArg,[argsArray])
  • 作用:改变this指向,预设参数是数组的备份形式,也是立即调用
  • 用途:处理数组参数
arduino 复制代码
const numbers = [1, 5, 3];
Math.max.apply(null, numbers); // 输出: 5
手写apply

与call大致相同,不同之处是传递的参数为数组。

javascript 复制代码
Function.prototype.myApply = function(context,argsArray){
    if(!context || context === null){
        context = window;
    }
    
    //判断参数是不是以数组形式传递的,或是否是类数组对象
      if (!Array.isArray(argsArray) && !(argsArray && typeof argsArray === 'object' && 'length' in argsArray)) {
        throw new TypeError('CreateListFromArrayLike called on non-object');
    } 
    
    //将当前函数绑定到context上
    let fn = new Symbol();
    context[fn] = this;
    
    //执行函数并保存结果
    const result = context[fn](...argArray);
    delete context[fn]; //删除防止内存泄漏
    
    return result;
}

bind

  • 语法:function.prototype.bind(thisArg,arg1,arg2,...)
  • 作用:永久绑定this,预设部分参数(柯里化),目前不调用,返回一个函数
  • 用途:事件监听、固定上下文
ini 复制代码
var bar=function(){   
  console.log(this.x);   
}
var foo={ 
     x:3   
}   
bar();  

var func=bar.bind(foo);   
func();
​
输出:
undefined
3

注:连续bind多次只会输出第一次的结果,在Javascript中,多次 bind() 是无效的。更深层次的原因, bind() 的实现,相当于使用函数在内部包了一个 call / apply ,第二次 bind() 相当于再包住第一次 bind() ,故第二次以后的 bind 是无法生效的。

关于bind的作用场景如下:

事件监听

  • 当将一个对象的方法作为事件监听器时,方法中的this会默认指向触发事件的DOM元素,而非原对象,导致上下文丢失。
javascript 复制代码
const button = document.querySelector("button");
​
const handler = {
  message: "Button clicked!",
  handleClick: function() {
    console.log(this.message); // 这里 this 预期指向 handler,在调用的时候实际指向 button
  }
};
​
// 直接绑定方法,this 会丢失!
button.addEventListener("click", handler.handleClick); 
// 点击后输出: undefined,因为此时this指向了button,而非handler

固定上下文

通过bind将handleClick方法的this永久绑定到handler对象。

less 复制代码
// 使用 bind 绑定 this 到 handler
button.addEventListener("click", handler.handleClick.bind(handler)); 
​
// 点击后输出: "Button clicked!"
手写bind
  • bind返回一个函数
  • 调用新函数时,通过apply强制将this设置为thisArg
  • 参数支持分步传递

方式1:借用apply

javascript 复制代码
//借用apply
Function.prototype.myBind = function(thisArg,...args){
    const fn = this; //保存原函数
    return function fn.apply(thisArg,[...args,...newArgs]);
};
​
button.addEventListener("click",handler.handlerClick.myBind(handler));

方式2:手写

在手写bind的过程中遇到了一些小细节上的问题,在这里记录一下:

  • bind并不直接调用函数,而是返回一个函数。

  • _this保存的是原函数,即调用newBind的函数。

  • 但this是动态的,取决于调用方式

    • new调用 :this是 _this的实例对象,该实例继承自_this.prototype

    • 关系为:this.__proto__ === _this.prototype

      ini 复制代码
      function Person(name) { this.name = name; }
      const BoundPerson = Person.myBind({});
      const instance = new BoundPerson("Bob"); // new 调用
      • this指向BoundPerson的实例对象instance
      • _this是绑定前的原函数Person
    • 普通调用:this指向绑定的context

      ini 复制代码
      function greet() { console.log(this.name); }
      const obj = { name: "Alice" };
      const boundGreet = greet.myBind(obj);
      boundGreet(); // 普通调用
      • _this是原函数greet
      • this指向obj(传入的context参数)

具体手写实现

javascript 复制代码
//纯手写

Function.prototype.newBind = function(context,...args){
    //判断context是否传入内容
    if(!context || context === null){
        context = window;
    }
    
    let fn = Symbol(); // 创建key值
    context[fn] = this; //指向调用函数的this
    let _this = this;  //保存原函数引用
    
    const result = function(...innerArgs){
    
    //合并参数
    const args = [...args,...innerArgs];
    
    //情况1:执行当前函数的时候,是【将bind绑定后的函数作为构造函数,通过new构造的实例】,需要将this指向实例化对象,比如:
    //const BoundFoo = Foo.bind(null);
    //const obj = new BoundFoo("Tom"); 
    if(this instanceof _this === ture)  //通过原型链判断
        this[fn] = _this;  //保存原函数
        const res1 = this[fn](...args);//!使用ES6的方法让bind支持参数合并
        delete this[fn];  //及时清理
        return res1;
    }else{
    
     //情况2:作为普通的函数使用,直接改变this指向为传入的context
        const res2 = context[fn](...args);
        delete context[fn];
        return res2;
    }
    
    // 如果绑定的是构造函数 那么需要继承构造函数原型的属性和方法
    // 实现继承的方式:使用 Object.create
        result.prototype = Object.create(this.prototype);
        return result;
}
  • delete:清理临时添加到对象上的方法,避免内存泄漏和属性污染。

    • 在bind的实现中,为了绑定this,将原函数(_this)挂载到对象上
    • 目的:通过context[fn]调用原函数时,让函数内部的this指向context或实例对象
相关推荐
Splendid14 分钟前
Geneformer:基于Transformer的基因表达预测深度学习模型
javascript·算法
EndingCoder15 分钟前
React Native 开发环境搭建(全平台详解)
javascript·react native·react.js·前端框架
小公主20 分钟前
用原生 JavaScript 写了一个电影搜索网站,体验拉满🔥
前端·javascript·css
Moment23 分钟前
为什么我在 NextJs 项目中使用 cookie 存储 token 而不是使用 localstorage
前端·javascript·react.js
天才熊猫君27 分钟前
uniapp小程序改网页笔记
javascript
江城开朗的豌豆35 分钟前
Git分支管理:从'独狼开发'到'团队协作'的进化之路
前端·javascript·面试
红衣信38 分钟前
电影项目开发中的编程要点与用户体验优化
前端·javascript·github
帅夫帅夫1 小时前
一文手撕call、apply、bind
前端·javascript·面试
锈儿海老师1 小时前
AST 工具大PK!Biome 的 GritQL 插件 vs. ast-grep,谁是你的菜?
前端·javascript·eslint
令狐寻欢2 小时前
JavaScript中 的 Object.defineProperty 和 defineProperties
javascript