【前端学习笔记】JS中的this

118 阅读5分钟

-----------------------------如有错误欢迎指正感激不尽~---------------------------------

主要参考:

《你不知道的JavaScript 上卷》

《现代JavaScript教程》

1.为什么要用this

this提供了一种更优雅的方式来隐式‘传递’一个对象引用,因此可以将API设计得更加简洁并且容易复用。

例子:

function identify(){
  return this.name.toUpperCase();
}
function speak(){
  var greeting = 'hello,i'm '+identify.call(this);
  console.log(greeting)
}

var me = {name:'kyle'}
identify.call(me);//KYLE
speak.call(me);//hello,i'm kyle

如果不使用this,就要显式地传入一个上下文对象:

function identify(context){
  return context.name.toUpperCase()
}
function speak(context){
  var greeting = 'hello,i'm '+identify.call(context);
  console.log(greeting)
}

var me = {name:'kyle'}
identify(me);//KYLE
speak(me);//hello,i'm kyle

2.this并非指向自身

function foo(num){
  console.log('foo:',num)
  this.count++;
  console.log('this',this) //------------------------> window
  console.log('this.count',this.count) //------------------> NaN,因为count为undefined,不能++
}
foo.count = 0;
var i;
for(i = 0;i<10;i++){
  if(i>5){
    foo(i)
  }
}
//foo:6
//foo:7
//foo:8
//foo:9

//foo被调用多少次?
console.log(foo.count)
//-------------------> 0
//可以证明this不是指向自身

如果需要从函数内部引用它自身,使用this是不够的,一般需要通过一个指向函数对象的词法标识符(变量)来引用它。

//方法1:------------具名函数
function foo(){
  foo.count = 4; //foo指向自身
}
//如果是匿名函数,可以用arguments.callee来引用当前正在运行的函数对象(已经弃用

//方法2:-------------强制this指向foo函数对象
function foo(num){
  console.log('foo:',num)
  this.count++;
}
foo.count = 0;
var i;
for(i = 0;i<10;i++){
  if(i>5){
    foo.call(foo,i);
  }
}
//foo被调用多少次?
console.log(foo.count)//4

3.this也不指向函数的词法作用域

function foo(){
  var a = 2;
  this.bar(); //引用bar最自然的方式是去掉this, bar里的this为window
}
function bar(){
  console.log(this);
  console.log(this.a);
}
foo();    //ReferenceError: a is not defined
          //foo在这里调用,则this为window

因为

function foo(){
  console.log(this);    
}
foo(); //-------------------------> window

4.this是什么

this是运行时绑定的,它的上下文取决于函数调用的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。

当一个函数被调用,会创建一个活动记录(执行上下文),这个记录会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this就是这个记录的一个属性,会在执行过程中用到。

5.绑定规则

5.1默认绑定

直接使用不带任何修饰的函数引用进行调用,则应用默认规则。

非严格模式下,this绑定到全局。

严格模式下,注意是函数本身内部使用严格模式,this为undefiend。

function foo(){
  'use strict';
  console.log(this.a);
}
var a = 2;
foo(); //----------------->TypeError:this is undefined

另外,在严格模式调用那个函数则无影响:

function foo(){
  console.log(this.a);
}
var a = 2;
(function(){
  'use strict';
  foo(); //---------------->2 this还是绑定到了全局
})()

5.2隐式绑定

调用位置有上下文对象,则this绑定到那个上下文对象。

function foo(){
  console.log(this.a);
}
var obj = {
  a:2,
  foo:foo
}
obj.foo();//-------------->2

注意1,对象属性引用链中只有最后一层起作用:

function foo(){
  console.log(this.a);
}
var obj2 = {
  a:2,
  foo:foo
}
var obj1 = {
  a:1,
  obj2:obj2
}
obj1.obj2.foo();//-------------->2  只有obj2起作用

注意2, 隐式丢失:被隐式绑定的函数可能会丢失绑定对象,从而把this绑定到全局对象或者undefined上(取决于严格模式)

情况1:使用函数别名var bar = obj.foo

function foo(){
  console.log(this.a);
}
var obj = {
  a:2,
  foo:foo
}
var a = 100;
var bar = obj.foo; //--------------------------> 注意这行,引用的是foo本身
bar(); //------------------->100

情况2:传参doFoo(obj.foo)

function foo(){
  console.log(this.a);
}
function doFoo(fn){
  fn();
}
var obj = {
  a:2,
  foo:foo
}
var a = 'oops,global'
doFoo(obj.foo); //------------->实际上是 令fn = obj.foo,与情况一类似

情况3:使用回调函数

function foo(){
  console.log(this.a);
}
var obj = {
  a:2,
  foo:foo
}
var a = 'oops,global'
setTimeout(obj.foo,1000);//-------------->'oops,global'

//------------------原因
//回调函数和下面代码类似:
function setTimeout(fn,delay){  //------------>fn = obj.foo
  //等待delay秒
  fn();
}

5.3显示绑定

使用call(...)、apply(...)、bind(...)、API调用的上下文。

如果在call、apply里传入一个原始值(字符串类型、布尔类型、数字类型)来当作this的绑定对象,这个原始值会被转换成它的对象形式(new String()、new Boolean()、new Numebr()),称为装箱。

var bar = foo.bind(obj): bind(..)是内置方法Function.prototype.bind(),会返回一个硬编码的新函数,把参数设置为this的上下文并调用原始函数。

API调用的‘上下文’:即内置函数里面的可选参数,如[1,2,3].forEach(foo,obj), 调用foo时把this绑定到obj

//-----

装箱:

定义:把基本数据类型转换为对应的引用类型的操作称为装箱。

//-----

拆箱

定义:定义:把引用类型转换为基本的数据类型称为拆箱。

5.4new 绑定

使用new操作符时,会执行以下操作:

1.创建(或者说构造)一个全新的对象;

2.这个新对象会被执行[[prototype]]连接;

3.这个新对象会绑定到函数调用的this;

4.如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。

5.5绑定的优先级

高----->低

new -----> call/apply/bind --------------> 上下文 ------------> 默认

5.6一些例外

  1. ) 将null或者undefined传入call、apply、bind,

这些值调用时会被忽略,实际应用默认规则。

什么时候会传入null?bind对参数进行柯里化时(预先设置一些参数)、用apply展开数组

此时null的作用相当于占位符

function foo(a,b){
  console.log('a:'+a+',b:'+b);
}
//展开数组
foo.apply(null,[2,3]); //a:2,b:3

//柯里化
var bar = foo.bind(null,2);
bar(3);
//a:2,b:3

但这种方法会有副作用(如果函数里面有this,容易修改全局对象),更安全的方法是创建一个空的委托对象。

let dmz = Object.create(null);

//和{}很像,但没有Object.pototype这个委托,所以比{}更空;this会被限制在这个空对象

foo.apply(dmz,[2,3])

2.) 间接引用

function foo(){
  console.log(this.a)
}
var a = 2;
var o = {a:3,foo:foo}
var p = {a:4}

o.foo(); //--------------->3
(p.foo = o.foo)(); //------------->2
//因为:
console.log(p.foo = o.foo) //-----------> function foo()

3.)箭头函数

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

箭头函数的绑定无法修改(new也不行

常用于回调函数中,例如事件处理器或者定时器:

function foo(){
  let a = 1;
  setTimeout(()=>{
    console.log(this.a) //------------> 这里的this在词法上继承自foo
  },1000)
}

我的GitHub:github.com/6fa