【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或实例对象
相关推荐
空中海11 分钟前
01 React Native 基础、核心组件与布局体系
javascript·react native·react.js
前端之虎陈随易2 小时前
2年没用Nodejs了,Bun很香
linux·前端·javascript·vue.js·typescript
好运的阿财3 小时前
OpenClaw工具拆解之host_workspace_write+host_workspace_edit
前端·javascript·人工智能·机器学习·ai编程·openclaw·openclaw工具
XiYang-DING4 小时前
JavaScript
开发语言·javascript·ecmascript
空中海5 小时前
02 React Native状态、导航、数据流与设备能力
javascript·react native·react.js
空中海5 小时前
02 状态、Hooks、副作用与数据流
开发语言·javascript·ecmascript
空中海6 小时前
04 React Native工程化、质量、发布与生态选型
javascript·react native·react.js
杨超凡6 小时前
豆包收费了?我特么自己用“意念”搓了一个!
javascript
threelab7 小时前
Three.js 咖啡杯烟雾效果 | 三维可视化 / AI 提示词
开发语言·javascript·人工智能