JavaScript中改变this指向的三种方式总结
作者:秋天的一阵风
前言
在上篇文章 可能是你看过最完整的this指向总结!中介绍了this指向在不同环境下的总结情况,里面提到过:
this 指向的值是可以通过手动方式去改变的,比如call、bind、apply方法。
那么在本篇中我们介绍的是call、bind、apply方法的使用以及如何实现自己的myCall、myBind、myApply方法。
一、call
1. 使用方法
function person(a,b,c){ console.log(this); console.log(this.name,this.age) } person(); // 1.直接调用,this指向window // 打印 window // 打印 undefined,undefined const obj = {name:"owl",age:18} person.call(obj) // 2.传入指定的this值 // 打印 {name: 'owl', age: 18} // owl 18 function person(a,b,c){ console.log(a,b,c) console.log(this); console.log(this.name,this.age) } person.call(obj,1,2,3) // 3.给person函数传入指定的this值和实参值 // 打印 1 2 3 // {name: 'owl', age: 18} // owl 18
2. 实现myCall方法
介绍完call()方法以后,我们来尝试写一个自己的myCall方法,具体实现代码和注释如下:
Function.prototype.myCall = function (obj) { if (typeof this !== "function") { throw new Error( "Function.prototype.myCall - what is trying to be bound is not callable" ); } const ctx = obj || window; // 1.定义一个ctx变量获取传入的对象obj,如果没有则取window ctx.func = this; // 2.在ctx对象上新增一个属性func,并且给他赋值为this // this就是调用myCall函数的函数,在本例中就是person()方法 const args = Array.from(arguments).slice(1); // 3.处理传入的参数,第一个参数为对象obj, // 所以从第二个开始参数截取 const result = arguments.length > 1 ? ctx.func(...args) : ctx.func(); // 4. 如果传入参数,我们就把实参带入到func函数中执行,如果没有,则直接执行。 delete ctx.func; // 5. 执行完函数以后,记得删除掉这个“中间变量”属性 ctx return result; // 6. 返回result };
在上面的代码中,func其实就是person函数,ctx则是我们传入要指定this指向的对象,也就是 {name: 'owl', age: 18}
。
那么我们在第四步使用ctx.func()
或者ctx.func(...args)
调用func()
时是不是就满足了上篇文章中的调用对象的函数方法时,被调用函数中的this永远指向这个对象
。
所以自然而然就实现了我们手动改变this指向的目的。
var obj = { name: "owllai", }; function testCall(a, b, c) { console.log(this.name, a, b, c); } testCall.myCall(obj,1,2,3)
apply
- apply和call的唯一区别就在于,接收的第二个参数为类数组。
- 除此之外,和call几乎一模一样,所以我们在使用和实现自定义apply方法的代码里只需要修改对应的部分就行了。
1.使用方法
function person(a,b,c){ console.log(this); console.log(this.name,this.age) } person(); // 1.直接调用,this指向window // 打印 window // 打印 undefined,undefined const obj = {name:"owl",age:18} person.apply(obj) // 2.传入指定的this值 // 打印 {name: 'owl', age: 18} // owl 18 function person(a,b,c){ console.log(a,b,c) console.log(this); console.log(this.name,this.age) } person.apply(obj,[1,2,3]) // 3.给person函数传入指定的this值和实参值(类数组对象) // 打印 1 2 3 // {name: 'owl', age: 18} // owl 18
2.实现myApply方法
Function.prototype.myApply = function (obj) { if (typeof this !== "function") { throw new Error( "Function.prototype.myApply - what is trying to be bound is not callable" ); } const ctx = obj || window; // 1.定义一个ctx变量获取传入的对象obj,如果没有则取window ctx.func = this; // 2.在ctx对象上新增一个属性func,并且给他赋值为this // this就是调用myApply函数的函数,在本例中就是person()方法 const args = arguments[1]; // 3.处理传入的参数,第一个参数为对象obj, // 第二个参数为数组实参 const result = arguments[1] ? ctx.func(...arguments[1]) : ctx.func(); //第四步: 调用方法,获得结果。 delete ctx.func; return result; };
myApply方法里面,我们只需要更改两点:
- 第三步获取参数上,直接获取arguments数组的第二项
- 第四步调用方法上,传入获取到的arguments 数组的第二项
bind
1.使用方法
- bind接收的参数形式和call有点相似的:第一个参数是指定this指向的值,第二个参数开始则是执行函数需要的形参
- bind方法在调用以后不会像call、apply一样直接执行,而是返回一个新函数。
语法:
bind(thisArg)
bind(thisArg, arg1)
bind(thisArg, arg1, arg2)
bind(thisArg, arg1, arg2, /* …, */ argN)
function person(a, b, c) { console.log(this); console.log(this.name, this.age); } const obj = { name: "owl", age: 18 }; let newPerson = person.bind(obj); console.log(newPerson); // ƒ person(a, b, c) { // console.log(this); // console.log(this.name, this.age); // } newPerson(); // {name: 'owl', age: 18} // owl 18 function person(a, b, c) { console.log(a,b,c) console.log(this); console.log(this.name, this.age); } let newPersonWithArgs = person.bind(obj,1,2,3) ; newPersonWithArgs(); // 1 2 3 // {name: 'owl', age: 18} // owl 18
2.实现myBind方法
简单版本
function person(a, b, c) { console.log(a, b, c); console.log(this); console.log(this.name, this.age); } let obj = { name: "owllai", age: 18, }; Function.prototype.myBind = function (obj) { if (typeof this !== "function") { throw new Error( "Function.prototype.bind - what is trying to be bound is not callable" ); } var self = this; // 1. 这个this代表调用myBind方法的函数,在本例中也就是person var args = Array.prototype.slice.call(arguments, 1); // 2. 获取传入的其他参数,从arguments数组的第二项开始截取 var fn = function () { // 3.定义一个要返回的函数 //4. 返回的新函数也是可以接收参数的,所以我们要一起获取 var newArgs = Array.prototype.slice.call(arguments); //5. 将obj和参数一起传入,使用apply来执行 return self.apply(obj, args.concat(newArgs)); }; //6. 最后返回结果函数 return fn; }; let newBind = person.myBind(obj, 1, 2, 3); newBind();
myBind的简单版本已经实现,但是还有一个问题没有解决,那就是既然返回的是一个新函数,那除了直接调用newBind() 方法以外,还可以将newBind当成构造函数,使用 new 关键字进行实例化。
比如下面的实例化例子:
let newBind = person.myBind(obj, 1, 2, 3); let n = new newBind();
- 我们知道变量n正常来说应该是newBind函数的实例化对象,构造函数的this指向实例化对象。
- 而在
return self.apply(obj, args.concat(newArgs));
这一行代码中,我们是写死this指向为obj。这样显然是不对的。 - 我们必须考虑到new的情况,所以对简单版本的myBind代码进行改造
完整版本
Function.prototype.myBind = function (obj) { if (typeof this !== "function") { throw new Error( "Function.prototype.bind - what is trying to be bound is not callable" ); } var self = this; // 这个this代表调用myBind方法的函数,在本例中也就是person函数 var args = Array.prototype.slice.call(arguments, 1); var fn = function () { var newArgs = Array.prototype.slice.call(arguments); return self.apply( //如果没有进行判断,永远写死obj作为apply的第一个参数,那么如果对fn这个返回函数进行new时,这个fn函数的this指向永远是外部传过来的obj //这样是不正确的,如果作为new关键字使用这个fn函数,this指向必须是指向new出来的实例对象 //怎么判断是不是用new关键字来调用呢? // 我们可以用 instanceof 来判断返回函数的原型是否在实例的原型链上 // 如果返回函数是被new了,那这个返回函数的实例对象的this就指向了person函数 this instanceof fn ? this : obj, args.concat(newArgs) ); }; // 创建一个空函数 var tmpFn = function () {}; // 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值 // 可以直接使用 fn.prototype = this.prototype (this代表fn) // fn.prototype = this.prototype; // 也就是让返回函数的原型对象和person函数的原型对象映射 // 至于为什么使用一个空函数 tmpFn 作为中介,把 fn.prototype 赋值为空对象的实例(原型式继承), // 这是因为直接 fn.prototype = this.prototype 有一个缺点,修改 fn.prototype 的时候,也会直接修改 this.prototype ; // tmpFn空函数的原型指向绑定函数的原型 tmpFn.prototype = this.prototype; //(this代表person函数) // 空对象的实例赋值给 fn.prototype fn.prototype = new tmpFn(); return fn; };
到此这篇关于JavaScript中改变this指向的三种方式总结的文章就介绍到这了,更多相关JavaScript改变this指向内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!