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 出场了。

apply 和 call 其实一样,只是传递参数行式是使用数组的方式,而且 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。

而且bind 和 call 其实也一样,只是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对象,你可以使用 call、apply 或 bind。

但如果我们没有用显示绑定的方法,直接调用 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 来自于 User 的 this,只要 User 的 this 不变,箭头函数的 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,使用 bind,call 或者 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. 在linux下配置jupyter notebook,本地浏览器访问

    1.安装jupyter notebook pip install jupyter 2.生成配置文件 jupyter notebook --generate-config 3.设置登陆密码并生成秘钥 s ...

  2. xenserver 添加和卸载硬盘

                最近在浪潮服务器上安了xenserver系统,创建虚拟机,没注意磁盘超负载就重启了服务导致各种坑,一言难尽,忧伤逆流成河啊,所以准备将各种操作整理总结记录下,持续更新ing~~ ...

  3. csp 201409-3 字符串匹配

    问题描述: 很简单,判断给定串是否在目标串中出现.分区分大小写和不区分大小写两种情况. csp特点: csp的第三题一般是字符串的处理,c++的string类虽然不好用,但是掌握的话在考试的时候可能能 ...

  4. python27期day02:while循环、break、格式化、运算符、编码初始、作业题。

    1.while循环:不断的重复着某件事就是循环 2.while循环图解: 3.break:终止当前循环. 4.continue就是跳出本次循环.继续下次循环. 下方代码都不会执行. 改变循环条件来终止 ...

  5. openlayers上添加点击事件

    有很多场景会有这个需求,就是我绘制了图标,点击图标需要展示一些对应的信息 openlayer的事件主要是通过监听来完成的,你所有的icon的点击事件都是可以通过监听map的点击事件来处理对应的逻辑的 ...

  6. 关于c# 中读取系统内存大小的问题。

    在程序中,调用WMI的时候,出现一个问题,就是我系统有插了两条内存条,共4G.然而自己只能在程序中查到安装内存为2G,感觉有点不淡定.这是之前的代码. static ManagementObjectS ...

  7. Postman测试后台使用@RequestBody接收参数的坑

    问题原因:我在使用PostMan测试接口时发现数据传递不过来,是因为请求体定义为JSON数据,自动就传递不过来,虽然问题简单,但由于之前这个用的较少,所以就忽略了这点. 解决问题链接:https:// ...

  8. 使用mysqldump命令进行mysql数据库备份

    1.用法 mysqldump -uroot -p --databases test > /home/test.sql mysqldump -uroot -h127.0.0.1 -p --data ...

  9. Supervisor-守护进程工具

    前言 手头上的服务器程序1.0版本终于要进入线上测试阶段了

  10. 常用HTTP状态码总结

    HTTP状态码(HTTP Status Code) 1xx(临时响应) 表示临时响应并需要请求者继续执行操作的状态代码. 代码 说明 (继续) 请求者应当继续提出请求. 服务器返回此代码表示已收到请求 ...