在JavaScript中,
this关键字是一个非常重要的概念,它通常用来代指某一个对象,但是this的指向非常地灵活,容易让初学者感觉捉摸不透;今天就让我们一起来学习这个关键字this,彻底搞懂this以及它的指向问题。
this
为何要设计this ?
在初期的JavaScript中,在对象当中定义的方法并不能直接访问定义在对象中的属性;
但我们可以通过对象的方式调用内部属性;
而这种访问方式并不优雅,于是js官方就给我们打造出了this关键字让我们使用;
其中,this代指的就是obj对象本身,通过this关键字去访问对象内部的属性。
所以说,关键字this其实是为了让对象中的函数有能力访问对象中自己的属性,并且this可以显著的提升代码质量,通过this还可以减少上下文参数的传递。
this的指向
在JavaScript中,this的指向非常灵活,利用好this可以节省很多工作量;那关于this的指向问题,要怎么看呢?
实际上,this的指向并不是静态确定的,而是动态的,在运行时根据上下文来决定;它的值取决于函数调用的方式。
默认绑定
首先,先让我们来看以下一段代码,以及它在浏览器中的v8(JavaScript执行引擎) 中的执行结果;
我们在函数体foo中去打印this的值;可以看到执行函数foo时,输出了Window对象?也就是说此时的 this指向的是Window对象。
Window对象是JavaScript中的全局对象,它是浏览器环境下执行脚本的主要环境。Window对象代表一个浏览器窗口或标签页,并封装了许多与浏览器窗口相关的属性和方法。
在上面的代码中,其实this的指向就遵循了默认绑定规则;当一个函数被独立调用时,即不带任何修饰符的调用(如对象的调用等),此时该函数的this指向 window 全局对象。
当一个函数是被独立调用时,会触发默认绑定规则,此时函数内的this指向是取决于该函数在哪个词法作用域里面生效。然而,在词法作用域中,变量和函数的可访问性是由它们在源代码中的位置决定的。
var a = 2
function foo(){
var a = 1
function bar(){
console.log(this.a);
}
bar() // 在foo的作用域里生效 在全局这个词法作用域里面生效
}
foo()
上面的代码在浏览器中会打印 2,这正是因为函数foo调用时,其嵌套函数bar的调用也会触发,而bar独立调用在foo的作用域里面,但this是要看函数bar在哪个词法作用域里生效,也就是说this取决于函数foo的词法作用域,而foo定义在了全局,也就是说函数foo的词法作用域是全局,所以此时bar里面的this就会指向全局的变量window,就会访问到全局中定义的变量a,输出2;这也是为什么,只要函数是独立调用时,函数里的this就一定指向的是全局window的原因。
隐式绑定
当一个函数被某一个对象所拥有,或者函数被某个上下文对象调用时,此时this会触发隐式绑定规则,该函数中的 this指向该上下文对象。
var obj = {
a: 1,
foo: foo
}
function foo(){
console.log(this.a);
}
obj.foo()
在上面的代码中,函数foo就是作为对象obj的属性调用;这时就触发了隐式绑定规则,函数foo里面的this就指向obj。
隐式丢失
了解完隐式绑定后,我们再来看下面一段代码;
var obj = {
a: 1,
foo: foo
}
function foo(){
console.log(this.a);
}
var obj2 = {
a:2,
obj: obj
}
obj2.obj.foo()
在上面的代码中,我们又将对象obj当作了对象obj2的属性;并通过属性去嵌套调用obj里面的函数foo,那此时的this会指向谁呢?
当一个函数被多个对象链式调用时,this指向最近的那个对象,这就是this的隐式丢失;所以,上面代码中 this是指向对象obj的。
显示绑定
除了js中内定的以上规则去判断this指向,我们能不能手动去修改this的指向呢?在 JavaScript 中,正好打造了三种方法提供给我们去修改this的指向。
在JavaScript中,我们使用.call(), .apply(), 或者.bind()方法去显式地设置函数调用时的this值;我们将这称之为this的显示绑定。
call
在JavaScript中,.call() 方法允许你调用一个函数,并传入一个特定的this值以及任意数量的参数;这使得你可以显式地控制函数内部的this值。
var obj = {
a:1
}
function foo(x,y){
console.log(this.a,x+y);
}
foo.call(obj,1,2) // call会把foo中的this指向到obj中
上面的 .call() 方法就把foo中this的指向改变了,让this指向了对象obj。
apply
除了call方法外,js中还打造了apply方法也可以改变this的指向;但这两个方法有什么不同呢?call和apply的区别就是参数传递的方式不同,在apply方法中,只能接收一个数组,我们把需要传递的参数放在数组里面。
var obj = {
a:1
}
function foo(x,y){
console.log(this.a,x+y);
}
foo.apply(obj,[2,3]) // 参数的传递方式是数组
bind
还有一个bind方法也可以改变this指向,.bind()方法允许你创建一个新的函数,该函数在调用时具有固定的this值。也就是说,bind方法会返回出一个函数体,且需要我们手动调用返回出来的函数后,this指向才会改变。
而在bind方法中,接收参数的方式也是最多且最零散的;如下代码中接收参数的方式都可以生效:
var obj = {
a:1
}
function foo(x,y){
console.log(this.a,x+y);
}
// const bar = foo.bind(obj)
// bar(1,2)
// const bar = foo.bind(obj,1)
// bar(2)
const bar = foo.bind(obj,1,2) // 参数够了后,不会在bar中再接收
bar(2)
new绑定
在JavaScript中,new绑定(Constructor Binding)是指当使用new关键字调用一个函数时,this的绑定行为。当一个函数作为构造函数被调用时,this会被绑定到新创建的对象实例上。
function Person(){
// let obj = {
// name: 'kunkun'
// }
// Person.call(obj)
// obj__proto__=Person.prototype
// return obj
this.name = 'kunkun'
return []; // return 的值是原始类型时,不会影响;但是return 的值是引用类型时,则会返回引用类型的值
}
let p = new Person()
console.log(p);
箭头函数
箭头函数,是 es6 中新打造出来用于更加简洁地构造函数的方式;它引入了一种新的函数定义方式。那它与普通函数有什么不同呢?
我们先来看下面一段代码:
var obj = {
a:1,
foo: function(){
const fn = () =>{
console.log(this.a); // 箭头函数中没有this关键字
}
fn()
}
}
obj.foo()
在上面的代码中,会打印出 1 ;这是因为箭头函数中没有this关键字,就算你在箭头函数里面写了this,那这个this也不是箭头函数的,它是箭头函数外层非箭头函数的this;也就是说箭头函数里的this其实指向的是obj这个对象。
手写 call
其实在显示绑定中,它修改this指向的底层原理就是借助了隐式绑定规则来完成的;接下来,我们以call方法为例,来看看其内部的工作原理:
根据call的功能,我们可以把步骤细分:
-
- 拿到foo
-
- 将foo引用到obj上
-
- 让obj触发foo
-
- 移除掉obj身上的foo
/**
* 定义 Function.prototype.mycall 方法
* 这个方法允许任何函数调用 mycall,并且可以传入一个对象作为上下文
* 它将函数的 this 值绑定到传入的对象上,并执行函数
* @param {Object} context - 要绑定函数的对象
* @param {...any} args - 要传递给函数的参数
* @returns {any} - 执行函数后的返回值
*/
Function.prototype.mycall = function() {
// 将这个方法写在function的原型对象上,这样每个实例都能访问到这个方法
// 使用 arguments 对象来获取调用 mycall 方法时传递的参数
// arguments 对象是一个类数组对象,包含了传递给函数的所有参数
const context = arguments[0]; // 获取需要指定this的对象
// 使用 Array.from 方法将 arguments 对象转换为数组,以便进行数组操作
const args = Array.from(arguments).slice(1); // 剩下的参数
// 将调用 mycall 的函数绑定到指定的 context 对象上
// 通过在 context 对象上设置一个名为 fn 的属性,将当前函数的引用赋值给它
context.fn = this;
// 调用 context 对象上的 fn 函数,并通过扩展运算符 (...) 将 args 数组作为参数传递
// 扩展运算符 (...) 将数组拆分为独立的参数,传递给 fn 函数
const res = context.fn(...args);
// 移除在 context 对象上添加的 fn 属性,以避免对它造成持久影响
// 使用 delete 操作符移除属性
delete context.fn;
// 返回函数调用结果
return res;
}
以上就是关于 JavaScript 中 this 关键字的知识点;觉得有用的话就点点赞吧。