JavaScript中的this

在 JavaScript 编程中,this 关键字是一个非常重要但却常常令人困惑的概念。理解 this 的工作原理对编写和调试代码至关重要。在不同的情况下,this 可能会引用不同的对象,这取决于函数的调用方式。

本文将深入探讨 JavaScript 中的 this 关键字,解释它在不同绑定规则下的行为,包括默认绑定、隐式绑定、显式绑定、硬绑定和 new 绑定。我们还将通过具体的代码示例来展示这些规则是如何影响 this 的值的。

通过这篇文章,您将更好地理解 this 的工作原理,能够更准确地预测和控制函数中的 this 绑定,为编写高质量的 JavaScript 代码打下坚实的基础。

this是什么

在 JavaScript 中,this 是一个函数在被调用时自动生成的一个关键字,它的值取决于函数的调用方式。当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this 就是记录的其中一个属性,会在函数执行的过程中用到(this 是一个执行上下文的一个属性)。

调用位置

调用位置是指函数在代码中被调用的位置,而不是声明的位置。调用栈展示了为了到达当前执行位置所调用的所有函数的顺序。分析函数的调用位置可以确定 this 的绑定方式,因为 this 的绑定规则取决于函数的调用方式(例如,直接调用、作为方法调用、使用 new 关键字调用等)。

javascript 复制代码
function baz() {
  // 当前调用栈是:baz
  // 因此,当前调用位置是全局作用域
  console.log( "baz" );
  bar(); // <-- bar 的调用位置
}

function bar() {
  // 当前调用栈是 baz -> bar
  // 因此,当前调用位置在 baz 中
  console.log( "bar" );
  foo(); // <-- foo 的调用位置
}

function foo() {
  // 当前调用栈是 baz -> bar -> foo
  // 因此,当前调用位置在 bar 中
  console.log( "foo" );
}
baz(); // <-- baz 的调用位置

绑定规则

默认绑定

如果一个函数独立调用(即它不是作为对象的方法或通过 new 关键字调用),那么 this 通常会引用全局对象(在浏览器中是 window,在 Node.js 中是 global)。在严格模式下,this 会是 undefined

javascript 复制代码
function foo() {
    console.log(this.a);
}

let a = 2;
foo(); // 输出2,在非严格模式下
      // 严格模式下会报错

注意:只有函数运行在非 strict mode 下时,默认绑定才能绑定到全局对象

隐式绑定

隐式绑定是指当函数作为对象的方法被调用时,函数调用时的 this 会被隐式地绑定到调用该方法的对象。如果一个函数作为对象的方法被调用,那么 this 会引用调用这个方法的对象。

javascript 复制代码
let obj = {
    a: 2,
    foo: function() {
        console.log(this.a);
    }
};

obj.foo(); // 输出2
javascript 复制代码
function foo(){
  console.log(this.a);
}
let obj = {
    a: 2,
    foo: foo
};

obj.foo(); // 输出2

需要注意的是 foo() 函数的声明方式,以及它如何被添加为 obj 对象的属性。无论是直接在 obj 中定义,还是先定义再添加为引用属性,这个函数实际上都不属于 obj 对象本身。原因如下:

函数不管是先声明再引用或者直接在对象中声明后引用,该函数都是在其声明时的作用域(通常是全局作用域)下定义的。对象本身并不创建新的作用域,作用域是由函数创建的。这意味着对象内部的方法仍然在全局作用域中执行。

javascript 复制代码
function foo() {
    console.log(this.a);
}

let obj = {
    a: 2,
    foo: foo
};

obj.foo(); // 输出2

即使 foo 函数被添加为 obj 的属性,它的作用域仍然是全局的,而不是 obj 的局部作用域。因此,this 绑定取决于函数的调用方式,而不是它的定义位置。

javascript 复制代码
function foo() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: foo
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo(); // 42

对象属性引用链中只有最顶层或者说最后一层会影响调用位置。

隐式丢失

如果你将这个方法赋值给一个变量,或者作为参数传递给另一个函数,那么这个函数可能会"丢失"它的this绑定。

javascript 复制代码
let obj = {
    a: 2,
    foo: function() {
        console.log(this.a);
    }
};

let bar = obj.foo; // 函数别名!

let a = "Oops, global 'a'";

bar(); // "Oops, global 'a'"
javascript 复制代码
let obj ={
    a:'a',
    foo:function(){
        console.log(this.a)
    }
}
obj.foo() // a
function thisFunc(fn){
    this.a = 10
    fn() // 10
}
thisFunc(obj.foo)

当你将一个对象的方法赋值给一个变量,然后通过这个变量调用这个方法时(例如 let bar = obj.foo; bar();),你实际上是在进行直接调用。因此,this 的值会按照默认绑定的规则来确定,而不是绑定到原来的对象。

显式绑定

使用call,apply或bind方法来显式地设置函数的this值。

javascript 复制代码
function foo() {
    console.log(this.a);
}

let obj = {
    a: 2
};

foo.call(obj); // 输出2

注意:当你使用call,apply或bind方法显式地设置函数的this值时,这个this值是固定的,不能被修改。

硬绑定

"硬绑定"是JavaScript中的一个术语,它指的是使用 call、apply 或 bind 方法显式地设置函数的 this 值,并返回一个新的函数。这个新函数的 this 值在任何情况下都不会改变。

在 bind 方法的例子中,bind 函数创建了一个新的函数,这个新函数的 this 值被永久地设置为指定的对象(例如 obj)。无论你如何调用这个新函数,它的 this 值都会是指定的对象。这就是所谓的"硬绑定"。

javascript 复制代码
function bind(fn, obj) {
  return function() {
    return fn.apply(obj, arguments);
  };
}

在ES5中,Function.prototype.bind 方法提供了一个内置的方式来进行硬绑定。bind 方法会创建一个新的函数,这个新函数的this 值被永久地绑定到bind方法的第一个参数。

javascript 复制代码
function foo(something) {
    console.log(this.a, something);
    return this.a + something;
}

var obj = {
    a: 2
};

var bar = foo.bind(obj);

var b = bar(3); // 2 3
console.log(b); // 5

在这个例子中,foo.bind(obj)创建了一个新的函数bar ,这个新函数的this 值被永久地绑定到obj 。无论你如何调用bar 函数,它的this 值都会是obj。这就是所谓的"硬绑定"。 注意:

  • callapply 不会永久性地改变函数的 this 指向。它们仅仅是在当前的函数调用中改变 this
  • .bind() 方法会创建一个新的函数,这个新函数的 this 指向被永久性地绑定到传入 .bind() 的第一个参数。一旦通过 .bind() 创建了新函数,这个新函数的 this 指向就不能再被改变了。

new绑定

如果一个函数通过new关键字被调用,那么this会引用新创建的对象。

javascript 复制代码
function Foo() {
    this.a = 2;
}

let obj = new Foo();
console.log(obj.a); // 输出2

在JavaScript中,使用new 关键字调用一个函数(通常被称为构造函数)会创建一个新的对象,并且这个新对象的this值会被绑定到这个函数中。这个过程可以分为以下四个步骤:

  1. 创建一个全新的对象。 当你使用new关键字调用一个函数时,JavaScript会创建一个新的空对象。
  2. 这个新对象会被执行[[原型]]连接。 这意味着新对象的原型(也就是它的proto 属性)会被设置为构造函数的prototype属性。这样,新对象就可以访问构造函数原型上的所有属性和方法。
  3. 这个新对象会绑定到函数调用的this。 在构造函数内部,this 关键字会引用新创建的对象。这意味着你可以在构造函数内部使用this关键字来设置新对象的属性和方法。
  4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。 如果构造函数返回了一个对象,那么这个对象会被new 表达式返回。如果构造函数没有返回一个对象,或者返回了一个非对象类型的值,那么new表达式会返回新创建的对象。

箭头函数

箭头函数不会创建自己的this上下文,而是从它们的定义位置的作用域继承this。

javascript 复制代码
let obj = {
    a: 2,
    foo: function() {
        setTimeout(() => {
            console.log(this.a);
        }, 100);
    }
};

obj.foo(); // 输出2
javascript 复制代码
let obj = {
    a: 2,
    foo: () => {
      console.log(this.a);
    }
};
var a =11
obj.foo();//11

说明:对象中的作用域为全局作用域(对象不会创建作用域)

异步回调函数this
javascript 复制代码
let obj={
    a:10
}
function foo(){
    setTimeout(function(){console.log(this.a)},1000)
}
foo.call(obj)//全局作用域的this

箭头函数在异步方法中定义的时候就继承当前作用域的this并绑定。

javascript 复制代码
let objB ={
    name:'objB',
    foo:function(){
        (()=>{
            console.log(this.name) // 箭头函数继承了foo函数作用域的this
        })()
    }
}
let name ='glory'
objB.foo() // glory

const add = '当前的位置是在全局作用域里面'
function setTimeoutFunc(){
    this.add = '当前的位置是在setTimeoutFunc里面'
    setTimeout(()=>{
        console.log(this.add);
    },100) // 这个箭头函数在此处定义的时候就继承当前的作用域
}
setTimeoutFunc() // 当前的位置是在setTimeoutFunc里面

优先级

绑定规则的优先级排序:new 绑定和显式绑定的优先级最高,箭头函数的优先级次之,隐式绑定的优先级再次之,最后是默认绑定。如果多个规则同时适用,那么优先级高的规则会决定 this 的值。

javascript 复制代码
function foo() {
console.log( this.a );
}
var obj1 = {
a: 2,
foo: foo
};
var obj2 = {
a: 3,
foo: foo
};
obj1.foo(); // 2
obj2.foo(); // 3
obj1.foo.call( obj2 ); // 3
obj2.foo.call( obj1 ); // 2
//显式的优先级比隐式高
javascript 复制代码
function foo(something) {
this.a = something;
}
var obj1 = {
foo: foo
};
var obj2 = {};
obj1.foo( 2 );
console.log( obj1.a ); // 2
obj1.foo.call( obj2, 3 );
console.log( obj2.a ); // 3
var bar = new obj1.foo( 4 );
console.log( obj1.a ); // 2
console.log( bar.a ); // 4
//new的优先级比隐式高

推荐阅读《你不知道的JavaScript》

相关推荐
tang游戏王1233 分钟前
AJAX进阶-day4
前端·javascript·ajax
如影随从9 分钟前
04-ArcGIS For JavaScript的可视域分析功能
开发语言·javascript·arcgis·可视域分析
知其然亦知其所以然11 分钟前
深入Kafka:如何保证数据一致性与可靠性?
后端·面试·kafka
web前端神器1 小时前
forever启动后端服务,自带日志如何查看与设置
前端·javascript·vue.js
才艺のblog1 小时前
127还是localhost....?
javascript·https·浏览器特性
杰哥在此1 小时前
Java面试题:解释跨站脚本攻击(XSS)的原理,并讨论如何防范
java·开发语言·面试·编程·xss
今天是 几 号1 小时前
WEB攻防-XSS跨站&反射型&存储型&DOM型&标签闭合&输入输出&JS代码解析
前端·javascript·xss
进击的阿三姐2 小时前
vue2项目迁移vue3与gogocode的使用
前端·javascript·vue.js
风火一回2 小时前
webpack+webpack server入门
javascript·webpack
nbsaas-boot3 小时前
为什么面向对象的设计方法逐渐减少
前端·javascript·vue.js