this的绑定方式基本有以下几种:

  1. 隐式绑定
  2. 显式绑定
  3. new 绑定
  4. window 绑定
  5. 箭头函数绑定

### 隐式绑定

第一个也是最常见的规则称为 隐式绑定

var a = {
str: 'hello',
sayHi() {
console.log(this.str)
}
}
a.sayHi()

a 调用sayHi,所以this指向了对象a

我们来看一个类似但稍微高级点的例子。

var wrapper = {
name: 'user1',
sayName() {
console.log(this.name)
},
inner: {
name: 'user2',
sayName() {
console.log(this.name)
}
}
} wrapper.sayName() //user1
wrapper.inner.sayName() //user2

第一个sayName,指向的是wrapper

第二个sayName,指向的是inner,因为最后调用者是inner

以上就是隐式绑定方式,隐式绑定是最为常见的,因为大部分情况下,方法的调用都会有一个调用对象。

那如果没有呢?我们来看下一条规则。

### 显式绑定

看一下下面这个例子:

function sayName () {
console.log(this.name)
} var user = {
name: 'wyh'
}

这里的sayName 函数不是 user 对象的函数,而是一个独立的函数

为了判断 this 的指向,首先要查看这个函数的调用对象

但是有一个问题,我们怎样能让 sayName 方法调用的时候将 this 指向 user 对象?

这里我们不能再像之前那样,使用 user.sayName(),因为我们的 user 对象里,没有 sayName 方法。

这时候就需要请我们的call出场了。

call 是每个函数都有的一个方法,它允许你在调用函数时为函数指定上下文。

传递给它的第一个参数会作为函数被调用时的上下文。

换句话说,this 将会指向传递给 call 的第一个参数。

利用call的方式调用函数,我们可以在调用 sayName 时,手动把this指向 user 对象,如下:

function sayName () {
console.log(this) //Object { name: "wyh" }
console.log(this.name)
} var user = {
name: 'wyh'
}
sayName.call(user) // wyh

这样我们就通过显示绑定的方式,使它指向了user, 因为我们使用 call这种方式,指定了 this 的指向。

我们稍微修改一下上面的例子:

因为我们上面的方式只能使用user内部的参数,但是使用call还可以为我们传递其他的参数,就像下面这样:

function sayHi(hobby1, hobby2, hobby3) {
console.log(`Hello everyone,my name is ${this.name} ,and I like ${hobby1}、 ${hobby2}、${hobby3}`) //注意只有name属于user,需要用this
} var user = {
name: 'cxk'
} var hobbies = ['sing', 'dance', 'basketball'] sayHi.call(user, hobbies[0], hobbies[1], hobbies[2]) //在user后面依次传递参数,用逗号间隔
// Hello everyone, my name is cxk,and I like sing、 dance、basketball

但是我们需要一个一个的传递 hobbies 数组的元素,有点麻烦,所以这时候需要我们的apply 出场了。

applycall 其实一样,只是传递参数行式是使用数组的方式,而且 apply 会在函数中为你自动进行展开。

那么现在使用 apply,修改一下上面的代码:

var hobbies = ['sing', 'dance', 'basketball']

// sayHi.call(user, hobbies[0], hobbies[1], hobbies[2])
sayHi.apply(user, hobbies)
// Hello everyone, my name is cxk,and I like sing、 dance、basketball

以上我们知道了 `call` 和 `apply` 的显式绑定规则,可以自己手动指定 `this` 的指向。

其实我们还有一个手动指定this的方法,它叫 bind

而且bindcall 其实也一样,只是bind不会立刻调用函数,而是返回一个能在以后调用的新函数。

我们使用 bind,修改一下上面的例子:

function sayHi(hobby1, hobby2, hobby3) {
console.log(this); //Object { name: "cxk" }
console.log(`Hello everyone,my name is ${this.name} ,and I like ${hobby1}、 ${hobby2}、${hobby3}`)
} var user = {
name: 'cxk'
} var hobbies = ['sing', 'dance', 'basketball'] var newFn = sayHi.bind(user, hobbies[0], hobbies[1], hobbies[2])
newFn()
// Hello everyone, my name is cxk,and I like sing、 dance、basketball

以上就是显示绑定的三种方式了,接下来我们看一下new绑定

### new 绑定

在 js 中,为了实现类,我们需要定义一些构造函数,在调用一个构造函数的时候需要加上 new 这个关键字:

function Person(name) {
this.name = name;
console.log(this); // Object { name: "wyh" }
} var p = new Person('wyh'); //使用new

当作构造函数调用时,this 指向了这个构造函数调用时候,实例化出来的对象;

当然,构造函数其实也是一个函数,如果我们把它当作一个普通函数执行,这个 `this` 仍然执行全局:

function Person(name) {
this.name = name;
console.log(this); // Window
} var p = Person('wyh'); //不使用new

其区别在于,如何调用函数(new)。

### window 绑定

还是之前的这段代码:

function sayName () {
console.log(this.name)
} var user = {
name: 'wyh'
}

上面我们说过,如果想让sayName方法指向user对象,你可以使用 callapplybind

但如果我们没有用显示绑定的方法,直接调用 sayName 结果会是什么呢?

function sayHello() {
console.log(this.str)
} var user = {
//注意这里不要使用name作为属性名,因为在window对象下有一个name属性
str: 'hello'
}
sayHello() // undefined

我们得到了undefined。这是因为这里既没有隐式绑定,也没有显示绑定或者 new 关键字

所以this指向了window对象,但在window中我们并没有str属性,所以得到的是undefined

这意味着如果我们向 window 对象添加 str 属性并再次调用 sayHello 方法,this.str 将不再是 undefined ,而是变成window对象的 str 属性值。

window.str = 'world';

function sayHello() {
console.log(this.str)
}
sayHello() // world

这就是 window 绑定 。如果其它规则都没满足,JavaScript就会默认 this 指向 window 对象。

在 ES5 添加的 严格模式 中,JavaScript 不会默认 this 指向 window 对象,而会正确地把 this 保持为 undefined。

'use strict'

window.str = 'world';

function sayHello() {
console.log(this.str)
}
sayHello() // TypeError: this is undefined

### 箭头函数绑定

在上面的函数中,this 有各种各样的指向(隐式绑定,显示绑定,new 绑定, window 绑定......)

虽然灵活方便,但由于不能在定义函数时知道指向,直到实际调用时才能知道 this 指向

假如我们有下面这段代码

function User() {
this.name = 'wyh'; setTimeout(function sayName() {
console.log(this.name); // wyh
console.log(this); // window
}, 1000);
} var user = new User();

sayName 里的 this 可以由上面的四个规则判断出来。对,因为没有显示绑定、隐式绑定或 new 绑定、所以直接得出结论 this 指向 window

但实际上我们想把 this 指向 user 对象

以前是怎么解决的呢?看下面的代码:

1. 使用闭包

闭包就是指有权访问另一个函数作用域中的变量的函数。

function User() {					//这里的User函数是一个闭包
this.name = 'wyh';
const _this = this; setTimeout(function sayName() {
console.log(_this.name); // wyh
console.log(_this); // Object { name: "wyh" }
}, 1000);
} var user = new User();

**2. 使用显示绑定 — `bind`**

function User() {
this.name = 'wyh'; setTimeout(function sayName() {
console.log(this.name); // wyh
console.log(this); // User { name: "wyh" }
}.bind(this)(), 1000);
} var user = new User();

上面的方法都可以解决问题,但是都要额外写冗余的代码来指定 this

ES6 引入箭头函数(Arrow Function) 来解决这个问题的,它可以轻松地让 sayName 函数保持 this 指向 user 对象。

下面是箭头函数版本:

function User() {
this.name = 'wyh'; setTimeout(() => {
console.log(this.name); // wyh
console.log(this); // User { name: "wyh" }
}, 1000);
} var user = new User();

通过直接把普通函数改成箭头函数就能解决问题

箭头函数在自己的作用域内不绑定 this,即没有自己的 this

如果要使用 this ,就会指向定义时所在的作用域的 this 值,在上面这个例子中,箭头函数内的 this 指向定义这个箭头函数时作用域内的 this,也就是 User中的 this,而 User 函数通过 new 绑定,所以 this 实际指向 user 对象。

那在严格模式下会有影响吗?

function User() {
this.name = 'wyh'; setTimeout(() => {
'use strict'
console.log(this.name); // wyh
console.log(this); // User { name: "wyh" }
}, 1000);
} var user = new User();

不会有影响,因为箭头函数没有自己的 this,它的 this 来自于 Userthis只要 Userthis 不变,箭头函数的 this 也保持不变

那么使用 `bind`,`call` 或者 `apply` 呢?

function User() {
this.name = 'wyh'; setTimeout((() => {
console.log(this.name); // wyh
console.log(this); // User { name: "wyh" }
}).bind('no body'), 1000);
} var user = new User();

答案还是没有影响。因为箭头函数没有自己的 this,使用 bindcall 或者 apply 时,箭头函数会自动忽略掉 bind 的第一个参数,即 thisArg

### 总结

基本上可以通过以下几点判断this的具体指向:

  • 查看函数在哪被调用
  • 有没有对象调用?如果有,就指向最后调用的对象
  • 该函数是不是用 “call”、“apply” 或者 “bind” 调用的?如果是,它会显式地指明 “this” 的引用。
  • 该函数是不是用 “new” 调用的?如果是,“this” 指向的就是新创建的对象
  • 是否使用了闭包
  • 是否存在箭头后函数
  • 非严格模式下,如果没有其他规则,“this” 会指向 “window” 对象
  • 严格模式下,没有其他规则,“this” 指向undefined

js中this绑定方式及如何改变this指向的更多相关文章

  1. JS中事件绑定的三种方式

    以下是搜集的在JS中事件绑定的三种方式.   1. HTML onclick attribute     <button type="button" id="upl ...

  2. js中事件绑定要注意的事项之如何在方法中自己打印自己的值

    下面是错误的js方法绑定,这样写会造成在方法中不能用 调用方法的dom本身的一些 东西,如各种属性或者jq对象等. <!DOCTYPE html> <html> <hea ...

  3. vue.js 中双向绑定的实现---初级

    1. 1 我们看到的变量,其实都不是独立的,它们都是windows对象上的属性 <!DOCTYPE html> <html lang="en"> <h ...

  4. JS中类型检测方式

    在js中的类型检测目前我所知道的是三种方式,分别有它们的应用场景: 1.typeof:主要用于检测基本类型. typeof undefined;//=> undefined typeof 'a' ...

  5. JS中对象继承方式

    JS对象继承方式 摘自<JavaScript的对象继承方式,有几种写法>,作者:peakedness 链接:https://my.oschina.net/u/3970421/blog/28 ...

  6. JS中循环绑定遇到的问题及解决方法

    本文是原创文章,如需转载,请注明文章出处 在工作中,有时会有这样的需求:在一个页面上添加了6个按钮,然后分别为他们绑定点击事件监听器,当点击按钮1时,输出1,当点击按钮2时,输出2. 循环绑定代码如下 ...

  7. js中拼接HTML方式方法及注意事项

    博主原创:未经博主允许,不得转载 在前端应用中,经常需要在js中动态拼接HTML页面,比如应用ajax进行局部刷新的时候,就需要在js中拼接HTML页面. 主要规则是将HTML页面的标签拼接为标签字符 ...

  8. JS中事件绑定函数,事件捕获,事件冒泡

    1 事件绑定:事件与函数绑定以及怎么取消绑定 1.1 元素.onclick这种形式,如下: <div id="div1">aaa</div> <scr ...

  9. js中常见继承方式

    1.原型模式 function Father(){ this.property = true; } Father.prototype.getValue = function(){ return thi ...

随机推荐

  1. torchline:让Pytorch使用的更加顺滑

    torchline地址:https://github.com/marsggbo/torchline 相信大家平时在使用Pytorch搭建网络时,多少还是会觉得繁琐,因为我们需要搭建数据读取,模型,训练 ...

  2. [原创]python+beautifulsoup爬取整个网站的仓库列表与仓库详情

    from bs4 import BeautifulSoup import requests import os def getdepotdetailcontent(title,url):#爬取每个仓库 ...

  3. 解决chrome连接自建https服务器报“您的连接不是私密连接”问题

    前一段时间,Chrome 突然显示出了“您的连接不是私密连接”,这下可难受了,大部分的网站打开都有问题. 找了各种方法,各种设置都是不行. 一.暴力.费力的方法直接卸载 Chrome ,删除一切数据以 ...

  4. SpringBoot注解分析解释

    使用注解的优势: 1.采用纯java代码,不在需要配置繁杂的xml文件 2.在配置中也可享受面向对象带来的好处 3.类型安全对重构可以提供良好的支持 4.减少复杂配置文件的同时亦能享受到springI ...

  5. 使用lombok的利弊

    使用lombok的好处是:1.减少大量的模板代码,get和set方法,从代码封装维度看,将大量的模板代码进行封装,不需要其他人员来不断编写,哪怕是IDE可以生成的代码,这也是重复代码,减少重复的出现; ...

  6. 日常笔记3关于bool类型数组初始化的问题

    一般会有两种考虑,全为true或全为false 赋值方式: <1>memset(boolArray,0,sizeof(Array)); 头文件:#include<cstring> ...

  7. CF1178F Short/Long Colorful Strip(DP)

    说起来,这题好像也不难-- 先考虑 F1 怎么做. 既然别的方法都不行不如试试\(f_{i,j}\) 表示在刚刚准备开始涂 \([i,j]\) 中最小编号的颜色之前,整个区间是同色的,且最后能做到 \ ...

  8. [LeetCode] 82. Remove Duplicates from Sorted List II 移除有序链表中的重复项之二

    Given a sorted linked list, delete all nodes that have duplicate numbers, leaving only distinct numb ...

  9. Ubuntu安装支持PCL、LAS的CloudCompare

    git clone --recursive https://github.com/cloudcompare/trunk.git cd trunk mkdir build cd build cmake ...

  10. 代码移植的福音 namespace_alias

    命名空间别名 允许程序员定义命名空间的另一个名字 它们常用作长的或嵌套过深的命名空间的简便使用方式. 我们也可以将用在代码移植上,而无需修改源代码的文件所定义的命名空间, 为后面升级merge代码创造 ...