学习笔记1: this 指针/闭包/作用域

97 阅读3分钟

this 指针/闭包/作用域

程序怎么执行

传统编译流程: 先进行分词 -> 构建 AST(抽象语法树)-> 生成代码

JavaScript 执行分为两个阶段: 1. 预编译阶段 - 前置阶段: - 进行变量声明 - 变量声明进行提升,但是值为 undefined - 非表达式函数进行提升 2. 执行阶段

JS 中变量是如何被赋值和使用的?有什么区别

var a = 2

  • var a: 编译器会先问作用域,是不是已经有了一个 a 在当前作用域中? 是:编译器忽略当前声明,继续往下走 不是:在当前作用域中声明此变量,并命名为 a
  • a = 2 编译器会先问作用域,是不是已经有了一个 a 在当前作用域中? 是:赋值为 2 不是:去上层作用域找

作用域

作用域:根据名称查找变量的规则

词法作用域/静态作用域

就是定义词法阶段的作用域,通俗来讲,就是你写代码的时候,将变量写在哪里决定的。 因此,当词法分析器处理代码时,会保持作用域不变。eval/with 除外

函数作用域

就是属于这个函数的全部变量,都可以在整个函数内使用

上下文

词法作用域是在写代码,或者定义时确定的, 动态作用域时运行时确定的

闭包

什么是闭包?

函数嵌套函数,内层函数引用了外层函数作用域下的变量,并且内层函数在全局下可访问,就形成了闭包

闭包使用场景

  • 函数式编程
  • 当一个函数的执行,和上下文有关时
  • 防抖/节流
/** 防抖 */
function debounce(func, delay) {
    let timer = null;
        return function() {
            const context = this;
            const args = arguments;
            clearTimeout(timer);
            timer = setTimeout(function() {
                func.apply(context, args);
            }, delay);
    };
}
/** 节流 */
function throttle(fn, delay){
     let timer = null;
     return function(){
            const context = this;
            const args = arguments;
            if (!timer) {
                timer = setTimeout(function(){
                fn.apply(context, args);
            }, delay);
        }
    };
 };
  • 保存执行环境

this

this 指向

是根据上下文动态决定的

- 在简单调用时,this指向默认为window, global, undefined(浏览器/node/严格模式)
- 对象调用时,绑定在对象上
- 使用call,bind, apply绑定在参数上
- 使用new关键字时,绑定在新创建的对象上 (以上三条优先级:new > call/bind/apply > 对象调用)
- 使用箭头函数,根据外层规则决定
var number = 5;
const obj = {
    number: 3,
    fn: (function(){
        var number;
        this.number *= 2;
        number *= 2;
        number = 3;
        return function() {
            var num = this.number;
            this.number *= 2;
            console.log(num);
            number *= 3;
            console.log(number);
        }
    })()
}

fn = obj.fn;
fn.call(null);
obj.fn();
console.log(number)

手动实现 call/apply/bind

/** call */
Function.prototype.myCall = function(context, ...args) {
    if (typeof this !== 'function') {
        throw new TypeError('Function.prototype.myCall - 被调用的对象必须是函数');
    }
    context = context || (typeof global !== 'undefined' ? global : typeof window !== 'undefined' ?window : undefined);
    let fn = Symbol('key');
    context[fn] = this;
    const result = context[fn](...args);
    delete context[fn];
    return result;
}

/** apply */
Function.prototype.myApply = function(context, argsArr) {
    if (typeof this !== 'function') {
        throw new TypeError('Function.prototype.myApply - 被调用的对象必须是函数');
    }
    if (argsArr && !Array.isArray(argsArr)) {
        throw new TypeError('Function.prototype.myApply - 第二个参数必须时数组');
    }
    context = context || (typeof global !== 'undefined' ? global : typeof window !== 'undefined' ?window : undefined);
    let fn = Symbol('key');
    context[fn] = this;
    const result = Array.isArray(argsArr) ? context[fn](...argsArr) : context[fn]();
    delete context[fn];
    return result;
}

/** bind */
Function.prototype.myBind = function (context, ...args) {
    if (typeof this !== 'function') {
        throw new TypeError('Function.prototype.myBind - 被调用的对象必须是函数');
    }
    context = context || (typeof global !== 'undefined' ? global : typeof window !== 'undefined' ?window : undefined);
    const _this = this;
    return function fn(...innerArgs) {
        if (this instanceof fn) {
            return new _this(...args, ...innerArgs);
        }
        return _this.apply(context, args.concat(innerArgs));
    };
}