关于修改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
inifunction Person(name) { this.name = name; } const BoundPerson = Person.myBind({}); const instance = new BoundPerson("Bob"); // new 调用
this
指向BoundPerson的实例对象instance_this
是绑定前的原函数Person
-
普通调用:this指向绑定的context
inifunction greet() { console.log(this.name); } const obj = { name: "Alice" }; const boundGreet = greet.myBind(obj); boundGreet(); // 普通调用
_this
是原函数greetthis
指向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或实例对象
- 在bind的实现中,为了绑定this,将原函数(