参考链接:
在手写之前,最好先了解为什么会出现这个需要改变this指向的场景,以及 bind, call, apply 的作用和它们的区别。
this 是什么
它是函数内部的属性,是函数执行的环境对象,也就是是函数的 this 会指向函数的执行环境。
this 指向哪里
函数的 this 关键字在 JavaScript 中的表现略有不同,此外,在严格模式和非严格模式之间也会有一些差别。
大致指向总结可以分为以下几种情况(非严格模式):
- 函数作为全局环境调用时 ```javascript // 非严格模式 var 声明才会挂在window上,let, const声明不会 var name = “global”; // 相当于 window.name = “global”
function a() { return this.name }
// 此处是在window全局环境中调用a函数,所以内部的this指向window a() === “global”;
2. 函数作为对象方法调用时```javascriptvar obj = {name: "obj",sayName: function(){return this.name}}// 此处调用者是obj,this指向obj内部obj.sayName() === "obj" // true// 稍微改动一下,添加下面代码:var sayName = obj.sayName;// 此时调用者是window,所以falsesayName() === "obj" // false
- 函数作为dom节点事件调用时 ```javascript var container3 = document.getElementById(‘container3’)
container3.onclick = function(){ // 指向节点本身 console.log(this) //
4. 作为构造函数实力化方法时```javascriptfunction A(name){this.name = name;this.sayName = function(){// 指向实例对象console.log(this.name)}}var a = new A('aa');a.sayName(); // aa
- 箭头函数里的this
```javascript
var name = ‘window’
var obj = {
name:’obj’,
fn: function(){
(function (){ console.log(this.name) })() }
} // 普通函数,由于闭包函数是window执行的,所以this指向window obj.fn() // window
// 箭头函数的this指向函数创建时的作用域
var obj2 = {
name:’obj’,
fn: function(){
(()=>{ //改成箭头函数
console.log(this.name)
})()
}
}
obj2.fn()
<a name="WRyvA"></a>## this指向怎么改众所周知,引用类型在内存中储存的是对象的内存地址,那么作为函数,它有可能在不同的环境(上下文)被执行。当在函数体内部,引用当前环境的其他变量时,函数需要获取当前的运行环境(context),所以,this 出现了,它设计的目的就是在函数体内部,代指函数当前的运行环境。那为什么需要改变 this 指向?也好理解,比如上面例子2的场景,如果调用对象被赋值后调用没有绑定 this,那么它原本的指向将会变成window(或者其他调用环境),这时候就需要绑定this的指向了,用到call,bind,apply。以上面的例子2为例:```javascriptvar obj = {name: "obj",sayName: function(){return this.name}}// bindvar sayNameBind = obj.sayName;var sayNameCall = obj.sayName;var sayNameApply = obj.sayName;console.log(sayNameBind.bind(obj)() === "obj") // trueconsole.log(sayNameCall.call(obj) === "obj") // trueconsole.log(sayNameApply.apply(obj) === "obj") // true
bind, call, apply 语法和区别
mdn:
apply:fn.apply(thisObj, 数组参数)
定义:应用某一个对象的一个方法,用另一个对象替换当前对象
说明:如果参数不是数组类型的,则会报一个TypeError错误。
call:fn.call(thisObj, arg1, arg2, argN)
apply与call的唯一区别就是接收参数的格式不同。
bind:fn.bind(thisObj, arg1, arg2, argN)
bind()方法创建一个新的函数,在bind()被调用时,这个新函数的this被bind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。
区别
call、bind 和 apply 的第一个参数都是要改变上下文的对象
call、bind 从第二个参数开始以参数列表的形式展现,apply 第二个参数是数组参数;
call、apply改变了函数的this上下文后便立刻执行该函数,bind 则是返回改变了上下文后的一个函数;
手写
apply
Function.prototype.myApply= function(context, args = []){// 1. 限制参数类型为数组if(!Array.isArray(args)) throw new Error('apply的第二个参数必须是数组')if (!context || context === null) context = window;// 创造唯一的key值 作为我们构造的context内部方法名const fn = Symbol();// 2.将函数挂载到传入的对象context[fn] = this;// 3.执行对象的方法return context[fn](...args);}// Test:var obj = {name: "obj",sayName: function(params){return this.name + params}}var sayNameApply = obj.sayName;console.log(sayNameApply.myApply(obj, ["test"]) === "objtest") // true
call,与apply的唯一区别就是参数格式不同
Function.prototype.myCall= function(context, ...args){if (!context || context === null) context = window;// 创造唯一的key值 作为我们构造的context内部方法名const fn = Symbol();// 2.将函数挂载到传入的对象context[fn] = this;// 3.执行对象的方法return context[fn](args);}// Test:var obj = {name: "obj",sayName: function(params){return this.name + params}}var sayNameCall = obj.sayName;console.log(sayNameCall.myCall(obj, "test") === "objtest") // true
bind
Function.prototype.myBind = function (context, ...args) {if (!context || context === null) {context = window;}const fn = Symbol();context[fn] = this;let _this = this;const result = function (...innerArgs) {if (this instanceof _this === true) {// 此时this指向指向result的实例 这时候不需要改变this指向this[fn] = _this;return this[fn](...[...args, ...innerArgs]);} else {// 如果只是作为普通函数调用 那就很简单了 直接改变this指向为传入的contextreturn context[fn](...[...args, ...innerArgs]);}};// 如果绑定的是构造函数 那么需要继承构造函数原型属性和方法// 实现继承的方式: 使用Object.createresult.prototype = Object.create(this.prototype);return result;};// Test:var obj = {name: "obj",sayName: function(params){return this.name + params}}var sayNameBind = obj.sayName;console.log(sayNameBind.myBind(obj, "test")()) // true
