this的那些事

211 阅读5分钟

学在前面

1.call apply bind的区别

  • call apply bind 都能改变this的指向, call、apply会立即执行, bind不会立即执行。
  • call和apply不会返回新函数立即执行,bind会返回一个新函数不会立即执行。
  • call和apply绑定一次之后下次在用的时候仍然需要绑定, bind返回的是一个新函数 且this不会在改变。
  • call 跟apply的区别:call后面跟着的是参数列表,apply后面的参数是个数组,call的性能要高于apply。

概念部分

  • 默认绑定

    1. 非严格模式下 this指向window(全局对象), 严格模式下this 指向undefined

    let f = function () { console.log(this) // window }

    2. 严格模式([use strict](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode))
    

    let f = function () { "use strict" console.log(this) // undefined }

  • 隐式绑定

    1. 如果函数调用时,前面存在调用它的对象,那么this就会隐式绑定到这个对象上:
    var name = 'lisi';
    var obj = {
    name: 'zhangsan',
    fn: function () {
    	console.log(this.name)
    }
    }
    obj.fn(); // zhangsan
    
    1. 如果函数前面有多个函数调用的话,就近原则
    var name = 'lisi';
    var obj = {
     name: 'zhangsan',
     fn: function () {
     	console.log(this.name)
     }
    }
    window.obj.fn(); // 这里输出的依旧是zhangsan
    
    1. 如果我们把这个函数复制给一个变量在执行, 这里相当云window.方法, 回去找window下面的全局属性 如果存在 那就是window下面的值 否则就是undefined.
    var name = 'lisi';
    var obj = {
     name: 'zhangsan',
     fn: function () {
     	console.log(this.name)
     }
    }
    let fn = obj.fn;
    fn() // 这里的值 就是lisi 并不是zhangsan
    
  • 显示绑定

    1. 通过 call apply bind 来改变函数的this指向
      let cal = {
        name: 'zhangsan'
      };
      let app = {
        name: 'lisi'
      };
      let bin = {
        name: 'wangerma'
      }
      function fn () {
      	console.log(this.name)
      };
      fn() // undefined
      fn.call(cal) // zhangsan
      fn.apply(app) // lisi
      fn.bind(bin)() // wangerma
    
    1. 如果我更改了上面代码的执行顺序, bind放在call的上面, 输入的值是一样的, bind返回一个新的函数,this不可更改
      fn = fn.bind(cal)
      fn() //  wangerma
      fn.apply(app) // wangerma
      fn.call(bin)() // wangerma
    
    1. 如果call apply bind 传入的参数是null 或者 undefined 内部的this仍指向window call和apply绑定之后,如果在调用还需要绑定, bind返回一个新的函数 且内部this的指向永远都不会改变。
      var name = 'xiaotaoqi'; // 如果把这个var 换成let 你会发现不一样的结果
      let cal = {
        name: 'zhangsan'
      };
      let app = {
        name: 'lisi'
      };
      let bin = {
        name: 'wangerma'
      }
      function fn () {
      	console.log(this.name)
      };
      fn() // xiaotaoqi
      fn.call(null) // xiaotaoqi
      fn.apply(undefined) // xiaotaoqi
      fn.call(app) // lisi
      fn() // xiaotaoqi
      fn = fn.bind(bin)
      fn() // wangerma
      fn() // wangerma
    
    1. js的 Api(数组中的一些方法等等)中也有部分方法绑定了this
      let obj = {
        name: 'xiyangyang'
      }
      [{name: 'huitailang'}, {name: 'hongtailang'}].forEach(function(){
      	console.log(this.name) // xiyangyang
      }, obj)
    
    1. new关键字会进行如下操作:
      • 创建一个空的简单JavaScript对象(即{});
      • 链接该对象(设置该对象的constructor)到另一个对象 ;
      • 将步骤1新创建的对象作为this的上下文 ;
      • 如果该函数没有返回对象,则返回this。
    	function Person () {
      this.name = 'dahuilang'
        }
     var person = new Person();
     console.log(person.name) // 大灰狼
    
  • 箭头函数

    1. 先简单的说下概念:
      • 更短的函数
      • 没有单独的this
      • 严格模式在箭头函数中不起作用
      • 通过call apply bind 不能修改函数中this的指向
      • 不能绑定arguments(这里可以用剩余运算符 ... 获取对应传入的参数) ,super或new.target
      • 不能作为构造构造器,不能与new 一起使用
      • 不能使用prototype属性
      • 通常情况下 yield关键字不能在箭头中使用,因此建有函数不能用作函数的生成器
     function Person() {
       this.age = 0;
       setInterval(function() {
     		console.log(this.age) // 这里的this指向的是window 所以值是undefined
       }, 1000);
     }
     new Person()
    
    1. 解决setTimeout中this指向的问题
     function Person() {
      this.age = 0;
      setInterval(() => {
    		console.log(this.age) // 这里的this 指向上一层的语法环境 所以指向的是Person类new出来的实例对象
      }, 1000);
    }
    new Person()
    
  • IIFE

     (function(){
      console.log(this) // 非严格模式下是this, 严格模式下是undefined
     })()
    

练习部分

 var number = 5;
 var obj = {
    number: 3,
     fn1:(function () { 
         var number;
         this.number *= 2; 
         number = number * 2;
         number = 3; 
         return function () {
             var num = this.number;
             this.number *= 2;
             console.log(num);
             number *= 3;
             console.log(number);
         }
     })()
 }
 var fn1 = obj.fn1;
 fn1.call(null)
 obj.fn1()
 console.log(window.number)
 
 答案: 10 9 3 27 20 

解题步骤:

  1. 主执行栈执行
    • obj.fn 是一个IIFE函数 所以会立即执行, 函数中的this指向window
    • this.number *= 2 全局的number 变成了10
    • number = 3 这个时候局部变量的number变成了3
    • 返回了一个新的函数 作为obj.fn的函数 新函数里面的number就是调用自执行函数定义的number 形成了闭包
     var number = 10;
     var obj = {
     number: 3,
      fn1:(){
              var num = this.number;
              this.number *= 2;
              console.log(num);
              number *= 3;
              console.log(number);
          }
      }
    }
    var fn1 = obj.fn1;
    fn1.call(null)
    obj.fn1()
    console.log(window.number)
    
  2. 执行 fn1.call(null),因为传入的null 此时fn1 里面的this指向window 所以num = 10 全局变量 this.number * 2 之后变了20, 此时外层的number 变成了20 必包中的number之前是3 所以变成了 9 所以输出的值为 20 9, 执行完之后代码变成
  var number = 20;
    var obj = {
    number: 3,
     fn1:(){
     		 // 必包中的number 变成了9
             var num = this.number;
             this.number *= 2;
             console.log(num);
             number *= 3;
             console.log(number);
         }
     }
   }

  1. obj.fn1()调用的时候 fn1 里面的this指向了obj, 此时num = 3, this.number * 2 = 6, 必包作用域中的number *= 3 变成了27 所以输出结果为 3 27, 执行之后代码变成
  var number = 20;
  var obj = {
  number: 6,
  fn1:(){
         // 必包中的number 变成了27
           var num = this.number;
           this.number *= 2;
           console.log(num);
           number *= 3;
           console.log(number);
       }
   }
 }
  1. window.numbwe 此时的结果为20

capp apply bind的简单实现

 const getContext = (context) => {
	context = context || window;
    let type = typeof context;
    if (type !== 'object') { // 如果是基本类型 包装成引用类型
      context = Object(context)
    }
    return context
}
  1. call简单实现
 Function.prototype.call = function (context, ...args) {
   context = getContext(context);
   let fn = Symbol('fn');
   context[fn] = this;
   let result = context[fn](...args);
   delete context[fn];
   return result;
 }
  1. apply简单实现
 Function.prototype.apply = function (context, args) {
   context = getContext(context);
   let fn = Symbol('fn');
   context[fn] = this;
   let result = context[fn](...args);
   delete context[fn];
   return result;
 }
  1. bind简单实现
  Function.prototype.bind = function (context, ...outerArgs) {
    return (...innerArgs) => this.call(context, ...outerArgs, ...innerArgs)
  }