this与执行上下文

this的概念

this是函数执行时的上下文对象,它的值取决于函数如何被调用。

this是什么

function showThis() {
    console.log(this);
}

// 不同调用方式,this不同
showThis();           // 全局对象(严格模式为undefined)
obj.showThis();       // obj
showThis.call(obj);   // obj
new showThis();       // 新创建的对象

this不是词法作用域

const obj = {
    name: "东巴文",
    greet: function() {
        console.log(this.name);
        
        function inner() {
            console.log(this.name);  // 不是obj.name
        }
        inner();  // 普通调用,this是全局或undefined
    }
};

obj.greet();
// 东巴文
// undefined(严格模式)

默认绑定

当函数独立调用时,使用默认绑定。

严格模式

// 非严格模式
function showThis() {
    console.log(this);  // window(浏览器)
}
showThis();

// 严格模式
"use strict";
function showThisStrict() {
    console.log(this);  // undefined
}
showThisStrict();

独立函数调用

function foo() {
    console.log(this);
}

foo();  // 默认绑定

// 嵌套函数
function outer() {
    console.log(this);  // 默认绑定
    
    function inner() {
        console.log(this);  // 默认绑定
    }
    inner();
}

outer();

严格模式的影响

模式 独立调用的this 东巴文建议
非严格 全局对象 -
严格 undefined 使用严格模式

隐式绑定

当函数作为对象方法调用时,使用隐式绑定。

对象方法调用

const obj = {
    name: "东巴文",
    greet: function() {
        console.log(this.name);
    }
};

obj.greet();  // "东巴文"(this指向obj)

隐式丢失

const obj = {
    name: "东巴文",
    greet: function() {
        console.log(this.name);
    }
};

// 赋值后丢失绑定
const greet = obj.greet;
greet();  // undefined(this丢失)

// 作为回调传递
function runCallback(callback) {
    callback();
}

runCallback(obj.greet);  // undefined

解决隐式丢失

const obj = {
    name: "东巴文",
    greet: function() {
        console.log(this.name);
    }
};

// 方法1:使用bind
const greet = obj.greet.bind(obj);
greet();  // "东巴文"

// 方法2:使用箭头函数
const obj2 = {
    name: "东巴文",
    greet: function() {
        const arrow = () => {
            console.log(this.name);
        };
        arrow();  // "东巴文"
    }
};

// 方法3:保存this引用
const obj3 = {
    name: "东巴文",
    greet: function() {
        const self = this;
        setTimeout(function() {
            console.log(self.name);  // "东巴文"
        }, 100);
    }
};

显式绑定

使用callapplybind显式指定this。

call方法

function greet(greeting, punctuation) {
    console.log(`${greeting}, ${this.name}${punctuation}`);
}

const obj = { name: "东巴文" };

greet.call(obj, "你好", "!");  // 你好, 东巴文!

apply方法

function greet(greeting, punctuation) {
    console.log(`${greeting}, ${this.name}${punctuation}`);
}

const obj = { name: "东巴文" };

greet.apply(obj, ["你好", "!"]);  // 你好, 东巴文!

call vs apply

// call:参数逐个传递
fn.call(thisArg, arg1, arg2, arg3);

// apply:参数以数组传递
fn.apply(thisArg, [arg1, arg2, arg3]);

// 实际应用
const numbers = [5, 6, 2, 3, 7];
const max = Math.max.apply(null, numbers);  // 7
const min = Math.min.apply(null, numbers);  // 2

// ES6展开运算符替代
const max2 = Math.max(...numbers);

bind方法

function greet(greeting) {
    console.log(`${greeting}, ${this.name}`);
}

const obj = { name: "东巴文" };

// bind返回新函数,不立即执行
const greetDongba = greet.bind(obj);
greetDongba("你好");  // 你好, 东巴文

// 柯里化
function add(a, b) {
    return a + b;
}

const addFive = add.bind(null, 5);
console.log(addFive(3));  // 8

bind的特点

// bind绑定后无法再改变
function foo() {
    console.log(this.name);
}

const obj1 = { name: "东巴文" };
const obj2 = { name: "JavaScript" };

const boundFoo = foo.bind(obj1);
boundFoo();  // "东巴文"

boundFoo.call(obj2);  // "东巴文"(无法改变)
boundFoo.apply(obj2);  // "东巴文"

new绑定

使用new调用构造函数时,this指向新创建的对象。

new的过程

function Person(name) {
    this.name = name;
}

const person = new Person("东巴文");

// new做了什么:
// 1. 创建一个新对象
// 2. 将this绑定到新对象
// 3. 执行构造函数代码
// 4. 返回新对象(除非构造函数返回对象)

手动实现new

function myNew(Constructor, ...args) {
    // 1. 创建新对象,原型指向构造函数的prototype
    const obj = Object.create(Constructor.prototype);
    
    // 2. 执行构造函数,绑定this
    const result = Constructor.apply(obj, args);
    
    // 3. 如果构造函数返回对象,则返回该对象
    if (result && typeof result === "object") {
        return result;
    }
    
    // 4. 否则返回新对象
    return obj;
}

const person = myNew(Person, "东巴文");

返回值的影响

function Person(name) {
    this.name = name;
    return { custom: true };  // 返回对象
}

const p = new Person("东巴文");
console.log(p);  // { custom: true }(不是Person实例)

function Person2(name) {
    this.name = name;
    return "字符串";  // 返回原始值
}

const p2 = new Person2("东巴文");
console.log(p2);  // Person2 { name: "东巴文" }(忽略原始值返回)

bind方法

bind方法创建一个新函数,绑定this和部分参数。

基本用法

const module = {
    x: 42,
    getX: function() {
        return this.x;
    }
};

const unboundGetX = module.getX;
console.log(unboundGetX());  // undefined

const boundGetX = unboundGetX.bind(module);
console.log(boundGetX());  // 42

偏函数应用

function multiply(a, b) {
    return a * b;
}

const double = multiply.bind(null, 2);
console.log(double(5));  // 10

const triple = multiply.bind(null, 3);
console.log(triple(5));  // 15

配合setTimeout

function Timer() {
    this.seconds = 0;
    
    // 不使用bind
    setInterval(function() {
        this.seconds++;  // this不指向Timer实例
    }, 1000);
    
    // 使用bind
    setInterval(function() {
        this.seconds++;
        console.log(this.seconds);
    }.bind(this), 1000);
}

手动实现bind

Function.prototype.myBind = function(thisArg, ...args) {
    const fn = this;
    
    const bound = function(...moreArgs) {
        // 如果作为构造函数调用,this指向新实例
        const isNewCall = this instanceof bound;
        return fn.apply(
            isNewCall ? this : thisArg,
            [...args, ...moreArgs]
        );
    };
    
    // 维护原型链
    bound.prototype = Object.create(fn.prototype);
    
    return bound;
};

call方法

call方法立即调用函数,指定this和参数。

基本用法

function greet(greeting, punctuation) {
    console.log(`${greeting}, ${this.name}${punctuation}`);
}

const person = { name: "东巴文" };

greet.call(person, "你好", "!");  // 你好, 东巴文!

借用方法

// 借用数组方法
const arrayLike = {
    0: "东",
    1: "巴",
    2: "文",
    length: 3
};

const arr = Array.prototype.slice.call(arrayLike);
console.log(arr);  // ["东", "巴", "文"]

// 借用Object方法
const obj = { a: 1, b: 2 };
const hasA = Object.prototype.hasOwnProperty.call(obj, "a");
console.log(hasA);  // true

数组最大值

const numbers = [5, 6, 2, 3, 7];

// 使用apply
const max = Math.max.apply(null, numbers);

// 使用call和展开运算符
const max2 = Math.max.call(null, ...numbers);

apply方法

apply方法与call类似,但参数以数组形式传递。

基本用法

function greet(greeting, punctuation) {
    console.log(`${greeting}, ${this.name}${punctuation}`);
}

const person = { name: "东巴文" };

greet.apply(person, ["你好", "!"]);  // 你好, 东巴文!

数组操作

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

// 合并数组
arr1.push.apply(arr1, arr2);
console.log(arr1);  // [1, 2, 3, 4, 5, 6]

// 或使用展开运算符
const merged = [...arr1, ...arr2];

判断类型

function getType(value) {
    return Object.prototype.toString.apply(value);
}

getType([]);        // "[object Array]"
getType({});        // "[object Object]"
getType(new Date()); // "[object Date]"
getType(null);      // "[object Null]"
getType(undefined); // "[object Undefined]"

执行上下文

执行上下文是JavaScript代码执行的环境。

执行上下文类型

// 全局执行上下文
var globalVar = "全局";

// 函数执行上下文
function foo() {
    var localVar = "局部";
}

// Eval执行上下文(不推荐使用)
eval("var evalVar = 'eval'");

执行上下文的生命周期

创建阶段:
1. 创建变量对象(VO)
2. 建立作用域链
3. 确定this值

执行阶段:
1. 变量赋值
2. 函数引用
3. 执行其他代码

变量对象

function foo(a, b) {
    var c = 10;
    function d() {}
    var e = function() {};
}

foo(10, 20);

// 变量对象(创建阶段)
VO = {
    arguments: { 0: 10, 1: 20, length: 2 },
    a: 10,
    b: 20,
    c: undefined,
    d: <function d>,
    e: undefined
}

// 执行阶段
VO = {
    arguments: { 0: 10, 1: 20, length: 2 },
    a: 10,
    b: 20,
    c: 10,
    d: <function d>,
    e: <function>
}

调用栈

调用栈用于管理函数调用的LIFO结构。

调用栈工作原理

function first() {
    console.log("first开始");
    second();
    console.log("first结束");
}

function second() {
    console.log("second开始");
    third();
    console.log("second结束");
}

function third() {
    console.log("third");
}

first();

// 调用栈变化:
// 1. first入栈
// 2. second入栈
// 3. third入栈
// 4. third出栈
// 5. second出栈
// 6. first出栈

// 输出:
// first开始
// second开始
// third
// second结束
// first结束

栈溢出

function recurse() {
    recurse();  // 无限递归
}

recurse();  // RangeError: Maximum call stack size exceeded

查看调用栈

function foo() {
    bar();
}

function bar() {
    console.trace();  // 打印调用栈
}

foo();

// 输出:
// bar
// foo
// <anonymous>

this绑定优先级

this绑定的优先级从高到低:

  1. new绑定
  2. 显式绑定(call/apply/bind)
  3. 隐式绑定(对象方法)
  4. 默认绑定

优先级验证

// 显式绑定 > 隐式绑定
function foo() {
    console.log(this.name);
}

const obj1 = { name: "东巴文", foo };
const obj2 = { name: "JavaScript" };

obj1.foo();           // "东巴文"(隐式绑定)
obj1.foo.call(obj2);  // "JavaScript"(显式绑定优先)

// new绑定 > 显式绑定
function Person(name) {
    this.name = name;
}

const boundPerson = Person.bind({ name: "绑定的" });
const p = new boundPerson("东巴文");
console.log(p.name);  // "东巴文"(new绑定优先)

判断this的流程

1. 函数是否用new调用?→ this是新创建的对象
2. 函数是否用call/apply/bind调用?→ this是绑定的对象
3. 函数是否作为对象方法调用?→ this是该对象
4. 默认绑定 → 严格模式undefined,非严格模式全局对象

下一步

掌握了this与执行上下文后,让我们继续学习:

  1. 数组基础 - 学习数组操作
  2. 数组方法 - 掌握数组方法
  3. 数组高级应用 - 数组高级技巧

东巴文(db-w.cn) - 让编程学习更简单

🎯 东巴文寄语:理解this是JavaScript进阶的关键,掌握this的四种绑定规则和优先级,能让你准确预测和控制this的值。在 db-w.cn,我们帮你彻底搞懂this!