this、call、apply、bind的理解

105 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第15天,点击查看活动详情 什么是this

this是在函数的内部使用,用来指代当前的运行环境

global this

1、this等价于window对象;

2、使用var定义的变量就相当于window定义变量

3、声明变量时没有使用var或者let、const的活,变量就是指向全局的

总结: 在全局范围里的this就相当于window对象(this=window)

function this

函数的this最好的理解就是在运行时this永远指向最后调用它的那个对象。

var name = "windowName"

function Animal(){

​ var name = 'tiger';

​ console.log(this.name); // windowName

​ console.log(this); // window

}

Animal()

console.log(this); //window

从上面的例子可以看到Animal()并没有调用的对象,所以this指向全局的window

var name = "windowName"

function Animal(){

​ var name = 'tiger';

​ console.log(this.name);

​ console.log(this);

}

let obj = {

​ name: 'obj',

​ animal: Animal

}

obj.animal() // obj

当我用obj的animal去调用animal方法时this就会指向obj

构造函数中的this

构造函数就是通过这个函数生成一个新的对象(object)。当函数作为构造器时,this指向新创建的对象。注意:如果没使用new关键字,那么就只是个普通函数,this将指向window对象

(总结一句话:谁被new了,this就指向谁)

class中的this

类通常包含constructor,this可以指向任何新创建的对象,this可以指向任何新创建的对象,不过在作为方法时,如果该方法作为普通函数被调用,this也可以指向任何其他值。与方法相同,类也可能失去对接收器的跟踪

call、apply和bind中的this

call、apply、bind被称为this的强绑定,用来改变函数执行时的this指向,目前所有关于它们的运用都是基于这点进行的

var name = 'xunCai'

function hei(){

​ console.log(this.name); //xunCai

}

var obj = {

​ name: 'kung'

}

hei()

hei.call(obj) // kung

hei.apply(obj) //kung

hei.bind(obj) //kung

箭头函数的this

箭头函数是没有this绑定的,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包裹,则this绑定的是最近一层非箭头函数的this,否则this为undefined,箭头函数的this始终指向函数定义时的this,而非执行时。

let name = 'kk'

let s = {

​ name: 'tt',

​ hei: function(){

​ console.log(this.name);

​ },

​ func: function(){

​ setTimeout(()=>{

​ this.hei()

​ },100)

​ }

}

s.func() // tt

tips: 使用call、apply或bind等方法给this传值,箭头函数会忽略。箭头函数引用的是箭头函数在创建时设置的this值

面试题

var number = 1;

var obj = {

number:2,

showNumber:function(){

this.number = 3;

(function(){ console.log(this.number); })(); // 1 自调用是种特殊情况,自调用会将this指向window对象

console.log(this.number); 3

、 }

};

​ obj.showNumber();

call & apply

每个函数都包含两个非继承而来的方法:apply()和call。都是用于特定的作用域中调用函数,实际上等于设置函数体内this对象的值

apply()

apply()方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是Array的实例,也可以是arguments对象。

function count(num1,num2){

return num1+num2;

}

function callSum1(num1,num2){

return count.apply(this,arguments);

}

function callSum2(num1,num2){

return count.apply(this,[num1,num2]);

}

console.log(callSum1(10,10));

console.log(callSum2(10,10));

tips:严格模式下,未指定环境对象而调用函数,则this值不会转型为window。除非明确把函数添加到某个对象或者调用apply()或者call(),否则this值将是undefined。

call()

call()方法与apply()方法的作用相同,它们的唯一区别在于接收函数的方式不同。在使用call()方法时,传递给函数的参数必须逐个列举出来

function count(num1,num2){

return num1+num2;

}

function callSum(num1,num2){

return count.call(this,num1,num2);

}

console.log(callSum(10,10));

call()方法与apply()方法返回的结果是完全相同的,至于是使用apply()还是call(),完全取决于采用哪种给函数传递参数的方式最方便。

参数数量/顺序确定就用call,参数数量/顺序不确定的话就用apply。

考虑可读性:参数数量不多就用call,参数数量比较多的话,把参数整合成数组,使用apply。

bind()

bind()方法会创建一个函数的实例,其this值会被绑定到传给bind()函数的值。bind()会返回一个新函数。

window.color = 'red';

var c = { color: 'blue' };

function sayColor(){

​ alert(this.color)

}

var objectSayColor = sayColor.bind(c);

objectSayColor();

call/apply与bind的区别

执行

call/apply改变了函数的this上下文后马上执行该函数

bind则是返回改变了上下文后的函数,不执行该函数

function count(a,b){

return a+b;

}

function sub(a,b){

return a-b;

}

console.log(count.bind(sub,5,3)); // 返回函数

console.log(count.bind(sub,5,3)()); //立即执行

返回值

call/apply返回fun的执行结果

bind返回fun的拷贝,并指定了fun的this指向,保存了fun的参数

手写实现apply、call、bind

apply

1、先给Function原型上扩展个方法并接受2个参数

Function.prototype.myApply = function (context,args) {}

2、因为不传context的话,this会指向window,所以这里将context和args做一下容错处理。

Function.prototype.myApply = function (context,args){

​ context = (typeof context === 'object'?context:window)

​ args = args ? args:[]

}

3、使用隐式绑定去实现显示绑定

Function.prototype.myApply = function (context,args){
​
    context = (typeof context === 'object'?context: window)
​
        args = args?args:[]
​
        //给context新增一个独一无二的属性以免覆盖原有属性const key = Symbol()
​
        context[key] = this
​
        //通过隐式绑定的方式调用函数
​
        context[key](...args)
​
}

4、最后一步要返回函数调用的返回值,并且把context上的属性删除避免造成影响

Function.prototype.myApply = function (context,args){

// 处理容错

context = ( typeof context === 'object'?context:window )

args = args?args:[]

//给context新增一个独一无二的属性以免覆盖原有属性

const key = Symbol();

context[key] = this;

// 通过隐式绑定的方式调用函数

const result = contextkey;

// 删除添加的属性

delete context[key]

// 返回函数调用的返回值

return result;

}

function func(...args){

console.log(this.name,...args);

}

const result = {

name: 'Jake'

}

func.myApply(result,[1,2])

call

// 传递参数从一个数组变成逐个传参,不用...扩展运算符的也可以用arguments代替

Function.prototype.myCall = function(context,...args){

​ // 这里默认不传就是window,也可以用es6给参数设置默认参数

​ context = ( typeof context === 'object' ? context : window )

​ args = args ? args : []

​ // 给context新增一个独一无二的属性以免覆盖原有属性

​ const key = Symbol();

​ context[key] = this;

​ // 通过隐式绑定的方式调用函数

​ const result = contextkey;

​ // 删除添加的属性】

​ delete context[key];

​ // 返回函数调用的返回值

​ return result;

}

bind

Function.prototype.myBind = function (objThis,...params){

const thisFn = this; // 存储源函数以及上方的params(函数参数)

// 对返回的函数 secondParams 二次传参

let fToBind = function (...secondParams){

​ const isNew = this instanceof fToBind // this是否是fToBind的实例 也就是返回的fToBind是否通过new调用

​ const context = isNew ? this : Object(objThis) // new调用就绑定到this上,否则就绑定到传入的objThis上

​ return thisFn.call(context,...params,...secondParams);

}

if(thisFn.prototype){

​ // 复制源函数的prototype给fToBind一些情况下函数没有prototype,比如箭头函数

​ fToBind.prototype = Object.create(thisFn.prototype);

}

​ return fToBind; 返回拷贝函数

}

总结

1、在浏览器里,在全局范围内的this指向window对象

2、在函数中,this永远指向最后调用他的那个对象

3、构造函数中,this指向new出来的那个新的对象

4、call、apply、bind中的this被强绑定在指定的那个对象上

5、箭头函数中this比较特殊,箭头函数this为父作用域的this,不是调用时的this,要知道前四种方式,都是调用时确定,也就是动态的,而箭头函数的this指向是静态的,声明的时候就确定了下来

6、apply、call、bind都是js给函数内置的一些API,调用他们可以为函数指定this的执行,同时也可以传参

\