未找到匹配的笔记

1.27 - 作用域、闭包、this指向

JavaScript闭包面试

JavaScript 深入理解:作用域、闭包、this指向

目录

  1. 作用域 (Scope)
  2. 闭包 (Closure)
  3. this 指向
  4. 综合实战案例

作用域

一、什么是作用域? (Scope)

作用域是变量和函数的可访问范围,它决定了代码中变量的可见性和生命周期。

二、作用域类型

1. 全局作用域 (Global Scope)

var globalVar = "我是全局变量";
let globalLet = "我也是全局变量";

function test() {
    console.log(globalVar); // 可以访问
    console.log(globalLet); // 可以访问
}

2. 函数作用域 (Function Scope)

function outer() {
    var functionVar = "函数作用域变量";
    
    console.log(functionVar); // ✅ 可以访问
}

console.log(functionVar); // ❌ ReferenceError: functionVar is not defined

关键特性

  • var 声明的变量具有函数作用域
  • 函数内部可以访问外部作用域的变量
  • 函数外部无法访问函数内部的变量

3. 块级作用域 (Block Scope)

if (true) {
    let blockLet = "块级作用域";
    const blockConst = "也是块级作用域";
    var notBlock = "不是块级作用域";
}

console.log(notBlock);      // ✅ "不是块级作用域"
console.log(blockLet);      // ❌ ReferenceError
console.log(blockConst);    // ❌ ReferenceError

let/const vs var 的区别

// var 没有块级作用域
for (var i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100);
}
// 输出: 3, 3, 3 (因为 i 是函数作用域,循环结束后 i = 3)

// let 有块级作用域
for (let j = 0; j < 3; j++) {
    setTimeout(() => console.log(j), 100);
}
// 输出: 0, 1, 2 (每次循环 j 都是新的变量)

三、词法作用域 (Lexical Scope)

JavaScript 使用词法作用域(也叫静态作用域),即函数的作用域在函数定义时就确定了,而不是在函数调用时确定。

var value = 1;

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

function bar() {
    var value = 2;
    foo(); // 输出什么?
}

bar(); // 输出: 1

原理解析

  • foo 函数在全局作用域中定义
  • 根据词法作用域,foo 函数内部的 value 会沿着定义时的作用域链查找
  • 查找顺序:foo 函数内部 → 全局作用域
  • 所以输出的是全局的 value = 1,而不是 bar 中的 value = 2

四、作用域链 (Scope Chain)

当查找变量时,JavaScript 引擎会按照以下顺序查找:

当前作用域 → 外层作用域 → 外外层作用域 → ... → 全局作用域
var a = 1;

function outer() {
    var b = 2;
    
    function inner() {
        var c = 3;
        console.log(a); // 1 (从全局作用域找到)
        console.log(b); // 2 (从 outer 作用域找到)
        console.log(c); // 3 (从当前作用域找到)
    }
    
    inner();
}

outer();

作用域链的形成

inner 的作用域链: [inner 作用域] → [outer 作用域] → [全局作用域]
outer 的作用域链: [outer 作用域] → [全局作用域]

五、变量提升 (Hoisting)

var 的提升

console.log(a); // undefined (不是报错)
var a = 1;

// 等价于:
var a;
console.log(a); // undefined
a = 1;

函数声明的提升

foo(); // "Hello" (可以在声明前调用)

function foo() {
    console.log("Hello");
}

let/const 的暂时性死区 (TDZ)

console.log(b); // ❌ ReferenceError: Cannot access 'b' before initialization
let b = 2;

// const 同理
console.log(c); // ❌ ReferenceError
const c = 3;

原理:let/const 也会提升,但在声明之前的区域是”暂时性死区”,无法访问。

六、面试常考题

题目1:循环中的闭包

// 问题代码
for (var i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}
// 输出: 5, 5, 5, 5, 5

// 解决方案1: 使用 let
for (let i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}
// 输出: 0, 1, 2, 3, 4

// 解决方案2: 使用 IIFE
for (var i = 0; i < 5; i++) {
    (function(j) {
        setTimeout(function() {
            console.log(j);
        }, 1000);
    })(i);
}
// 输出: 0, 1, 2, 3, 4

// 解决方案3: 使用 setTimeout 的第三个参数
for (var i = 0; i < 5; i++) {
    setTimeout(function(j) {
        console.log(j);
    }, 1000, i);
}
// 输出: 0, 1, 2, 3, 4

闭包

一、什么是闭包? (Closure)

闭包是指有权访问另一个函数作用域中变量的函数

更准确的定义:

  • 闭包 = 函数 + 函数能够访问的自由变量
function outer() {
    var count = 0; // 自由变量
    
    function inner() {
        count++;
        console.log(count);
    }
    
    return inner;
}

var counter = outer();
counter(); // 1
counter(); // 2
counter(); // 3

二、闭包的原理

执行上下文和变量对象

当函数执行时,JavaScript 引擎会创建一个执行上下文,包含:

  1. 变量对象 (VO): 存储变量、函数声明、参数
  2. 作用域链 (Scope Chain): 保存所有父级作用域的变量对象
  3. this 值
function createCounter() {
    var count = 0; // 存储在 createCounter 的变量对象中
    
    return function() {
        count++;
        return count;
    };
}

var counter1 = createCounter();
var counter2 = createCounter();

console.log(counter1()); // 1
console.log(counter1()); // 2
console.log(counter2()); // 1 (独立的闭包)

内存模型

全局执行上下文
  ├─ counter1 → 匿名函数
  │             └─ [[Scope]] → createCounter 的 AO (count = 2)
  └─ counter2 → 匿名函数
                └─ [[Scope]] → createCounter 的 AO (count = 1)

每次调用 createCounter(),都会创建一个新的执行上下文和变量对象,形成独立的闭包。

三、闭包的应用场景

1. 模块化模式 (Module Pattern)

var Module = (function() {
    // 私有变量
    var privateVar = "我是私有的";
    var privateCount = 0;
    
    // 私有方法
    function privateMethod() {
        console.log("私有方法");
    }
    
    // 返回公共接口
    return {
        publicMethod: function() {
            privateCount++;
            console.log("调用次数:", privateCount);
            return privateVar;
        },
        getCount: function() {
            return privateCount;
        }
    };
})();

console.log(Module.publicMethod()); // "调用次数: 1", "我是私有的"
console.log(Module.publicMethod()); // "调用次数: 2", "我是私有的"
console.log(Module.getCount());     // 2
console.log(Module.privateVar);     // undefined (无法访问私有变量)

2. 函数工厂

function makeMultiplier(multiplier) {
    return function(number) {
        return number * multiplier;
    };
}

var double = makeMultiplier(2);
var triple = makeMultiplier(3);

console.log(double(5));  // 10
console.log(triple(5));  // 15

3. 防抖和节流

// 防抖 (Debounce)
function debounce(fn, delay) {
    var timer = null; // 闭包变量
    
    return function() {
        var context = this;
        var args = arguments;
        
        clearTimeout(timer);
        timer = setTimeout(function() {
            fn.apply(context, args);
        }, delay);
    };
}

// 使用示例
var searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce(function(e) {
    console.log('搜索:', e.target.value);
}, 500));

// 节流 (Throttle)
function throttle(fn, delay) {
    var lastTime = 0;
    
    return function() {
        var now = Date.now();
        if (now - lastTime >= delay) {
            fn.apply(this, arguments);
            lastTime = now;
        }
    };
}

4. 缓存 (Memoization)

function memoize(fn) {
    var cache = {}; // 闭包保存缓存
    
    return function() {
        var key = JSON.stringify(arguments);
        if (cache[key]) {
            console.log('从缓存读取');
            return cache[key];
        }
        
        var result = fn.apply(this, arguments);
        cache[key] = result;
        return result;
    };
}

// 使用示例:计算斐波那契数列
var fibonacci = memoize(function(n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
});

console.log(fibonacci(10)); // 计算
console.log(fibonacci(10)); // 从缓存读取

5. 私有变量和方法

function Person(name) {
    // 私有变量
    var age = 0;
    
    // 公共方法(通过闭包访问私有变量)
    this.getName = function() {
        return name;
    };
    
    this.setAge = function(newAge) {
        if (newAge > 0 && newAge < 150) {
            age = newAge;
        }
    };
    
    this.getAge = function() {
        return age;
    };
}

var person = new Person("张三");
person.setAge(25);
console.log(person.getAge());  // 25
console.log(person.age);       // undefined (私有变量无法直接访问)

四、闭包的陷阱和注意事项

1. 内存泄漏

// ❌ 不好的例子:循环引用导致内存泄漏
function leakyFunction() {
    var element = document.getElementById('myDiv');
    var data = new Array(10000).fill('x');
    
    element.onclick = function() {
        console.log(data[0]); // 闭包引用了 data 和 element
    };
    // element 和闭包相互引用,可能导致内存泄漏
}

// ✅ 好的例子:及时清理引用
function betterFunction() {
    var element = document.getElementById('myDiv');
    var id = element.id;
    
    element.onclick = function() {
        console.log(id); // 只保存必要的数据
    };
    
    element = null; // 解除引用
}

2. 在循环中创建闭包

// ❌ 常见错误
function createFunctions() {
    var result = [];
    for (var i = 0; i < 3; i++) {
        result[i] = function() {
            return i;
        };
    }
    return result;
}

var funcs = createFunctions();
console.log(funcs[0]()); // 3
console.log(funcs[1]()); // 3
console.log(funcs[2]()); // 3

// ✅ 解决方案1:使用 let
function createFunctionsFixed1() {
    var result = [];
    for (let i = 0; i < 3; i++) {
        result[i] = function() {
            return i;
        };
    }
    return result;
}

// ✅ 解决方案2:使用 IIFE
function createFunctionsFixed2() {
    var result = [];
    for (var i = 0; i < 3; i++) {
        result[i] = (function(num) {
            return function() {
                return num;
            };
        })(i);
    }
    return result;
}

五、面试高频题

题目1:实现一个 once 函数

function once(fn) {
    var called = false;
    var result;
    
    return function() {
        if (!called) {
            called = true;
            result = fn.apply(this, arguments);
        }
        return result;
    };
}

// 使用示例
var initialize = once(function() {
    console.log("初始化");
    return "初始化完成";
});

console.log(initialize()); // "初始化", "初始化完成"
console.log(initialize()); // "初始化完成" (不再打印"初始化")

题目2:实现 bind 方法

Function.prototype.myBind = function(context) {
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);
    
    return function() {
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(context, args.concat(bindArgs));
    };
};

// 测试
var obj = { value: 42 };
function fn(a, b) {
    console.log(this.value, a, b);
}

var boundFn = fn.myBind(obj, 1);
boundFn(2); // 42, 1, 2

题目3:实现私有计数器

var Counter = (function() {
    var count = 0; // 私有变量
    
    return {
        increment: function() {
            return ++count;
        },
        decrement: function() {
            return --count;
        },
        getCount: function() {
            return count;
        },
        reset: function() {
            count = 0;
            return count;
        }
    };
})();

console.log(Counter.increment()); // 1
console.log(Counter.increment()); // 2
console.log(Counter.getCount());  // 2
console.log(Counter.reset());     // 0

this指向

一、什么是 this?

this 是函数运行时的上下文对象,它的值取决于函数的调用方式,而不是定义方式。

二、this 的绑定规则

1. 默认绑定 (Default Binding)

独立函数调用时,this 指向全局对象(浏览器中是 window,Node.js 中是 global)。

function foo() {
    console.log(this); // window (非严格模式) 或 undefined (严格模式)
}

foo();

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

2. 隐式绑定 (Implicit Binding)

通过对象调用函数时,this 指向该对象。

var obj = {
    name: "张三",
    sayName: function() {
        console.log(this.name);
    }
};

obj.sayName(); // "张三" (this 指向 obj)

// 多层对象
var obj2 = {
    name: "李四",
    child: {
        name: "王五",
        sayName: function() {
            console.log(this.name);
        }
    }
};

obj2.child.sayName(); // "王五" (this 指向 child)

隐式绑定丢失

var obj = {
    name: "张三",
    sayName: function() {
        console.log(this.name);
    }
};

var name = "全局";
var fn = obj.sayName; // 赋值给变量
fn(); // "全局" (this 指向 window,隐式绑定丢失)

// 作为回调函数
setTimeout(obj.sayName, 100); // "全局" (隐式绑定丢失)

3. 显式绑定 (Explicit Binding)

使用 callapplybind 显式指定 this。

function greet(greeting, punctuation) {
    console.log(greeting + ', ' + this.name + punctuation);
}

var person = { name: "张三" };

// call: 参数逐个传递
greet.call(person, "你好", "!"); // "你好, 张三!"

// apply: 参数以数组形式传递
greet.apply(person, ["你好", "!"]); // "你好, 张三!"

// bind: 返回新函数,永久绑定 this
var boundGreet = greet.bind(person, "你好");
boundGreet("!"); // "你好, 张三!"

bind 的实现原理

Function.prototype.myBind = function(context) {
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);
    
    var fBound = function() {
        var bindArgs = Array.prototype.slice.call(arguments);
        // 如果作为构造函数调用,this 指向实例
        return self.apply(
            this instanceof fBound ? this : context,
            args.concat(bindArgs)
        );
    };
    
    // 维护原型链
    fBound.prototype = Object.create(this.prototype);
    return fBound;
};

4. new 绑定 (New Binding)

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

function Person(name) {
    this.name = name;
    this.sayName = function() {
        console.log(this.name);
    };
}

var person1 = new Person("张三");
person1.sayName(); // "张三" (this 指向 person1)

new 的执行过程

function myNew(Constructor, ...args) {
    // 1. 创建一个新对象
    var obj = {};
    
    // 2. 将新对象的 __proto__ 指向构造函数的 prototype
    obj.__proto__ = Constructor.prototype;
    
    // 3. 将构造函数的 this 绑定到新对象,并执行构造函数
    var result = Constructor.apply(obj, args);
    
    // 4. 如果构造函数返回对象,则返回该对象;否则返回新对象
    return typeof result === 'object' && result !== null ? result : obj;
}

// 测试
var person2 = myNew(Person, "李四");
person2.sayName(); // "李四"

三、绑定优先级

new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定

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

var obj1 = { name: "obj1", foo: foo };
var obj2 = { name: "obj2" };

// 隐式绑定 vs 显式绑定
obj1.foo.call(obj2); // "obj2" (显式绑定优先)

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

var bar = Bar.bind(obj1);
var instance = new bar("new绑定");
console.log(instance.name); // "new绑定" (new 绑定优先)

四、箭头函数的 this

箭头函数没有自己的 this,它的 this 继承自外层作用域(词法作用域)。

var obj = {
    name: "张三",
    regularFunc: function() {
        console.log(this.name); // "张三"
    },
    arrowFunc: () => {
        console.log(this.name); // undefined (this 指向全局)
    },
    nestedFunc: function() {
        var arrow = () => {
            console.log(this.name); // "张三" (继承自 nestedFunc 的 this)
        };
        arrow();
    }
};

obj.regularFunc(); // "张三"
obj.arrowFunc();   // undefined
obj.nestedFunc();  // "张三"

箭头函数的特性

  1. 没有自己的 this,继承外层作用域的 this
  2. 不能使用 call/apply/bind 改变 this
  3. 不能作为构造函数使用(不能使用 new)
  4. 没有 arguments 对象
  5. 没有 prototype 属性
// 箭头函数解决回调中的 this 问题
function Timer() {
    this.seconds = 0;
    
    // 传统方法:需要保存 this
    setInterval(function() {
        this.seconds++; // ❌ this 指向 window
    }, 1000);
    
    // 使用 that/self
    var that = this;
    setInterval(function() {
        that.seconds++; // ✅ 通过闭包访问外层 this
    }, 1000);
    
    // 使用箭头函数
    setInterval(() => {
        this.seconds++; // ✅ 箭头函数继承外层 this
    }, 1000);
}

五、常见面试题

题目1:输出结果

var name = "全局";

var obj = {
    name: "obj",
    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);
        };
    }
};

obj.foo1();          // "obj" (隐式绑定)
obj.foo2();          // "全局" (箭头函数,this 指向全局)

var fn3 = obj.foo3();
fn3();               // "全局" (默认绑定)

var fn4 = obj.foo4();
fn4();               // "obj" (箭头函数,继承 foo4 的 this)

题目2:实现一个柯里化函数

function curry(fn) {
    return function curried(...args) {
        if (args.length >= fn.length) {
            return fn.apply(this, args);
        } else {
            return function(...args2) {
                return curried.apply(this, args.concat(args2));
            };
        }
    };
}

// 测试
function add(a, b, c) {
    return a + b + c;
}

var curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3));    // 6
console.log(curriedAdd(1, 2)(3));    // 6
console.log(curriedAdd(1)(2, 3));    // 6

题目3:手写 call、apply、bind

// call 实现
Function.prototype.myCall = function(context, ...args) {
    context = context || window;
    var fn = Symbol('fn'); // 使用 Symbol 避免属性冲突
    context[fn] = this;
    
    var result = context[fn](...args);
    delete context[fn];
    return result;
};

// apply 实现
Function.prototype.myApply = function(context, args) {
    context = context || window;
    var fn = Symbol('fn');
    context[fn] = this;
    
    var result = args ? context[fn](...args) : context[fn]();
    delete context[fn];
    return result;
};

// bind 实现
Function.prototype.myBind = function(context, ...args) {
    var self = this;
    
    var fBound = function(...args2) {
        return self.apply(
            this instanceof fBound ? this : context,
            args.concat(args2)
        );
    };
    
    fBound.prototype = Object.create(this.prototype);
    return fBound;
};

// 测试
var obj = { value: 42 };

function test(a, b) {
    console.log(this.value, a, b);
}

test.myCall(obj, 1, 2);      // 42, 1, 2
test.myApply(obj, [1, 2]);   // 42, 1, 2
var bound = test.myBind(obj, 1);
bound(2);                    // 42, 1, 2

题目4:setTimeout 中的 this

var obj = {
    name: "obj",
    foo: function() {
        console.log(this.name);
    }
};

setTimeout(obj.foo, 1000); // undefined (this 指向 window)

// 解决方案1:箭头函数
setTimeout(() => obj.foo(), 1000); // "obj"

// 解决方案2:bind
setTimeout(obj.foo.bind(obj), 1000); // "obj"

// 解决方案3:传递函数
setTimeout(function() {
    obj.foo();
}, 1000); // "obj"

综合实战

案例1:实现一个事件发布订阅系统

class EventEmitter {
    constructor() {
        this.events = {}; // 使用对象存储事件
    }
    
    // 订阅事件
    on(eventName, callback) {
        if (!this.events[eventName]) {
            this.events[eventName] = [];
        }
        this.events[eventName].push(callback);
        
        // 返回取消订阅的函数(闭包)
        return () => {
            this.off(eventName, callback);
        };
    }
    
    // 只订阅一次
    once(eventName, callback) {
        var wrapper = (...args) => {
            callback.apply(this, args);
            this.off(eventName, wrapper);
        };
        this.on(eventName, wrapper);
    }
    
    // 发布事件
    emit(eventName, ...args) {
        var callbacks = this.events[eventName];
        if (!callbacks) return;
        
        callbacks.forEach(callback => {
            callback.apply(this, args);
        });
    }
    
    // 取消订阅
    off(eventName, callback) {
        var callbacks = this.events[eventName];
        if (!callbacks) return;
        
        if (!callback) {
            delete this.events[eventName];
        } else {
            this.events[eventName] = callbacks.filter(cb => cb !== callback);
        }
    }
}

// 使用示例
var emitter = new EventEmitter();

var unsubscribe = emitter.on('login', (user) => {
    console.log(`用户 ${user} 登录了`);
});

emitter.once('logout', (user) => {
    console.log(`用户 ${user} 登出了`);
});

emitter.emit('login', '张三');  // "用户 张三 登录了"
emitter.emit('logout', '张三'); // "用户 张三 登出了"
emitter.emit('logout', '李四'); // 无输出(once 只触发一次)

unsubscribe(); // 取消订阅
emitter.emit('login', '王五');  // 无输出

案例2:实现 Promise.all

Promise.myAll = function(promises) {
    return new Promise((resolve, reject) => {
        if (!Array.isArray(promises)) {
            return reject(new TypeError('参数必须是数组'));
        }
        
        var results = [];
        var completedCount = 0;
        var total = promises.length;
        
        if (total === 0) {
            return resolve(results);
        }
        
        promises.forEach((promise, index) => {
            Promise.resolve(promise).then(
                value => {
                    results[index] = value;
                    completedCount++;
                    
                    if (completedCount === total) {
                        resolve(results);
                    }
                },
                reason => {
                    reject(reason);
                }
            );
        });
    });
};

// 测试
var p1 = Promise.resolve(1);
var p2 = new Promise(resolve => setTimeout(() => resolve(2), 100));
var p3 = Promise.resolve(3);

Promise.myAll([p1, p2, p3]).then(results => {
    console.log(results); // [1, 2, 3]
});

案例3:实现深拷贝(处理循环引用)

function deepClone(obj, hash = new WeakMap()) {
    // 处理 null 和基本类型
    if (obj === null) return obj;
    if (typeof obj !== 'object') return obj;
    
    // 处理日期
    if (obj instanceof Date) return new Date(obj);
    
    // 处理正则
    if (obj instanceof RegExp) return new RegExp(obj);
    
    // 处理循环引用
    if (hash.has(obj)) return hash.get(obj);
    
    // 根据原对象的原型创建新对象
    var cloneObj = new obj.constructor();
    hash.set(obj, cloneObj);
    
    // 遍历对象
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            cloneObj[key] = deepClone(obj[key], hash);
        }
    }
    
    return cloneObj;
}

// 测试
var obj1 = {
    name: '张三',
    info: { age: 25 },
    hobbies: ['篮球', '足球']
};
obj1.self = obj1; // 循环引用

var obj2 = deepClone(obj1);
console.log(obj2.self === obj2); // true
console.log(obj1 === obj2);      // false

案例4:实现一个简单的虚拟 DOM diff

function createElement(type, props, ...children) {
    return {
        type,
        props: props || {},
        children: children.flat()
    };
}

function diff(oldNode, newNode) {
    // 节点类型不同,直接替换
    if (typeof oldNode !== typeof newNode) {
        return { type: 'REPLACE', newNode };
    }
    
    // 文本节点
    if (typeof oldNode === 'string') {
        if (oldNode !== newNode) {
            return { type: 'TEXT', newNode };
        }
        return null;
    }
    
    // 标签不同,替换
    if (oldNode.type !== newNode.type) {
        return { type: 'REPLACE', newNode };
    }
    
    // 比较属性
    var propsPatches = diffProps(oldNode.props, newNode.props);
    
    // 比较子节点
    var childrenPatches = [];
    var maxLength = Math.max(
        oldNode.children.length,
        newNode.children.length
    );
    
    for (var i = 0; i < maxLength; i++) {
        childrenPatches[i] = diff(
            oldNode.children[i],
            newNode.children[i]
        );
    }
    
    return {
        type: 'UPDATE',
        props: propsPatches,
        children: childrenPatches
    };
}

function diffProps(oldProps, newProps) {
    var patches = {};
    
    // 检查修改和新增的属性
    for (var key in newProps) {
        if (oldProps[key] !== newProps[key]) {
            patches[key] = newProps[key];
        }
    }
    
    // 检查删除的属性
    for (var key in oldProps) {
        if (!(key in newProps)) {
            patches[key] = undefined;
        }
    }
    
    return patches;
}

// 使用示例
var oldVNode = createElement('div', { id: 'container' },
    createElement('span', {}, 'Hello')
);

var newVNode = createElement('div', { id: 'container', class: 'active' },
    createElement('span', {}, 'World')
);

var patches = diff(oldVNode, newVNode);
console.log(JSON.stringify(patches, null, 2));

总结

作用域核心要点

  1. JavaScript 使用词法作用域(静态作用域)
  2. 作用域链在函数定义时确定
  3. let/const 有块级作用域,var 没有
  4. 变量提升:var 和函数声明会提升,let/const 有暂时性死区

闭包核心要点

  1. 闭包 = 函数 + 能访问的自由变量
  2. 闭包可以访问外部函数的变量,即使外部函数已经返回
  3. 每次函数调用都会创建新的执行上下文和闭包
  4. 注意内存泄漏:及时清理不需要的引用

this 核心要点

  1. this 的值取决于函数调用方式,不是定义方式
  2. 绑定优先级:new > 显式绑定 > 隐式绑定 > 默认绑定
  3. 箭头函数没有自己的 this,继承外层作用域的 this
  4. 严格模式下,独立调用函数的 this 是 undefined

面试技巧

  1. 理解原理,不要死记硬背
  2. 能手写实现核心方法(bind、call、apply)
  3. 熟悉常见应用场景(模块化、防抖节流、事件系统)
  4. 注意边界情况(严格模式、箭头函数、循环引用)
  5. 结合实际项目经验,说明如何在工作中应用

推荐学习资源

  1. 书籍

    • 《你不知道的JavaScript(上卷)》
    • 《JavaScript高级程序设计》
    • 《JavaScript权威指南》
  2. 在线资源

    • MDN Web Docs
    • JavaScript.info
    • 阮一峰的ES6教程
  3. 实践建议

    • 在浏览器控制台中实际运行代码
    • 使用 debugger 和断点调试
    • 查看调用栈和作用域链
    • 手写实现核心方法

记住:理解比记忆重要,实践比理论重要