手记

JavaScript this绑定

  1. 理解this

    1.1 为什么使用this

    下面的代码中,当调用对象的方法时,希望将对象的名称一起打印出来

const obj = {
  name: 'marlboroKay',  
  running(){      
    console.log(`${obj.name} can running`); 
  },
  studying(){      
    console.log(`${obj.name} can studying`);    
  },
  playing(){
    console.log(`${obj.name} can playing`);
  },  
}
obj.running(); //marlboroKay can running
obj.studying(); //marlboroKay can studying
obj.playing(); //marlboroKay can  playing

但是上面的代码存在一个弊端,如果将对象名称obj修改为 object,那么所有方法中的obj.name都需要替换成object.name。

    在日常的业务代码中,通常会使用this来指向obj

const object = {
  name: 'marlboroKay',
  running(){
    console.log(`${this.name} can running`);
  },
  studying(){
    console.log(`${this.name} can studying`);
  },
  playing(){
    console.log(`${this.name} can playing`);
  },
}
object.running(); //marlboroKay can running
object.studying(); //marlboroKay can studying
object.playing(); //marlboroKay can playing

    所以,在某些函数或者方法中,this可以更加清晰便捷的引用对象。使代码逻辑变得清晰且易于复用

    1.2  this指向什么?

//定义函数foo
let foo = function(){
  console.log(this);
}
//1.直接调用
foo(); //Window

//2.将foo放入对象中,再调用
let obj = {
  name: 'marlboroKay',
  foo,
}
obj.foo(); //{name: "marlboroKay", foo: ƒ} obj对象

//3.通过call调用
foo.call("this") //String {"this"}

  通过上面的三种不同调用,可以发现:

      i:函数调用时,JavaScript会默认给this绑定一个值

      ii:this 的绑定和定义的位置(代码的位置)无关,和调用方式和调用位置有关

      iii:this 是在运行时被绑定的

    1.3 this绑定规则

    1.3.1 默认绑定:函数调用时无任何调用前缀

    普通函数调用

 let foo = function(){
   console.log(this);
 }
 foo(); //Window
 //通常默认绑定时,函数中的this指向全局对象(window)
 let foo = function(){
   'use strict'
   console.log(this);
 }
 foo(); //undefined
 //严格模式下,指向undefined

    函数调用链(一个函数内部调用另一个函数)

  function foo1(){
    console.log(this);
    foo2();
  }
  function foo2(){
    console.log(this);
    foo3();
  }
  function foo3(){
    console.log(this);
  }
  foo1();
  // Window
  // Window
  // Window
  //所有函数在被调用时,都没有调用前缀,所以仍然使默认绑定

    函数作为参数,传入到另一个函数中

  function foo(func){
    func();
  }
  function bar(){
    console.log(this);
  }
  foo(bar); // Window

    函数作为参数扩展

  function foo(func){
    func();
  }
  const obj = {
    name: 'marlboroKay',
    bar(){
      console.log(this);
    }
  }
  foo(obj.bar); //Window

    当foo(obj.bar)执行时, 当前位置没有任何对象绑定,仍然是一个默认绑定。(可以看成跟3.函数作为参数,传入到另一个函数中)

    1.3.2 隐式绑定:函数调用的所在位置中,是通过某个对象发起的函数调用

    对象调用函数

  let foo = function(){
    console.log(this);
  }
  const obj = {
    name: 'marlboroKay',
    foo,
  }
  obj.foo(); //{name: "marlboroKay", foo: ƒ} obj对象

    foo调用的位置为obj.foo(),当foo调用时,会隐式的绑定到obj对象上

    就近调用

  let foo = function(){
    console.log(this);
  }
  const obj = {
    name: 'marlboroKay',
    foo,
  }
  const obj2 = {
    name: 'obj2',
    obj,
  }
  obj2.obj.foo() //{name: "marlboroKay", foo: ƒ} obj对象

    隐式丢失

  let foo = function(){
    console.log(this);
  }
  const obj = {
    name: 'marlboroKay',
    foo,
  }
  let bar = obj.foo;
  bar() //Window

    foo最终被调用的位置是bar,而bar在调用时,没有绑定任何对象,也就没有产生隐式绑定。(相当于一个默认绑定,所以this最终指向Window)

    1.3.3 显示绑定

    隐式绑定有个前提:函数调用的所在位置中,是通过某个对象发起的函数调用。但是如果不想通过对象引用函数的方式来进行函数调用,又同时想在这个对象上进行强制的函数调用,该如何处理呢?

    JavaScript 中所有的函数都可以使用call和apply方法

    call和apply的区别,简单描述:第一个参数时相同的,第二个参数,call为参数列表,apply为数组。第一个参数要求传入为对象,当调用call或apply时,会将this绑定到出入的对象上。

    由于明确将this绑定到传入的对象上,所以这种绑定方式称为显示绑定。

    call绑定this对象

  let foo = function(){
    console.log(this);
  }
  foo.call(window); //Window
  foo.call(666); //Number {666}
  foo.call({name: 'marlboroKay'}) //{name: "marlboroKay"}

    bind绑定:一个函数总是显示绑定到一个对象上,可以采用bind()。bind()会返回一个新的函数,把this绑定到第一个传入的参数上

  let foo = function(){
    console.log(this);
  }
  const obj = {
    name: 'marlboroKay',
  }
  let bar = foo.bind(obj);
  bar(); // {name: "marlboroKay"}

    内置函数

  setTimeout(function(){
    console.log(this)
  }, 1000) //Window
  //setTimeout会传入一个回调函数,该函数的this通常时指向Window
  
  [1, 2, 3].forEach(function(){
    console.log(this)
  })
  //Window
  //Window
  //Window
  //forEach()会传入一个回调函数,默认情况下,执行默认绑定
  //forEach()第二个可选参数,可以设置this绑定对象
  
  [1, 2, 3].forEach(function(){
    console.log(this)
  }, 'marlboroKay')
  //String {"marlboroKay"}
  //String {"marlboroKay"}
  //String {"marlboroKay"}

    1.4 new 绑定

    JavaScript中,用new修饰的函数,为函数的构造调用,new关键字调用函数时,会执行如下操作:

    i:创建一个全新的对象

    ii:新对象的 \\_\\_proto\\_\\_ 指向原函数的 prototype属性。(继承原函数的原型)

    iii:新对象会绑定到函数调用的this上。(this绑定在这一步完成)

    iiii:如果原函数没有返回其他对象,则返回这个新对象

  function Foo(name){
    console.log(this);
    this.name = name;
  }
  let foo = new Foo('M');
  console.log(foo);
  //Foo {}
  //Foo {name: "M"}

    使用new调用函数后,函数会原函数名称命名并创建一个新的对象,并返回

    注意:如果原函数返回一个对象类型,则无法返回新对象,将丢失this绑定的新对象

  function Foo(name){
    this.name = name;  
    return {name: 'MarlboroKay'}
  }
  let foo = new Foo('M');
  console.log(foo) //{name: "MarlboroKay"}
  console.log(foo.name) // MarlboroKay

    1.5 规则优先级

    默认规则的优先级是最低的,因为存在其他规则时,就会通过其他规则的方式来绑定this

    显示绑定与隐式绑定谁的优先级更高呢?

  function foo(){
    console.log(this);
  }
  const obj = {
    name: 'obj',
    foo,
  }
  const obj2 = {
    name: 'obj2',
    foo,
  }
  obj.foo() //{name: "obj", foo: ƒ}
  obj2.foo() //{name: "obj2", foo: ƒ}
  obj.foo.call(obj2) //{name: "obj2", foo: ƒ}

    说明显示绑定 > 隐式绑定

    new绑定与隐式绑定,显示绑定的优先级谁更高呢?

    new 与 隐式绑定:

  function foo(){
    console.log(this);
  }
  const obj = {
    name: 'obj',
    foo,
  }
  new obj.foo(); //foo {}

    说明new > 隐式绑定

    new 与 显示绑定(call,apply):

  function foo(){
    console.log(this);
  }
  const obj = {
    name: 'obj',
  }
  let bar = new foo.call(obj); 
  //Uncaught TypeError: foo.call is not a constructor

    new绑定和call、apply是不允许同时使用的,所以不存在谁的优先级更高

    new 与 显示绑定(bind):

  function foo(){
    console.log(this);
  }
  const obj = {
    name: 'obj',
  }
  let bar = foo.bind(obj);
  new bar(); // foo {}

    说明new > bind

    优先级总结:new绑定 > 显示绑定 > 隐式绑定 > 默认绑定

    2. this规则之外

    2.1 忽略显示绑定:

    如果在显示绑定中,传入一个null或者undefined,那么这个显示绑定会被忽略,使用默认规则:

  function foo(){
    console.log(this);
  }
  const obj = {
    name: 'obj',
  }
  foo.call(obj) // {name: "obj"}
  foo.call(null) //Window
  foo.call(undefined) //Window

    2.2 函数的间接引用

  //间接引用
  let num = 1;
  let num2 = 2;
  let res = (num = num2)
  ;console.log(res) //2
  
  //函数间接引用
  function foo(){
    console.log(this);
  }
  const obj = {
    name: 'obj',
    foo,
  }
  const obj2 = {
    name: 'obj2',
  }
  obj.foo(); //{name: "obj", foo: ƒ}
  (obj2.foo = obj.foo)(); // Window

    (obj2.foo = obj.foo)的结果是 foo 函数,foo函数被直接调用,是默认绑定

    2.3 ES6 箭头函数:

    箭头函数不使用this的四种标准规则(也就是不绑定this),而是根据外层作用域来决定this

  //隐式绑定丢失
  var name = 'THIS';
  const obj = {
    name: 'marlboroKay',
    getName(){
      console.log(this.name);
    },
  }
  let foo = obj.getName;
  foo(); //"THIS"
  
  //ES6 箭头函数
  var name = 'THIS';
  const obj = {
    name: 'marlboroKay',
    getName(){
      return () => {
        console.log(this.name)
      }
    },
  }
  let foo = obj.getName();
  foo(); //marlboroKay

    因为箭头函数并不绑定this对象,那么this引用就会从上层作用域中找到对应的this。

    如果getName() 也是一个箭头函数,那么foo()会返回什么呢?

  const obj = {
    name: 'marlboroKay',
    getName: () => {
      return () => {
        console.log(this);
      }
    },
  }
  let foo = obj.getName();
  foo(); //Window

    依然是不断的从上层作用域找,那么找到了全局作用域,在浏览器的全局作用域内,this代表的就是window

    3. this 面试题

    3.1 

  var name = 'Window';
  const obj = {
    name: 'MarlboroKay',
    getName(){
      console.log(this.name);
    },
  }
  function getName(){
    let gName = obj.getName;
    gName();
    obj.getName();
    (obj.getName)();
    (temp = obj.getName)()
  }
  getName();

    ----------------------答案分割线----------------------

//Window
//MarlboroKay 隐式绑定
//MarlboroKay
//Window

    3.2 

  var name = 'Window';
  const obj = {
    name: 'MarlboroKay',
    foo(){
      console.log(this.name);
    },
    foo2:() => console.log(this.name),
    foo3(){
      return function(){
        console.log(this.name);
      }
    },
    foo4(){
      return () => {
        console.log(this.name)
      }
    },
  }
  const obj2 = {name: 'obj2'}
  
  obj.foo();
  obj.foo.call(obj2);
  
  obj.foo2();
  obj.foo2.call(obj2);
  
  obj.foo3()();
  obj.foo3.call(obj2)();
  obj.foo3().call(obj2);
  
  obj.foo4()();
  obj.foo4.call(obj2)();
  obj.foo4().call(obj2);

      ----------------------答案分割线----------------------

//MarlboroKay 隐式绑定
//obj2 显示绑定 > 隐式绑定

//Window foo2为箭头函数,不满足this规则,所以返回window.name
//Window 同理

//Window foo3返回一个函数,调用时在全局作用域,所以返回window.name
//Window foo3显示绑定到obj2,但是返回的函数调用时在全局作用域,所以返回window.name
//obj2 foo3返回函数,显示绑定到obj2,所以返回obj2.name

//MarlboroKay foo4返回箭头函数,箭头函数查找上一层作用域,所以返回obj.name
//obj2 foo4显示绑定到obj2,并返回一个箭头函数,箭头函数查找上一层作用域,所以返回obj2.name
/*MarlboroKay foo4返回的箭头函数显示绑定到obj2,但箭头函数不满足显示绑定规则,仍然查找上一层作用域,
所以返回obj1.name*/

    3.3

  var name = 'Window';
  function GetThisName(name){
    this.name = name;
    this.foo2 = () => console.log(this.name);
  }
  GetThisName.prototype.foo = function(){
    console.log(this.name);
  }
  GetThisName.prototype.foo3 = function(){
    return function(){
      console.log(this.name);
    }
  }
  GetThisName.prototype.foo4 = function(){
    return () => {
      console.log(this.name);
    }
  }
  let g1 = new GetThisName('g1');
  let g2 = new GetThisName('g2');
  
  g1.foo();
  g1.foo.call(g2);
  
  g1.foo2();
  g1.foo2.call(g2);
  
  g1.foo3()();
  g1.foo3.call(g2)();
  g1.foo3().call(g2);
  
  g1.foo4()();
  g1.foo4.call(g2)();
  g1.foo4().call(g2);

    ----------------------答案分割线----------------------

  //g1 隐式绑定
  //g2 显示绑定 > 隐式绑定
  
  //g1 foo2返回箭头函数,箭头函数查找上一层作用域,返回g1.name
  //g1 foo2返回箭头函数,显示绑定不生效,返回g1.name
  
  //Window foo3返回一个函数,调用时在全局作用域,所以返回window.name
  //Window foo3显示绑定到g2,但是返回的函数调用时在全局作用域,所以返回window.name
  //g2  foo3返回函数,显示绑定到g2,所以返回g2.name
  
  //g1 foo4返回箭头函数,箭头函数查找上一层作用域,所以返回g1.name
  //g2 foo4显示绑定到g2,并返回一个箭头函数,箭头函数查找上一层作用域,所以返回g2.name
  //g1 foo4返回的箭头函数显示绑定到g2,但箭头函数不满足显示绑定规则,
  //仍然查找上一层作用域,所以返回g1.name

    3.4 

  var name = 'Window';
  function GetThisName(name){
    this.name = name;
    this.obj = {
      name: 'obj',
      foo(){
        return function(){
          console.log(this.name);
        }
      },
      foo2(){
        return () => {
          console.log(this.name);
        }
      },
    }
  }
  let g1 = new GetThisName('g1');
  let g2 = new GetThisName('g2');
  g1.obj.foo()();
  g1.obj.foo.call(g2)();
  g1.obj.foo().call(g2);
  
  g1.obj.foo2()();
  g1.obj.foo2.call(g2)();
  g1.obj.foo2().call(g2);

     ----------------------答案分割线----------------------

  //Window 返回一个函数,默认绑定
  //Window 虽然显示绑定到g2,但是函数调用时在全局作用域下,默认绑定
  //g2 foo返回的函数,显示绑定到g2,所以返回g2.name
  
  //obj foo2返回一个箭头函数,箭头函数调用时,查找上一层作用域,返回obj.name
  //g2 foo2 显示绑定到g2,箭头函数调用时,查找上一层作用域,返回g2.name
  //obj foo2返回的箭头函数,显示绑定到g2,但是箭头函数不满足显示绑定规则,查找上一层作用域,返回obj.name

    3.5

  var num1 = '66';
  const obj = {
    num1: 99,
    foo(){
      console.log(this.num1);
      function bar(){
        console.log(this.num1)
      }
      bar();
    },
  }
  obj.foo();

      ----------------------答案分割线----------------------

//99 隐式绑定
//66 默认绑定(bar在调用时,没有显示和隐式绑定,处于全局作用域)

    3.6

  var num1 = 66;
  const obj = {
    num1: 99,
    foo(){
      console.log(this.num1);
    },
  }
  let bar = obj.foo;
  const obj2 = {
    num1: 33,
    foo:obj.foo,
  }
  obj.foo(); //?
  bar();//?
  obj2.foo() //?

     ----------------------答案分割线----------------------

//99 隐式绑定
//66 显示绑定
//33 隐式绑定(this的绑定与定义时无关,与调用时的位置和调用方式有关)

    3.7

  function foo(val){
    this.a = val;
      return this;
  }
  var a = foo(33);
  var b = foo(66);
  console.log(a.a); //?
  console.log(b.a); //?

     ----------------------答案分割线----------------------

//undefined
//66
/* 
foo(33),相当于默认引用,this指向window,此时this.a 等同于 window.a = 33
return this 等同于 return window
所以,var a = foo(33) 等同于 var a = window
但需要注意,此时var声明的a,属于全局作用域的window下,也就是:window.a = window
如果后续没有其他操作,则此时 console.log(a.a) 为 Window
当 foo(66) 执行时,此时,window.a = 66,return window
同理:window.b = window,b.a 等同于window.a 为66。
所以,console.log(a.a) 等同于 console.log(66.a) === undefined
console.log(b.a) 等同于 console.log(window.a) === 66
*/

    3.8

  function foo(){
    getName = function(){
      console.log(1);
    }
    return this;
  }
  foo.getName = function(){console.log(2)};
  foo.prototype.getName = function(){console.log(3)};
  var getName = function(){console.log(4)};
  function getName(){console.log(5)};
  foo.getName(); //?
  getName(); //?
  foo().getName(); //?
  getName(); //?
  new foo.getName(); //?
  new foo().getName(); //?
  new new foo().getName(); //?

    可以先仔细做一下,仔细思考。(答案参考评论区)

3人推荐
随时随地看视频
慕课网APP

热门评论

https://www.mooyuu.com/user/89564/

https://www.mooyuu.com/user/89564/infos.html

https://www.mooyuu.com/user/89564/favs.html

https://my.oschina.net/jjgjgj

https://www.changchenghao.cn/user/2119


2, 4, 1, 1, 2, 3, 3

//2 不要与foo.prototype混淆,foo.prototype 是在new 调用时,重新绑定一个foo对象,并将getName = 3 绑定到新的foo上
//4 函数表达式,变量声明提升,5会被4覆盖。
//1 
/*
 foo() 默认绑定,return this 等同于 return window,并且此时,定义了一个全局函数getName(),
 当foo()执行完后,此时window.getName 等同于 console.log(1),将之前的console.log(4)覆盖
 */
 //1 理由同上
 //2 new 调用 foo.getName(),执行console.log(2),返回一个foo.getName 对象。
 //3 new foo().getName() 等同于 const obj = new foo(), obj.getName(),此时的getName是绑定到prototype上的。
 //3 
 /* 
 有一点绕,new new foo().getName() 等同于, const obj = new foo(),const obj2 = new obj.getName()
 obj.getName() 输出3,所以const obj2 输出3,返回一个foo.getName 新对象
 (其实,第7个console等同于第5和第6的结合)
 */


查看全部评论