我们知道浏览器运行环境下在全局作用域下的this是指向window的,但是开发中却很少在全局作用域下去使用this,通常都是在函数中进行使用,而函数使用不同方式进行调用,其this的指向是不一样的。JavaScript中函数在被调用时,会先创建一个函数执行上下文(FEC),而这个上下文中记录着函数的调用栈、活动对象(AO)以及this等等。那么this到底指向什么呢?下面一起来看看this的四个绑定规则。
在进行独立函数调用时,this就会使用默认绑定,而默认绑定的值就是全局的window。独立函数调用可以简单认为函数没有被绑定到某个对象上进行调用。
满足默认绑定的函数调用情况:
function foo() {
console.log(this)
}
foo() // window
function foo1() {
console.log(this) // window
}
function foo2() {
console.log(this) // window
foo1()
}
function foo3() {
console.log(this) // window
foo2()
}
foo3()
function foo() {
return function() {
console.log(this)
}
}
const fn = foo()
fn() // window
将对象中的方法赋值给别人,别人再进行调用,虽然是对象中的方法再次赋值,最后被赋值的对象都是进行独立函数调用的;
obj中的bar方法作为参数传递到foo函数中,然后进行调用(最后被传递的参数进行了独立调用)
function foo(func) {
func()
}
const obj = {
bar: function() {
console.log(this)
}
}
foo(obj.bar) // window
const obj = {
bar: function() {
console.log(this)
}
}
const fn = obj.bar
fn() // window
隐式绑定是开发中比较常见的,通过某个对象对函数进行调用,也就是说函数的调用是通过对象发起的,常见为对象中方法的调用(这个对象会被js引擎绑定到函数中的this里面)。
满足隐式绑定的函数调用情况:
const obj = {
foo: function() {
console.log(this)
}
}
obj.foo() // obj对象
function bar() {
console.log(this)
}
const obj = {
foo: bar
}
obj.foo() // obj对象
const obj1 = {
name: 'obj1',
obj2: {
name: 'obj2',
foo: function() {
console.log(this)
}
}
}
obj1.obj2.foo() // obj2对象
如果我们不希望使用隐式绑定的对象,而想在调用函数时给this绑定上自己想绑定的东西,那么这样的行为就称为显示绑定,显示绑定主要借助于三个方法,分别为call、apply和bind。
JS中所有的函数都有call和apply方法属性(主要是其原型链上实现了这两个方法),可以说这两个函数就是给this准备的。简单介绍一下这两个函数的区别:在进行函数调用时,都可以使用call和apply进行调用,其传入的第一个参数都是一样的,也就是我们希望给this绑定的值,后面的参数apply对应为包含多个参数的数组,call对应为参数列表。
function foo() {
console.log(this)
}
const obj = {
name: 'curry',
age: 30
}
foo() // window
foo.call(obj) // obj对象
foo.apply(obj) // obj对象
function sum(x, y) {
console.log(this, x + y)
}
const obj = {
name: 'curry',
age: 30
}
sum(10, 20) // window 30
sum.call(obj, 10, 20) // obj对象 30
sum.apply(obj, [10, 20]) // obj对象 30
function foo() {
console.log(this)
}
// 1.绑定字符串
foo.call('aaa')
foo.apply('bbb')
// 2.绑定字符串
foo.call(111)
foo.apply(222)
// 3.绑定数组
foo.call([1, 2, 3])
foo.apply(['a', 'b', 'c'])
call和apply可以帮助我们调用函数明确this的绑定对象,而bind与这两种的用法不太一样。如果希望一个函数的this可以一直绑定到某个对象上,那么bind就可以派出用场了。
function foo() {
console.log(this)
}
const obj = {
name: 'curry',
age: 30
}
const newFoo = foo.bind(obj)
newFoo() // obj对象
function foo() {
console.log(this)
}
const obj1 = {
name: 'curry',
age: 30
}
const obj2 = {
name: 'kobe',
age: 24
}
const newFoo = foo.bind(obj1)
newFoo() // obj1对象
newFoo.call(obj2) // obj1对象
newFoo.apply(obj2) // obj1对象
const obj3 = {
bar: newFoo
}
obj3.bar() // obj1对象
JavaScript中的函数不仅可以通过上面的方法进行调用,还可以使用new关键字来调用函数,当使用new来调用的函数,一般称为构造函数(ES6中的类),以这样的方式来调用的函数this所绑定的值,就叫做new绑定。
构造函数一般用于创建对象,通过new调用构造函数可以返回一个实例对象,而this绑定就是这个实例对象。那么使用new调用时,函数内部会进行哪些操作呢?如下:
function Student(sno, name, age) {
this.sno = sno
this.name = name
this.age = age
console.log('this: ', this)
}
const stu1 = new Student(1, 'curry', 30) // this: {sno: 1, name: "curry", age: 30}
console.log(stu1) // Student {sno: 1, name: "curry", age: 30}
const stu2 = new Student(2, 'kobe', 24) // this: {sno: 2, name: "kobe", age: 24}
console.log(stu2) // Student {sno: 2, name: "kobe", age: 24}
console.log(stu1.__proto__ === Student.prototype) // true
console.log(stu2.__proto__ === Student.prototype) // true
setTimeout(function() {
console.log(this) // window
}, 1000)
// ------相当于------
function mySetTimeout(callback, delay) {
callback() // 独立函数调用
}
const boxDiv = document.querySelector('.box')
boxDiv.onclick = function() {
console.log(this) // boxDiv(DOM对象)
}
boxDiv.addEventListener('click', function() {
console.log(this) // boxDiv(DOM对象)
})
const arr = [1, 2, 3, 4, 5]
const obj = {
name: 'curry',
age: 30
}
arr.forEach(function() {
console.log(this) // obj对象
}, obj)
arr.map(function() {
console.log(this) // obj对象
}, obj)
arr.filter(function() {
console.log(this) // obj对象
}, obj)
上面提到了this的四种绑定规则,如果在函数调用时,使用到了多种绑定规则,最终函数的this指向什么呢?那么这里就涉及到this绑定的优先级,优先级高的规则决定this的最终绑定。
this四种绑定规则优先级如下:
const obj1 = {
name: 'obj1',
foo: function() {
console.log(this)
}
}
const obj2 = {
name: 'obj2'
}
obj1.foo.call(obj2) // obj2对象
obj1.foo.apply(obj2) // obj2对象
const newFoo = obj1.foo.bind(obj2)
newFoo() // obj2对象
function foo() {
console.log(this)
}
const obj1 = {
name: 'curry',
age: 30
}
const obj2 = {
name: 'kobe',
age: 24
}
const newFoo = foo.bind(obj1)
newFoo() // obj1对象
newFoo.call(obj2) // obj1对象
newFoo.apply(obj2) // obj1对象
// 1.new绑定高于隐式绑定
const obj1 = {
foo: function() {
console.log(this)
}
}
const f = new obj1.foo() // foo函数对象
// 2.new绑定高于显示绑定
function foo(name) {
console.log(this)
}
const obj2 = {
name: 'obj2'
}
const newFoo = foo.bind(obj2)
const nf = new newFoo(123) // foo函数对象
总结:
在特殊情况下,this的绑定不一定满足上面的绑定规则,主要有以下特殊情况:
function foo() {
console.log(this)
}
foo.call(null) // window
foo.call(undefined) // window
foo.apply(null) // window
foo.apply(undefined) // window
const newFoo1 = foo.bind(null)
const newFoo2 = foo.bind(undefined)
newFoo1() // window
newFoo2() // window
const obj1 = {
name: 'obj1',
foo: function() {
console.log(this)
}
}
const obj2 = {
name: 'obj2'
}
// 被认为是独立函数调用
;(obj2.bar = obj1.foo)() // window
ES6中的箭头函数:箭头函数不会使用上面的四种绑定规则,也就是说不绑定this,箭头函数的this是根据它外层作用域中的this绑定来决定的;
数组内置方法中回调使用箭头函数:
const names = ['curry', 'kobe', 'klay']
const obj = {
name: 'obj'
}
names.map(() => {
console.log(this) // window
}, obj)
const obj1 = {
name: 'obj1',
obj2: {
name: 'obj2',
foo: () => {
console.log(this)
}
}
}
obj1.obj2.foo() // window
function foo() {
return () => {
console.log(this)
}
}
const obj = {
name: 'curry',
age: 30
}
const bar = foo.call()
bar() // obj对象
以上提到的内容都是在浏览器环境中进行测试的,在浏览器环境下的this是指向全局window的,那么在node环境中全局this指向什么呢?
为什么全局this打印为一个空对象?
当我们js文件被node执行时,该js文件会被视为一个模块;
node加载编译这个模块,将模块中的所有代码放入到一个函数中;
然后会执行这个函数,在执行这个函数时会通过apply绑定一个this,而绑定的this就为{}
;
那为什么node中还是可以使用想window中的全局方法呢?像setTimeout、setInterval等。
因为node环境中也是存在全局对象的,通过打印globalThis
就可以进行查看;
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://www.cnblogs.com/MomentYY/p/15890957.html
内容来源于网络,如有侵权,请联系作者删除!