this上下文

57 阅读4分钟

一、作用域 + 上下文

作用域链

    1. 对于作用域链我们直接通过创建态来定位作用域链
    1. 手动取消全局,使用块级作用域
 let a = 'global';
    console.log(a);

    function course() {
        let b = 'zhaowa';
        console.log(b);

        session();
        function session() {
            let c = 'this';
            console.log(c);

            teacher();
            function teacher() {
                let d = 'yy';
                console.log(d);

                console.log('test1', b);
            }
        }
    }
    console.log('test2', b);
    course();

    if(true) {
        let e = 111;
        console.log(e);
    }
    console.log('test3', e)

二、this 上下文context

  • this是在执行时动态读取上下文决定的,而不是创建时

1.函数直接调用

  • this 指向的是window => 函数表达式、匿名函数、嵌套函数
    function foo() {
        console.log(this);
    }

    foo();   //  Window

2.隐式绑定

  • this的指向是调用堆栈的上一级 => 对象、数组等引用关系逻辑
    function fn() {
        console.log('隐式绑定', this.a);
    }
    const obj = {
        a: 1,
        fn
    }

    obj.fn = fn;
    obj.fn();

面试题:

// 第一题
const foo = {
    bar: 10,
    fn: function() {
        console.log(this.bar);
        console.log(this);
    }
}
let fn1 = foo.fn;
fn1();  // undefined  Window

// 第二题, 如何改变指向
const o1 = {
    text: 'o1',
    fn: function() {
        // 直接使用上下文 - 传统分活
        return this.text;
    }
}

// 1. 在执行函数时,函数被上一级调用,上下文指向上一级
const o2 = {
    text: 'o2',
    fn: function() {
        // 呼叫领导执行 - 部门协作
        return o1.fn();
    }
}

// 2. 直接变成公共函数,指向window
const o3 = {
    text: 'o3',
    fn: function() {
        // 直接内部构造 - 公共人
        let fn = o1.fn;
        return fn();
    }
}

console.log('o1fn', o1.fn());   // o1
console.log('o2fn', o2.fn());   // o1
console.log('o3fn', o3.fn());   // undefined

// 第三题-现在我要将console.log('o2fn', o2.fn())的结果是o2
// 1. 人为干涉,改变this - bind/call/apply   o1.fn.apply(o2);
// 2. 不改变this
// this指向最后调用他的对象,在fn执行时,o1.fn抢过来挂载在自己o2fn上即可
const o1 = {
    text: 'o1',
    fn: function() {
        // 直接使用上下文 - 传统分活
        return this.text;
    }
}

// 1. 在执行函数时,函数被上一级调用,上下文指向上一级
const o2 = {
    text: 'o2',
    fn: o1.fn
}
console.log('o2fn', o2.fn());   // o2

显示绑定

function foo() {
    console.log('函数内部this', this);
}
foo();

// 使用
foo.call({a: 1});
foo.apply({a: 1});

const bindFoo = foo.bind({a: 1});
bindFoo();

面试题-call、apply、bind的区别

    1. call < = > apply 传参不同 依次传入/数组传入
    1. bind返回函数改变指向后的函数,不执行

new-this指向的new创建的实例

class Course {
    constructor(name) {
        this.name = name;
        console.log('构造函数中的this:', this);
    }

    test() {
        console.log('类方法中的this:', this);
    }
}

const course = new Course('this');
course.test();

面试题- 类中的异步方法,this有区别吗

    1. 执行setTimeout时,匿名方法执行时,效果和全局执行函数效果相同
    1. 再追问,如何解决?--箭头函数
class Course {
    constructor(name) {
        this.name = name;
        console.log('构造函数中的this:', this);
    }

    test() {
        console.log('类方法中的this:', this);
    }
    asyncTest() {
        console.log('异步方法外:', this);
        setTimeout(function() {
            console.log('异步方法内:', this);
        }, 100)
    }
}

const course = new Course('this');
course.test();
course.asyncTest();

三、手写一个bind/apply

  • 原理或者手写类题目,结题思路
    1. 说明原理,写下注释
    1. 根据注释,补齐代码 手写bind-function.bind(thisArg[, arg1[, arg2[, ...]]])
// 函数原型上的方法
Function.prototype.newBind = function(){
    // 这里的this就是执行函数
    const fn = this;
    // 第一个参数是新的上下文,后面的参数是执行函数的参数
    const arg = Array.prototype.slice.call(arguments);   // 由于argument是类数组
    const context = arg.shift();
    // 返回新函数
    return function() {
        return fn.apply(context,arg);
    }
}

手写apply-`function.apply(thisArg, args)

// 函数原型上的方法
Function.prototype.newApply = function(){
    // 这里的this就是执行函数
    const fn = this;
    // 第一个参数是新的上下文,后面的参数是执行函数的参数
    const context = arguments[0] || window;
    context.fn = fn;
    // 返回新函数执行结果
    let result = arguments[1] ? context.fn(...arguments[1]) : context.fn();
    delete context.fn;
    return result;
}

四、如何突破作用域束缚-闭包

  • 闭包:一个函数和他周围状态的引用捆绑在一起的组合

函数作为返回值的场景

  • 函数外部获取到了函数作用域内的变量值
    function mail() {
        let content = '信';
        return function() {
            console.log(content);
        }
    }
    const envelop = mail();
    envelop();

函数作为参数的时候

    // 单一职责
    let content;
    // 通用存储
    function envelop(fn) {
        content = 1;

        fn();
    }

    // 业务逻辑
    function mail() {
        console.log(content);
    }

    envelop(mail);

函数嵌套

    let counter = 0;

    function outerFn() {
        function innerFn() {
            counter++;
            console.log(counter);
            // ...
        }
        return innerFn;
    }
    outerFn()();

事件处理(异步执行)的闭包

    let lis = document.getElementsByTagName('li');

    for(var i = 0; i < lis.length; i++) {
        (function(i) {
            lis[i].onclick = function() {
                console.log(i);
            }
        })(i);
    }

立即执行嵌套

    (function immediateA(a) {
        return (function immediateB(b) {
            console.log(a); // 0
        })(1);
    })(0);

当立即执行遇上块级作用域

    let count = 0;

    (function immediate() {
        if(count === 0) {
            let count = 1;

            console.log(count);
        }
        console.log(count);
    })();

实现私有变量

    function createStack() {
        return {
            items: [],
            push(item) {
                this.item.push(item);
            }
        }
    }

    const stack = {
        items: [],
        push: function() {}
    }

    function createStack() {
        const items = [];
        return {
            push(item) {
                items.push(item);
            }
        }
    }
    // Vuex store