js中this绑定方式及如何改变this指向
this的绑定方式基本有以下几种:
- 隐式绑定
- 显式绑定
- new 绑定
- window 绑定
- 箭头函数绑定
### 隐式绑定
第一个也是最常见的规则称为 隐式绑定。
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指向的更多相关文章
- JS中事件绑定的三种方式
以下是搜集的在JS中事件绑定的三种方式. 1. HTML onclick attribute <button type="button" id="upl ...
- js中事件绑定要注意的事项之如何在方法中自己打印自己的值
下面是错误的js方法绑定,这样写会造成在方法中不能用 调用方法的dom本身的一些 东西,如各种属性或者jq对象等. <!DOCTYPE html> <html> <hea ...
- vue.js 中双向绑定的实现---初级
1. 1 我们看到的变量,其实都不是独立的,它们都是windows对象上的属性 <!DOCTYPE html> <html lang="en"> <h ...
- JS中类型检测方式
在js中的类型检测目前我所知道的是三种方式,分别有它们的应用场景: 1.typeof:主要用于检测基本类型. typeof undefined;//=> undefined typeof 'a' ...
- JS中对象继承方式
JS对象继承方式 摘自<JavaScript的对象继承方式,有几种写法>,作者:peakedness 链接:https://my.oschina.net/u/3970421/blog/28 ...
- JS中循环绑定遇到的问题及解决方法
本文是原创文章,如需转载,请注明文章出处 在工作中,有时会有这样的需求:在一个页面上添加了6个按钮,然后分别为他们绑定点击事件监听器,当点击按钮1时,输出1,当点击按钮2时,输出2. 循环绑定代码如下 ...
- js中拼接HTML方式方法及注意事项
博主原创:未经博主允许,不得转载 在前端应用中,经常需要在js中动态拼接HTML页面,比如应用ajax进行局部刷新的时候,就需要在js中拼接HTML页面. 主要规则是将HTML页面的标签拼接为标签字符 ...
- JS中事件绑定函数,事件捕获,事件冒泡
1 事件绑定:事件与函数绑定以及怎么取消绑定 1.1 元素.onclick这种形式,如下: <div id="div1">aaa</div> <scr ...
- js中常见继承方式
1.原型模式 function Father(){ this.property = true; } Father.prototype.getValue = function(){ return thi ...
随机推荐
- vue router 导航守卫生命周期
导航守卫 导航守卫主要用来通过跳转或取消的方式守卫导航.有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的.(记住参数或查询的改变并不会触发进入/离开的导航守卫.你可以通过观察$r ...
- 生产者和消费者模型producer and consumer(单线程下实现高并发)
#1.生产者和消费者模型producer and consumer modelimport timedef producer(): ret = [] for i in range(2): time.s ...
- zzTensorflow技术内幕:
性能优势 TensorFlow在大规模分布式系统上的并行效率相当高,如下图所示: 图5:TensorFlow并发效率 在GPU数量小于16时,基本没有性能损耗,在50块的时候,可以获得80%的效率,也 ...
- MySQL中连接超时自动断开的解决方案
前言: MySQL数据库一般默认的连接超时时间为3600s(1小时),但是在进行大规模的线程事务操作时,一个连接会一直等待执行,这时候如果数据库的超时时间设置的过短,就可能会出现Mysql数据连接自动 ...
- O(n log n)求最长上升子序列与最长不下降子序列
考虑dp(i)表示新上升子序列第i位数值的最小值.由于dp数组是单调的,所以对于每一个数,我们可以二分出它在dp数组中的位置,然后更新就可以了,最终的答案就是dp数组中第一个出现正无穷的位置. 代码非 ...
- Layui的一些心得
做的项目中用到了前端框架Layui 对于layui,经常犯的错与走的弯路,做下总结: 首先要引用Layui框架罗! 1. TopLayerClose(); 关闭当前页面 2. TopLayerIn ...
- 使用async-profiler简单分析zeebe 工作流引擎的性能
刚开始的时候直接使用的系统暴露的prometheus metrics,发现越高的版本反而性能越差,期间使用过了 perf 打算使用perf 生成火焰图的,但是因为符号缺失,只找到了占用较高的任务,详细 ...
- 交换机端口与Mac地址绑定(基于Cisco模拟器)
实验设备: 二层交换机一台,主机三台 实验步骤: 1.进入相应的接口 (以端口1设置Mac地址绑定,PC0接1端口举例) Switch>enable Switch#config Configur ...
- 数据结构——顺序栈(sequence stack)
/* sequenceStack.c */ /* 栈 先进后出(First In Last Out,FILO)*/ #include <stdio.h> #include <stdl ...
- ASP.NET Core 中的 Main 方法
ASP.NET Core 中的 Main 方法 在 ASP.NET Core 项目中,我们有一个名为Program.cs的文件.在这个文件中,我们有一个public static void Main( ...