彻底弄懂js中this指向(包含js绑定、优先级、面试题详解)
为什么要使用this
在javascript中,this可谓是无处不在,它可以用来指向某些元素、对象,在合适的地方使用this,能让我们减少无用代码的编写
var user = {
name: "aclie",
sing: function () {
console.log(user.name + '在唱歌')
},
dance: function () {
console.log(user.name + '在跳舞')
},
study: function () {
console.log(user.name + '在学习')
},
}
以上这段代码中,每个方法都需要用到user对象中的name属性,如果当user对象名称发生变化,那么所有方法都要改动,这种情况下,使用this是个很好的选择
var user = {
name: "aclie",
sing: function () {
console.log(this.name + '在唱歌')
},
dance: function () {
console.log(this.name + '在跳舞')
},
study: function () {
console.log(this.name + '在学习')
},
}
this的指向
this的指向和函数在哪里定义无关,和如何调用有关
以下foo函数调用方式不同,this的值也不同
function foo(){
console.log(this)
}
foo()
var obj = {
foo: foo
}
obj.foo()
obj.foo.apply("hello")
执行结果如下图所示

this四种绑定方式
一、默认绑定
当函数独立调用时,this默认绑定window
// 1、直接调用
function foo() {
console.log(this)
}
foo()
// 2、对象中的函数
var obj1 = {
foo: foo
}
var fn1 = obj1.foo
fn1()
// 3、被全局变量引用
var obj2 = {
bar: function () {
console.log(this)
}
}
var fn2 = obj2.bar
fn2()
// 4、函数嵌套调用
function foo1() {
console.log('foo1', this)
}
function foo2() {
console.log('foo2', this)
foo1()
}
function foo3() {
console.log('foo3', this)
foo2()
}
foo3()
// 5、通过闭包调用
var obj2 = {
bar: function () {
return function () {
console.log(this)
}
}
}
obj2.bar()()
执行结果如下

以上五种调用方式全都属于默认绑定,因为他们最终都是单独的对函数进行调用
二、隐式绑定
调用的对象内部有对函数的引用
function foo() {
console.log(this)
}
var obj1 = {
name: 'obj1',
foo: foo
}
obj1.foo()
var obj2 = {
name: 'obj2',
bar: function () {
console.log(this)
}
}
obj2.bar()
var obj3 = {
name: 'obj3',
baz: obj2.bar
}
obj3.baz()
以上代码执行结果为

以上三种都属于隐式绑定,他们都是通过对象调用,this就指向了该对象
三、显式绑定
不希望在对象内部包含这个函数的引用,但又希望通过对象强制调用,使用call/apply/bind进行显式绑定
function foo() {
console.log(this)
}
var obj = {
name: 'obj1',
}
foo.call(obj)
foo.apply(obj)
foo.call("xxx")
以上代码的执行结果为

foo函数直接调用this应该指向window,这里通过call/apply来改变了this的指向
四、new绑定
通过new关键字来创建构造函数的实例,绑定this
function Person(name, age) {
this.name = name
this.age = age
}
const p1 = new Person('alice', 20)
const p2 = new Person('mogan', 24)
console.log(p1)
console.log(p2)
以上代码的执行结果如下

此时this指向的是通过new创建的实例对象
this绑定的优先级
一、隐式绑定高于默认绑定
function foo() {
console.log(this)
}
var obj = {
name: 'obj',
foo: foo
}
obj.foo()
以上代码执行结果为

foo函数默认绑定window对象,当同时存在隐式绑定和默认绑定时,隐式绑定优先级高于默认绑定
二、显示绑定高于隐式绑定
// 案例一
var user = {
name: 'user',
foo: function(){
console.log(this)
}
}
user.foo.call('kiki')
// 案例二
function foo() {
console.log(this)
}
var obj = {
name: "obj",
foo: foo.bind("aclie")
}
obj.foo()
以上代码的执行结果为

如果隐式绑定优先级更高的话,this的指向应该都为对象,但根据以上执行结果得知this绑定为显示绑定的结果,所以当同时存在隐式绑定和显示绑定时,显示绑定的优先级高于隐式绑定
三、new高于隐式绑定
var user = {
name: 'lisa',
foo: function () {
console.log(this)
}
}
new user.foo()
以上代码的执行结果如下

当同时存在于new关键字绑定和隐式绑定时,this绑定了foo构造函数,所以new关键字的优先级高于隐式绑定
四、new高于显示绑定
function bar(){
console.log(this)
}
var fn = bar.bind('hello')
new fn()
以上代码的执行结果如下

当同时存在于new关键字绑定和显示绑定时,this绑定了bar构造函数,所以new关键字的优先级高于显示绑定
综上,以上四种绑定的优先级顺序为
new关键字 > 显式绑定 > 隐式绑定 > 默认绑定
规则之外
还有几种特殊的绑定方式,不在上述四种绑定规则中
一、忽略显示绑定
当显示绑定的值为 null/undefined 时,this直接绑定window
var user = {
name: 'alice',
foo: function () {
console.log(this)
}
}
user.foo()
user.foo.call(null)
user.foo.apply(undefined)
以上代码执行结果如下

二、间接函数引用
var obj1 = {
name: 'obj1',
foo: function () {
console.log(this)
}
}
var obj2 = {
name: 'obj2'
};
obj2.baz = obj1.foo;
obj2.baz();
(obj2.bar = obj1.foo)()
以上代码的执行结果为

两种方式所绑定的this不同,第二种方式进行了赋值调用,实际上是间接函数引用,(obj2.bar = obj1.foo)这里返回了赋值的结果,再加上一个小括号,就直接调用赋值的结果函数
三、箭头函数
箭头函数是不绑定this的,它的this来源于上级作用域
var user = {
name: 'kiki',
foo: () => {
console.log('箭头函数中的this',this)
}
}
user.foo()
以上代码的执行结果如下

这里调用foo函数,因为箭头函数不绑定this,所以去foo函数的上级查找this,找到了全局对象window
面试题
1、考察间接函数引用
var name = "window";
var person = {
name: "person",
sayName: function () {
console.log(this.name);
}
};
function sayName() {
var sss = person.sayName;
sss();
person.sayName();
(person.sayName)();
(b = person.sayName)();
}
sayName();
执行sayName函数
- 变量sss 被person.sayName方法赋值,执行sss函数,此时是独立函数调用,this指向全局window,全局中变量name被绑定到了window中,所以this.name为"window"
- person.sayName() 为隐式绑定,this指向person对象,所以this.name为person.name,即"person"
- (person.sayName)() 与前一个本质是一样的,隐式绑定,this指向person对象,所以this.name为person.name,即"person"
- (b = person.sayName)() 是间接函数引用,person.sayName赋值给b变量,而小括号括起来的代表赋值的结果,this指向window,this.name为window.name,即"window"
所以执行结果为

2、定义对象时是不产生作用域的
var name = 'window'
var person1 = {
name: 'person1',
foo1: function () {
console.log(this.name)
},
foo2: () => console.log(this.name),
foo3: function () {
return function () {
console.log(this.name)
}
},
foo4: function () {
return () => {
console.log(this.name)
}
}
}
var person2 = { name: 'person2' }
person1.foo1();
person1.foo1.call(person2);
person1.foo2();
person1.foo2.call(person2);
person1.foo3()();
person1.foo3.call(person2)();
person1.foo3().call(person2);
person1.foo4()();
person1.foo4.call(person2)();
person1.foo4().call(person2);
调用过程分析
foo1函数
- person1.foo1() 隐式绑定,this指向person1,this.name为person1.name,即 "person1"
- person1.foo1.call(person2) 隐式绑定+显示绑定person2,显示绑定优先级更高,所以this指向person2,this.name为person2.name,即 "person2"
foo2函数
- person1.foo2() 隐式绑定, 箭头函数没有自己的this,所以向上层作用域查找,找到了全局window(person1是对象,定义它的时候不产生作用域),全局变量name被绑定到了window中,this.name为window.name,即 "window"
- person1.foo2.call(person) 隐式绑定+显示绑定,但是 箭头函数不绑定this,这里的显示绑定无效,没有自己的this,向上层作用域查找,找到全局window,this.name为window.name,即 "window"
foo3函数
- person1.foo3()() 这里相当于执行person1.foo()的返回函数,这里是独立函数调用,this指向全局window,this.name为window.name,即 "window"
- person1.foo3.call(person2)() 这里通过call改变的是foo3函数中this的指向,但最终执行的是foo3函数返回的闭包,闭包作为独立函数调用,this仍然指向全局window,this.name为window.name,即'window"
- person1.foo3().call(person2) 这里将foo3函数返回的闭包显示绑定了person2对象,this指向person2,this.name为person2.name,即"person2"
foo4函数
- person1.foo4()() 执行person1.foo()的返回值,返回的闭包是箭头函数没有this的,向上层作用域查找,找到了foo4函数,foo4的this指向person1,所以闭包的this也指向person1,thiss.name为person1.name,即 "person1"
- person1.foo4.call(person2)() 返回的闭包没有this,向上层作用域找到了foo4函数,foo4函数的this通过显示绑定变成了person2,所以闭包的this也指向person2,this.name为person2.name,即"person2"
- person1.foo4().call(person) 返回的闭包是箭头函数,无法通过call进行显示绑定,直接向上级作用域查找,找到foo4函数,foo4的this指向person1,所以闭包的this指向person1,this.name为person1.name,即"person1"
上述代码的执行结果如下

3、构造函数中定义函数,该函数的上级作用域是构造函数
var name = 'window'
function Person (name) {
this.name = name
this.foo1 = function () {
console.log(this.name)
},
this.foo2 = () => console.log(this.name),
this.foo3 = function () {
return function () {
console.log(this.name)
}
},
this.foo4 = function () {
return () => {
console.log(this.name)
}
}
}
var person1 = new Person('person1')
var person2 = new Person('person2')
person1.foo1()
person1.foo1.call(person2)
person1.foo2()
person1.foo2.call(person2)
person1.foo3()()
person1.foo3.call(person2)()
person1.foo3().call(person2)
person1.foo4()()
person1.foo4.call(person2)()
person1.foo4().call(person2)
调用分析过程
foo1函数
- person1.foo1() 隐式绑定,this指向person1,person1创建实例时传入name为person1,所以this.name为person1
- person1.foo1.call(person2) 隐式绑定+显示绑定,显示绑定优先级更高,绑定person2,person2创建实例时传入的name为person2,所以this.name为person2
foo2函数
- person1.foo2() 隐式绑定,但foo2是箭头函数,没有自己的this,向上层作用域查找,找到了Person构造函数,此时this是指向person1这个对象的,而person1实例化时传入的name为person1,所以this.name为person1
- person1.foo2.call(person2) 隐式绑定+显式绑定,但foo2是箭头函数,不绑定this,所以this仍然需要向上层作用域查找,找到Person构造函数,this指向person1对象,所以this.name为person1
foo3函数
- person1.foo3()() 执行person1.foo3的返回值,返回的函数是独立调用,this指向window,全局的name变量被绑定到window中,this.name为window.name,即 "window"
- person1.foo3.call(person2)() 显式绑定更改的是foo3函数的this,最终执行的是foo3函数的返回值,仍然是函数的独立调用,所以this指向window,this.name为window.name,即 "window"
- person1.foo3().call(person2) foo3函数的返回函数通过显示绑定将this绑定到了person2中,person2创建实例时传入的name为person2,所以this.name为person2
foo4函数
- person1.foo4()() 执行foo4函数的返回值,返回函数为箭头函数,没有this,所以向上层作用域查找,找到foo4函数的this指向person1,所以箭头函数的this也指向person1,所以this.name为person1
- person1.foo4.call(person2)() foo4通过显示绑定将this绑定成了person2,返回的函数为箭头函数,this与父级作用域foo4一致,所以箭头函数的this也指向person2,所以this.name为person2
- person1.foo4().call(person2) foo4函数的返回值为箭头函数,不绑定this,这里显示绑定无效,向上级作用域查找this,找到foo4函数,this指向person1
执行结果如下

4、区分作用域
var name = 'window'
function Person (name) {
this.name = name
this.obj = {
name: 'obj',
foo1: function () {
return function () {
console.log(this.name)
}
},
foo2: function () {
return () => {
console.log(this.name)
}
}
}
}
var person1 = new Person('person1')
var person2 = new Person('person2')
person1.obj.foo1()()
person1.obj.foo1.call(person2)()
person1.obj.foo1().call(person2)
person1.obj.foo2()()
person1.obj.foo2.call(person2)()
person1.obj.foo2().call(person2)
foo1函数
- person1.obj.foo1()() 执行foo1函数的返回函数,此时该函数为独立函数调用,this指向window,全局变量name被添加到window中,这里的this.name指向window.name,即 "window"
- person1.obj.foo1.call(person2)() 这里显示绑定改变foo1中this的指向,但最终执行的是foo1函数的返回值,返回函数作为独立函数调用,this仍然指向window,所以this.name为window.name,即 "window"
- person1.obj.foo1().call(person2) 这里通过显示绑定更改foo1函数的返回函数中this的指向, 所以该函数this指向person2,而person2在实例化的时候传入name值为person2,所以this.name为person2
foo2函数
- person1.obj.foo2()() 执行foo2的返回函数,此时该函数为独立函数调用,但它自己没有this,要向上级作用域查找,找到foo2函数的this指向obj,所以该函数的this也指向obj,this.name为obj.name,即 "obj"
- person1.obj.foo2.call(person2)() 执行foo2的返回函数,此时该函数为独立函数调用,但它自己没有this,要向上级作用域查找,foo2函数的this通过显示绑定变成person2,所以该函数的this也为person2,而person2在实例化的时候传入name值为person2,所以this.name为person2
- person1.obj.foo2().call(person2) foo2的返回函数为箭头函数,不绑定this,显式绑定无效,也没有自己的this,要向上级作用域查找,找到foo2函数的this指向obj,所以该函数的this也指向obj,this.name为obj.name,即 "obj"
所以执行结果为

以上就是关于this指向的理解,关于js高级,还有很多需要开发者掌握的地方,可以看看我写的其他博文,持续更新中~
彻底弄懂js中this指向(包含js绑定、优先级、面试题详解)的更多相关文章
- [转]js中几种实用的跨域方法原理详解
转自:js中几种实用的跨域方法原理详解 - 无双 - 博客园 // // 这里说的js跨域是指通过js在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,或者通过js获取页面中不同 ...
- js中几种实用的跨域方法原理详解(转)
今天研究js跨域问题的时候发现一篇好博,非常详细地讲解了js几种跨域方法的原理,特分享一下. 原博地址:http://www.cnblogs.com/2050/p/3191744.html 下面正文开 ...
- js中几种实用的跨域方法原理详解
这里说的js跨域是指通过js在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,或者通过js获取页面中不同域的框架中(iframe)的数据.只要协议.域名.端口有任何一个不同,都被 ...
- js中几种实用的跨域方法原理详解【转】
源地址:http://www.cnblogs.com/2050/p/3191744.html 这里说的js跨域是指通过js在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,或者通 ...
- 【转】js中几种实用的跨域方法原理详解
这里说的js跨域是指通过js在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,或者通过js获取页面中不同域的框架中(iframe)的数据.只要协议.域名.端口有任何一个不同,都被 ...
- js中 call() 和 apply() 方法的区别和用法详解
1.定义 每个函数都包含俩个非继承而来的方法:call() 和 apply() call 和 apply 可以用来重新定义函数的的执行环境,也就是 this 的指向:call 和 apply 都是 ...
- 关于js中this指向的理解总结!
关于js中this指向的理解! this是什么?定义:this是包含它的函数作为方法被调用时所属的对象. 首先,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁 ...
- 一文弄懂神经网络中的反向传播法——BackPropagation【转】
本文转载自:https://www.cnblogs.com/charlotte77/p/5629865.html 一文弄懂神经网络中的反向传播法——BackPropagation 最近在看深度学习 ...
- 前端js中this指向及改变this指向的方法
js中this指向是一个难点,花了很长时间来整理和学习相关的知识点. 一. this this是JS中的关键字, 它始终指向了一个对象, this是一个指针; 参考博文: JavaScript函数中的 ...
- js中this指向的三种情况
js中this指向的几种情况一.全局作用域或者普通函数自执行中this指向全局对象window,普通函数的自执行会进行预编译,然后预编译this的指向是window //全局作用域 console.l ...
随机推荐
- Prism Sample 18-NavigationCallback
同17相比,在导航方法中增加了回调函数 private void Navigate(string navigatePath) { if (navigatePath != null) _regionMa ...
- 2022-04-05:golang中go.mod文件,做框架开发需要解析,请问如何解析?
2022-04-05:golang中go.mod文件,做框架开发需要解析,请问如何解析? 答案2022-04-05: go.mod解析可参考这个.做框架开发主要是用到require和replace.具 ...
- 2021-04-18:给定一个二维数组matrix,里面的值不是1就是0,上、下、左、右相邻的1认为是一片岛,返回matrix中岛的数量。
2021-04-18:给定一个二维数组matrix,里面的值不是1就是0,上.下.左.右相邻的1认为是一片岛,返回matrix中岛的数量. 福大大 答案2021-04-18: 并查集. 代码用gola ...
- 2021-12-27:给定一个字符串str,和一个正数k, str子序列的字符种数必须是k种,返回有多少子序列满足这个条件。 已知str中都是小写字母, 原始是取mod, 本节在尝试上,最难的, 搞出
2021-12-27:给定一个字符串str,和一个正数k, str子序列的字符种数必须是k种,返回有多少子序列满足这个条件. 已知str中都是小写字母, 原始是取mod, 本节在尝试上,最难的, 搞出 ...
- 2021-12-15: 路径总和 III。给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。路径 不需要从根节点开
2021-12-15: 路径总和 III.给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目.路径 不需要从根节点开 ...
- 解决:Error: [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试。
启动django应用时报如下错误:Error: [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试. 1.首先退出酷狗音乐再试试 2.是8000端口被其他程序占用了, ...
- DataGridViewImageColumn 图片照片
Private Sub BT_PHOTOADDRESS_Click(sender As Object, e As EventArgs) Handles BT_PHOTOADDRESS.Click Di ...
- 神经网络初步(Neural Network)——思想 具体实例以及代码实现
在前面我们详细的讨论过softmax损失函数以及SVM损失函数,以及应用了支持向量机进行图片分类的任务,不妨先复习一下支持向量机相关的思想内核:支持向量机想要寻求一组映射关系f(x)=wx+b,先将每 ...
- C#/VB.NET:如何从 PowerPoint 演示文稿中提取文本
在学习或者日常工作中,有时我们需要把幻灯片的东西整理成文字,而从 PowerPoint 演示文稿中一张一张的整理手动复制粘贴,整个过程会非常费精力且耗时.那么怎么样才能比较轻松且快速地提取PowerP ...
- iOS中容易用错的常用知识点
坐标系转换 ios中的坐标系有三种 视图坐标系:原点(0,0)视图的左上角 窗口坐标系:原点(0,0)窗口的左上角 世界坐标系:原点(0,0)游戏中世界的原点 平时开发中经常会遇到转UIWindow坐 ...