this指针/闭包/作用域

221 阅读4分钟

this相关知识点框架

image.png

this指向

image.png

上下文 + 作用域

执行上下文概念

image.png

作用域概念

image.png

image.png

作用域链概念

image.png

作用域链

  let a = 'global';
  console.log(a);

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

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

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

        // 作用域查找
        console.log(b);
      }
    }
  }
  course();

  // 取消了全局、块级作用域
  if(true){
    let e = 1111;
    console.log(e);
  }

结果: global halo this yy halo 1111

image.png

this 上下文context

this是在执行时动态读取上下文决定的,不是在定义时决定

函数直接调用 - this 指向 window

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

隐式绑定 - this指向调用堆栈的上一级

  function fn() {
    console.log(this)
    console.log('隐式绑定:', this.a);
  }
  const obj = {
    a: 1
  }

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

输出: {a: 1, fn: ƒ} 隐式绑定: 1

实战:

  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() {
      console.log(this)
      return this.text;
    }
  }
  const o2 = {
    text: 'o2',
    fn: function() {
      return o1.fn();
    }
  }
  const o3 = {
    text: 'o3',
    fn: function() {
      let fn = o1.fn;
      return fn();
    }
  }

  console.log(o1.fn()); //{text: 'o1', fn: ƒ}  o1
  console.log(o2.fn()); //{text: 'o1', fn: ƒ}  o1
  console.log(o3.fn()); // Window undfined
    1. 在执行函数时,如果函数被上一级所调用,那么上下文即指向上一级
    1. 否则为全局孤立,指向window

追问: 现在我需要将console.log(o2.fn())结果是o2

// 1 - 人为干涉、改变this - bind/call/apply
// 2 - 不许改变this
const o1 = {
  text: 'o1',
  fn: function() {
    return this.text;
  }
}
const o2 = {
  text: 'o2',
  fn: o1.fn
}
// this指向最后调用他的对象,在fn执行时,函数挂到o2上即可
最终o2.fn()得到的结果就是'o2'

显式绑定(bind | apply | call)

  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. 都是改变this指向的;
  2. 第一个参数都是this要指向的对象;
  3. 都可以利用后续参数传参; 不同点:
  4. 传参不同,call和bind的参数是依次传参,一一对应的
  5. 但apply只有两个参数,第二个参数为数组
  6. call和apply都是对函数进行直接调用,而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有区别么

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

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

    asyncTest() {
      console.log('异步方法外', this);
      // setTimeout(() => {
      //   console.log('异步方法中的this', this); // 改成箭头函数后this指向course 指向与外层作用域this相同 
      // }, 100)
      setTimeout(function() {
        console.log('异步方法中的this', this);
      }, 100)
    }
  }
  const course = new Course('this');
  course.test();
  course.asyncTest();

打印输出:

  1. 构造函数中的this Course {name: 'this'}
  2. 类方法中的this Course {name: 'this'}
  3. 异步方法外 Course {name: 'this'}
  4. undefined
  5. 异步方法中的this Window 
    1. 执行setTimeout时,传入匿名function执行,效果和全局执行函数效果相同
    1. 再追问,如何解决。把function改为无独立上下文的箭头函数即可

追问 bind原理 / 手写bind

    1. bind在哪里
  function sum(a, b, c) {
    console.log(a, b, c, this);
    return a + b + c;
  }
  // 1. sum.bind - 在哪里 ? => Function.prototype
  Function.prototype.newBind = function () {
      // 2. bind 是什么?
      // a.返回一个函数 b. 返回原函数执行结果 c. 传参不变
      const _this = this;
      // args特点: 第一项 - newThis, 第二项 ~ 最后一项 - 函数传参
      const args = Array.prototype.slice.call(arguments);
      const newThis = args.shift();
      return function () {
          return _this.apply(newThis, args);
      }
  }
    1. apply应用 - 多传参数组化
  Math.max(2, 4, 5, 6);
  const arr = [2, 4, 5, 6];
  let max = Math.max.apply(this, arr); // 这里的this换成{}都可,相当于apply的副业

优先级 - new > 显式 > 隐式 > 默认

  function fn() {
    console.log(this);
  }
  const obj = {
    fn
  }
  obj.fn() // obj 隐式绑定
  
 // 显式 > 隐式
 obj.fn.bind(111)() // Number {111} call,apply, bind为显示
 
funciton foo(a) {
    this.a = a
}
const obj1 = {}
let bar = foo.bind(obj1);
bar(2);
console.log(obj1.a); // 2  foo里的this指向的是obj1

// new
let baz = new bar(3)

// new > 显式
console.log(obj1.a) //2
console.log(baz.a) //3

### 聊完了作用域和上下文,如何突破作用域的束缚
### 闭包: 一个函数和他周围状态的引用捆绑在一起的组合

#### 函数作为返回值场景
```js
  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); // 1

函数嵌套

  let counter = 0;

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

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

  let lis = document.getElementsByTagName('li');
 
  for (var i = 0; i< lis.length; i++) {
      lis[i].onclick = function() {
        console.log(i);
      }
      setTimeout(function() {
        console.log(i); // 6个6 因为上层作用域的变量i已经循环完成
      }, 100)
  }
  
  for (var i = 0; i< lis.length; i++) {
    (function(i) {   
      lis[i].onclick = function() {
        console.log(i);
      }
      setTimeout(function() {
        console.log(i); // 1,2,3,4,5 拿到的是闭包声明的时候的作用域的i,这时候已经传进来了,不会被外层循环所累加
      }, 100)
    })(i);
  }

追问:

立即执行嵌套

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

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

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

      console.log(count); // 1
    }
    console.log(count); // 0
  })();

拆分执行 多个闭包

  function createIncrement() {
    let count = 0;

    function increment() {
      count++;
      console.log(count);
    }

    let message = `count is ${count}`;

    function log() {
      console.log(message);
    }

    return [increment, log];
  }

  const [increment, log] = createIncrement();
  increment(); // 1
  increment(); // 2
  increment(); // 3
  log(); // count is 0

实现私有变量

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

闭包概念

image.png

image.png

image.png

image.png

闭包习题训练

juejin.cn/post/684490…

箭头函数的 this 指向哪⾥?

箭头函数并没有属于⾃⼰的this,它所谓的this是捕获其所在上下⽂的 this 值,作为⾃⼰的this值

// ES6 
const obj = {
    getArrow() { 
        return () => { 
            console.log(this === obj); // true
        }; 
    } 
}

bind() 连续调用多次,this的绑定值是什么呢?

image.png 答案是,两次都仍将输出 3 ,而非期待中的 4 和 5 。 原因是,在Javascript中,多次 bind() 是无效的。

以下代码的输出是什么?

image.png

window
obj
window
window