JS高级—作用域/this指针/闭包

183 阅读3分钟

作用域

面试题

  // 1. 执行顺序
  let a = 'global';
  console.log(a);

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

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

          teacher();
          // 2. 函数提升 - 作用域之内
          function teacher() {
              // 2.1 let不支持提升
              // 2.2 变量通过var支持提升,声明提升
              // var e = undefined
              console.log(e);
              let d = 'yunyin';
              var e = 'yy';
              // e = 'yy';
              console.log(d);

              console.log('test1', b) // 3. 作用域向上查找,向下传递
          }
      }
  }
  course();

  // 提升优先级
  console.log('yunyin', yunyin);
  function yunyin() {
      this.course = 'js'
  }
  yunyin = 'course';
  // 变量优先 => 函数需要变量

  // 块级作用域
  if (true) {
      let e = 111;
      var f = 222;
  }
  console.log(f);
  console.log(e);
    1. 对于作用域链我们直接通过创建态来定位作用域链 —— 静态
    1. 手动取消全局

this上下文context

  • this是在执行时动态读取上下文决定的,而不是创建时
  • 考察重点—各使用态的指针指向

函数直接调用中 —— this指向的window

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

隐式绑定 —— this指代调用堆栈上一级 => 对象、数组等引用关系逻辑

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

面试题1

    const foo = {
        bar: 10,
        fn: function() {
            console.log(this.bar);
            console.log(this);
        }
    }
    // 取出
    let fn1 = foo.fn;
    // 独立执行
    fn1();

面试题2——追问1 如何改变属性指向?

    const o1 = {
        text: 'o1',
        fn: function(){
            // 直接使用上下文 - 传统派活
            console.log('o1fn', 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('o1fn', o1.fn());
    console.log('o2fn', o2.fn());
    console.log('o3fn', o3.fn());

基于上面的结果我们能看到: o2指向的是o1; o3指向的是window。

面试题3——追问2 现在我要将console.log('o2fn', oa.fn())的结果是o2。

//1. 人为干涉,改变this - bind / call / apply
const o1 = {
      text: "o1",
      fn: function () {
        // 直接使用上下文 - 传统派活
        console.log("o1fn", this);
        return this.text;
      },
    };
const o2 = {
      text: "o2",
      fn: function () {
        // 呼叫领导执行 —— 部门协作
        return o1.fn();
      },
    };
o1.fn.apply(o2);
o1.fn.call(o2);
o1.fn.bind(o2)();
// 2. 不需人为改变
const o1 = {
      text: 'o1',
      fn: function(){
          // 直接使用上下文 - 传统派活
          console.log('o1fn', this);
          return this.text;
       }
     }

const o2 = {
      text: 'o2',
      fn: o1.fn
      }
console.log('o2fn', o2.fn());

显式绑定

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

foo();

// 使用
foo.call({
  a: 1
},4,5);
foo.apply({
  a: 2
},[6,7]);

const bindFoo = foo.bind({
  a: 3
},8,9);
bindFoo();

面试题1——call/apply/bind的区别

call与apply的唯一区别

传给fun的参数写法不同:

  • apply是第2个参数,这个参数是一个数组:传给fun参数都写在数组中。
  • call从第2~n的参数都是传给fun的。
call/apply与bind的区别

执行

  • call/apply改变了函数的this上下文后马上执行该函数
  • bind则是返回改变了上下文后的函数,不执行该函数

返回值:

  • call/apply 返回fun的执行结果
  • bind返回fun的拷贝,并指定了fun的this指向,保存了fun的参数。

面试题2——手写bind/apply/call

Function.prototype.newBind = function () {
  //this指当前函数
  const _this = this;
  //Array.prototype.slice.call()方法能够将一个具有length属性的对象转换为数组。
  const args = Array.prototype.slice.call(arguments);
  //shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。此方法改变原数组
  const newThis = args.shift();
  //返回一个函数
  return function () {
  return _this.apply(newThis, args);
  };
};
Function.prototype.newApply = function (context) {
  context = context || window;
  // 挂载执行函数
  // context.fn = this;
  context.__proto__.fn = this;
  let result = arguments[1] ? context.fn(...arguments[1]) : context.fn();
  delete context.fn;
  return result;
};
Function.prototype.newCall = function (context) {
  context = context || window;
  context.__proto__.fn = this;
  const args = Array.prototype.slice.call(arguments);
  args.shift();
  const result = args.length>0 ? context.fn(...args) : 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()();

    // 立即执行函数 => js模块化的基石
    (function immediate(args) {
        console.log(++args)
    })(1);

    // 实现私有变量
    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);
            }
        }
    }