一文全面解析JS中的this绑定规则
作者:一溪风月
大家好,我是一名喜欢摸鱼的前端程序员,写过JavaScript的都知道,JS中的this
相对来讲是比较难以捉摸的,尤其在一些复杂的场景下的指向总是让人摸不着头脑,所以这篇文章我们就来系统的学习和研究一下this
的绑定规则,并且会在文章的最后列出了四道关于this
绑定的题目,有兴趣的小伙伴可以自己试着做一下,相信在你耐心看完这些内容之后,你会对this
有一个更加全面且深刻的理解。
一.直接调用(独立函数调用/全局调用)
直接调用顾名思义就是直接进行函数的调用,怎么直接调用哪? 我们常见的直接调用分为两种情况,如下:
function foo () { console.log(this) } //进行函数调用 foo()
这是最常见的一种情况,这种情况this在非严格模式的情况下指向的是全局对象,这种调用方式,我们称之为独立函数调用,或者称之为全局调用。
还有另外一种情况是在对象中定义,然后赋值给一个变量,然后单独对这个赋值后的函数进行调用,依然是独立函数调用或者称之为全局调用,非严格模式下指向的是window。
let obj = { name: "zpj", foo: function () { console.log(this) } } let bar = obj.foo bar()
这种写法,依然属于独立函数调用,指向的依然是window或者说全局对象。
注意:独立函数调用(全局调用)this的指向在严格模式下并不指向window而是指向undefined所以我们在使用的时候千万不要使用this来代替window,而是直接使用window。
二.对象绑定
通过对象绑定比较容易理解,因为我们在平时会经常用到,当我们在如下的这种方式进行的时候会指向调用它的对象obj;
let obj={ name:"aaa", foo:function(){ console.log(this); } } obj.foo() // { name: 'aaa', foo: [Function: foo] }
三.new绑定
在讲解new绑定之前,我们先来看下当我们在new一个对象的时候到底做了什么事情。
- 创建新的空对象。
- 将this指向这个空对象。
- 指向函数体中的代码。
- 没有显示返回空对象的时候默认返回这个对象。
从第二步我们知道,当我们进行new操作的时候会将this绑定到实例化的这个对象上面。
function foo (name) { this.name = name console.log(this.name) } let bar = new foo("zzz") console.log(bar) // zzz // foo { name: 'zzz' }
通过上述的代码我们可以看到当我们进行对象实例化的时候函数内部的this指向的是实例化的这个对象。
四.this指向绑定事件的元素
<ul id="color-list"> <li>red</li> <li>yellow</li> <li>blue</li> <li>green</li> <li>black</li> <li>white</li> </ul>
// this 是绑定事件的元素 // target 是触发事件的元素 和 srcElememnt 等价 let colorList = document.getElementById("color-list"); colorList.addEventListener("click", function (event) { console.log('this:', this); console.log('target:', event.target); console.log('srcElement:', event.srcElement); })
有些时候我们会遇到一些困扰,比如在div节点的事件函数内部,有一个局部的 callback 方法,该方法被作为普通函数调用时,callback 内部的this是指向全局对象 window的.
<div id="div1">我是一个div</div>
window.id = 'window'; document.getElementById('div1').onclick = function(){ console.log(this.id); // div1 const callback = function(){ console.log(this.id); // 因为是普通函数调用,所以 this 指向 window } callback(); }
此时有一种简单的解决方案,可以用一个变量保存 div节点的引用,如下:
window.id = 'window'; document.getElementById('div1').onclick = function(){ console.log(this.id); // div1 const that = this; // 保存当前 this 的指向 const callback = function(){ console.log(that.id); // div1 } callback(); }
五.显式绑定(call/apply)
有的时候this的指向并不能如我们所愿,这个时候我们需要手动去更改this的指向,来满足我们的需求,其实在JavaScript中给我们提供了能够更改this的绑定,首先我们看下call和apply。
function foo(name,age){ console.log(this); console.log(name,age); } const obj = { name:"zs", age:12, } foo.call(obj,'ls',30) // { name: 'zs', age: 12 } // ls 30
通过call函数我们可以看到我们可以手动的将this绑定到我们新定义的对象上面来,并且通过call单个传参的方式将参数传递给了这个函数,实现了函数的调用和this的绑定。
function foo (name, age) { console.log(this) console.log(name, age) } const obj = { name: "zs", age: 12, } foo.apply(obj, ['nnn', 45]) // { name: 'zs', age: 12 } // ls 30
我们会发现我们使用apply的方式进行绑定的修改结果依然如此,差别在于他们的传参方式不同,call是单个的方式进行传参的,而apply是通过数组的方式传参的。
六.bind函数的显式绑定
bind的绑定和call和apply的使用有些差别,使用bind会生成一个新的函数,这个新函数我们称之为BF绑定函数,我们需要手动对这个函数进行调用。
function foo(){ console.log("foo",this) } let obj = { name:"why" } let bar = foo.bind(obj) bar()
七.内置函数的调用绑定思考
我们在开发中会用到很多内置的函数,比如定时器setTimeout 这个时候我们需要靠经验来判断当前的this指向因为有些东西根本不是我们来调用的,而是函数内部调用的,我们根本不知道他们做了什么,这些内容需要我们自己总结一下,内容如下。
- 定时器内部的函数this指向window
setTimeout(function () { console.log(this, "1") }, 500) setTimeout(() => { console.log(this, "2") }, 500)
- 按钮的点击事件,指向事件发起的对象,也就是绑定事件的元素。
let box = document.querySelector(".test") box.onclick = function () { console.log(this) }
- forEach中的this也是指向window
const array = [1, 2, 3, 4, 5, 6] array.forEach(item => { console.log(this) })
八.绑定优先级的比较
我们前面了解的都是一些独立的规则,但是实际的情况往往是比较复杂的,可能涉及到多个绑定一起使用的情况,这个时候我们就需要研究一下不同函数之间调用的优先级。
- 直接调用(默认绑定)的优先级是最低的。
- 显式绑定优先级高于对象绑定(隐式绑定)。
function foo () { console.log(this.name) } let obj = { name: "zzz", bar: foo } obj.bar.call({ name: "aaa" }) // aaa
- new绑定优先级高于对象绑定(隐式绑定)的优先级
function foo (name) { this.name = name console.log(this.name) } let obj = { name: "zzz", bar: foo } new obj.bar("ccc") // ccc
- new绑定不能和call与apply一起使用,new绑定的优先级比bind高。
function foo (name) { this.name = name console.log(this.name) } let bar = foo.bind("zzz") new bar("ccc") // ccc
- bind和apply的优先级,bind的优先级更高,也高于call因为call和apply使用方法一样。
function foo () { console.log(this) } let bar = foo.bind("zzz") bar.call("aaa") // zzz
九.绑定规则之外
其实在上述的绑定规则之外还有许多我们有时候按照规则难以理解的情况,我们来总结下有哪些情况。
- 在显式绑定当中如果传入nullundefined这个绑定会被忽略,使用默认规则,严格模式能够绑定。
function foo () { console.log(this) } foo.apply(null) foo.apply(undefined) // window // window
- 创建一个函数的间接引用,使用函数的默认绑定规则,指向window(了解)
var obj1 = { name:"obj1", foo:function(){ console.log("foo",this) } } var obj2={ name:"obj2" }; (obj2.foo = obj1.foo)() // window
十.箭头函数的使用
我们之前使用函数的方式是这样的。
// 普通函数方式 function foo1(){} // 函数表达式方式 let foo2 = function(){}
箭头函数的写法
// 箭头函数的完整写法 // 1.()函数的参数 // 2.{}函数体 let foo3 = (name,age)=>{ console.log(name); console.log(age); }
箭头函数与普通函数的区别:箭头函数中没有this
和arguments
并且不能作为构造函数使用。
箭头函数的优化方式:
- 当一个参数的时候可以省略函数参数的小括号。
let name = ["abc","bca","nba"]; name.forEach(item=>{ console.log(item); })
- 如果函数体中只有一行执行代码
{}
可以省略,但是一行代码中不能带return
;
let name = ["a","b","c"]; name.filter(item=> console.log(item))
- 如果函数体中只有一行代码,那么这行代码的返回值会作为整个函数的返回值。
let name = ["a","b","c"]; name.filter(item=>item==='a')
- 如果默认返回值是一个对象,那么这个对象必须加小括号
let arr = ()=>({name:"zzz",age:12})
箭头函数使用案例:使用所有nums
的所有平方和的值。
let nums = [20,30,11,15,111]
/* *1.首先调用filter函数然后返回 *2.然后调用map函数然后返回 *3.然后调用reduce计算后返回。 */ let nums = [20, 30, 11, 15, 111] let num = nums.filter(item => item % 2 === 0) .map(item => item * item) .reduce((pre, cur) => pre + cur, 0) console.log(num) // 1300
十一.箭头函数中的this
箭头函数中是没有this
的,所以this
如果在箭头函数中的this
就是上级作用域的this
。
let bar =()=>{ console.log(this) } // window
因为没有this
的原因,箭头函数中使用显式绑定也是无法绑定过去的。
let bar =()=>{ console.log(this) } bar.call({name:"zzz"}) // window
我们再来看下一个案例来明白下this
的查找规则。
let obj = { name: "obj", foo: function () { let bar = () => { console.log(this) } return bar } } let fn = obj.foo() fn.call("bbb") // obj
- 首先调用
foo
函数返回bar
定义的函数,箭头函数没有this
。 - 根据作用域的查找规则,会向上层找
this
,foo函数中是有作用域的。 - 使用
call
函数是无法更改掉this
的。 - 所以打印出来
this
的指向是obj
函数。
十二.this常见面试题解析
oconst o1 = { text: 'o1', fn: function () { return this.text; } } const o2 = { text: 'o2', fn: function () { return o1.fn(); } } const o3 = { text: 'o3', fn: function () { var fn = o1.fn; return fn(); } } console.log(o1.fn()); // o1 console.log(o2.fn()); // o1 console.log(o3.fn()); // undefined
解析:首先o1.fn
是一个对象调用,所以this指向的是这个对象o1
,o2.fn()
单调用的时候返回的是o1.fn
因此指向的仍然是o1
,第三个o1.fn
进行了重新赋值然后调用,独立函数调用指向undefined
。
var name = "window"; var person = { name:"person", sayName:function(){ console.log(this.name); } } function sayName(){ var sss = person.sayName; sss(); // window person.sayName(); // person (person.sayName)(); // person 等价于 person.sayName() (b=person.sayName)(); // window } sayName();
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 person1.foo1.call(person2); // person2 person1.foo2() // window person1.foo2.call(person2); // window person1.foo3()(); //window 独立函数调用 person1.foo3.call(person2)() // window // 默认调用 person1.foo3().call(person2) // person2 person1.foo4()() // person1 person1.foo4.call(person2)() // person2 person1.foo4().call(person2) // person1
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 person1.foo1.call(person2) //person2 person1.foo2() // person1 person1.foo2.call(person2) // person1 person1.foo3()() // window person1.foo3.call(perosn2)() // window person1.foo3().call(person2) // person2 person1.foo4()() // person1 person1.foo4.call(person2)() // person2 person1.foo4().call(person2) // person1
var name = "window" function Person(name){ this.name = name this.obj = { name:'obj', foo: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()() // window 独立函数调用 person1.obj.foo1.call(person2)() // window person1.obj.foo1().call(person2) // person2 person1.obj.foo2()() // obj person1.obj.foo2.call(person2)() // person2 person1.obj.foo2().call(person2) // obj
以上就是一文全面解析JS中的this绑定规则的详细内容,更多关于JS this绑定规则的资料请关注脚本之家其它相关文章!