this

95 阅读2分钟

this----你不知道的JavaScript(上)

问题:重复使用一个函数,但引入不同的上下文对象,需要动态根据引入的对象编写不同版本的函数,引入this可自动引用合适的上下文对象。

function identify(){
  return this.name.toUpperCase();
}
var me = {name:'kk'}
var you = {name:'ll'}
identify.call(me);//KK
identify.call(you);//LL

一、this的误解:

  1. this指向函数本身。

this并不指向函数本身。

function foo(num){
  console.log('foo:'+num);
  //记录foo调用次数
  this.count++;
}
foo.count=0;
for(var i=0;i<5;i++){
  if(i>1){
    foo(i);
  }
}
//打印foo:2,3,4三次
console.log(foo.count)//0

上述证明,this不指向函数本身。

解决方案一,创建一个带有count变量的对象data,用data.count引用。

function foo(num){
  console.log('foo:'+num);
  //记录foo调用次数
  data.count++;
}
var data = {
  count:0
}
foo.count=0;
for(var i=0;i<5;i++){
  if(i>1){
    foo(i);
  }
}
console.log(data.count)//3

解决方案二:将this换为foo来引用,即为foo.count++;

解决方案三:在foo被调用时,强制this指向foo函数对象。

foo(i)----->foo.call(foo,i)

  1. this指向函数作用域。

this不指向函数的作用域。不能用this来引用一个作用域内部的东西。

二、this的理解:

this在运行时绑定,只取决于函数的调用方式。函数被调用时,创建一个执行上下文记录函数在哪里被调用、函数的调用方法、传参等信息。

调用位置:函数被调用的位置。最重要是分析调用栈(为了达到当前执行位置所调用的所有函数)

function baz(){
  //调用栈baz
  //调用位置全局作用域
  console.log('baz')
  bar();//<-bar调用位置
}

function bar(){
  //调用栈baz->bar
  //调用位置baz中
  console.log('bar')
  foo();//<-foo调用位置
}

function foo(){
  //调用栈baz->bar->foo
  //调用位置在bar中
  console.log('foo')
}
  baz();//<-baz调用位置

三、this的绑定:

  1. 独立函数调用:this指向全局作用域,默认绑定全局。
function foo(){
  console.log(this.a)
}
var a = 2;
foo();
//打印2

非严格模式下指向全局作用域。严格模式下,this会绑定undefined。

因为foo是直接使用不带任何修饰的函数引用进行调用的,只能使用默认绑定。

  1. 隐式绑定:指向上下文对象(调用时所处的执行上下文)
  • 对象属性引用链中只有最顶层或者说最后一层会影响调用位置。

隐式绑定需要在一个对象内部包含指向函数的属性,通过属性间接引用函数,从而间接绑定到对象上。

function foo(){
  console.log(this.a)
}
var obj2 = {
  a:42foo:foo
}
var obj1 = {
  a: 2obj1: obj1
}
obj1.obj2.foo();//42
  • 隐式丢失:被隐形绑定的函数会丢失绑定对象,会用默认绑定。
function foo(){
  console.log(this.a)
}
var obj = {
  a:2foo:foo
}
var bar = obj.foo;
var a = 'ohhhh!!'
bar()//'ohhhh!!'

bar是obj.foo的引用。实际上,引用的是foo本身,因此bar是一个不带任何修饰的函数调用,应用默认绑定。

当传入回调函数时,参数传递就是一种隐式赋值。和上述情况一样默认绑定。

function foo(){
  console.log(this.a)
}

function doFoo(fn){
  //fn引用的是foo
  fn();//<-调用位置
}

var obj = {
  a:2foo:foo
}
var a = 'ohhhh!!'
doFoo(obj.foo)//'ohhhh!!'
  1. 显式绑定:call(fn(),arguement),apply(fn(),arguement))

隐式绑定需要对象的属性指向函数。当不想在函数内部包含函数引用而又想在对象中调用函数,那么需要call,apply。调用函数的同时指定this。

this只能显示绑定一次,再绑定也不会改。

  1. new构造函数
fucntion foo(a){
  this.a = a;
}
var bar = new foo(2);
console.log(bar.a)//2

构造函数的this指向对象。

  1. 优先级

默认绑定<隐式绑定<显式绑定<new

new相当于创建了一个新的对象,与之前的绑定无关,新建了一个对象绑定了函数的this。

先看有没有new,再看显式绑定,如果都没有就默认绑定。

  1. 例外

当apply绑定的是null时,用于将数组展开成参数。

function foo(a,b){
  console.log('a:'+a+',b:'+b)
}
foo.apply(null,[2,3])

当bind绑定的是null时,可进行柯里化。

  1. 箭头函数this

this指向箭头函数所在的上下文,箭头函数的绑定无法被修改。

检测所学。做一下下面这道题:

function Foo() {
    getName = function () { alert (1); };
    return this;
}
Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
var getName = function () { alert (4);};
function getName() { alert (5);}

//请写出以下输出结果:
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();

正确结果:

Foo.getName();//2
getName();//4
Foo().getName();//1
getName();//1
new Foo.getName();//2
new Foo().getName();//3
new new Foo().getName();//3

看代码,首先,声明了一个foo函数,然后给foo添加了一个getname属性。

  • Foo.getName()访问属性,为2.
  • getName()在代码中有两个位置涉及到,一个是函数表达式var getName= function(){};一个是function getName(){}声明。之前在词法作用域部分,提到了函数声明会提升,然后运行函数表达式,表达式的4覆盖了声明的5,此处显示4.
  • foo()调用完,return的是this作用域,然后调用其中的getName属性,但注意到,此处并没有var声明getName,所以是getName赋值。那么getName是哪的,也就是第二个涉及到的全局的getName,将其赋值为1.显示为1.
  • 通过第三个,已经修改了getName赋值为1,所以显示为1.
  • new Foo.getName()先进行属性调用,foo.getName取到的是2这个函数,然后new当做构造函数调用,显示2
  • new Foo().getName()先执行foo()得到了this,然后new。new一个构造函数只有当返回一个对象的时候,结果为对象,否则new完之后得到的都是实例化对象。此时new完得到的就是实例化对象,访问其getName属性。但构造函数中没有定义任何属性,因此访问原型链得到3.
  • new Foo()得到的实例化对象,实例化对象的getName属性,得到3函数,再实例化该构造函数。显示3.

题目2:

/**
 * Question 2
 */
var name = 'window'

function Person (name) {
  this.name = name;
  this.show1 = function () {
    console.log(this.name)
  }
  this.show2 = () => console.log(this.name)
  this.show3 = function () {
    return function () {
      console.log(this.name)
    }
  }
  this.show4 = function () {
    return () => console.log(this.name)
  }
}

var personA = new Person('personA')
var personB = new Person('personB')

personA.show1()//personA
personA.show1.call(personB)//personB

personA.show2()//personA
personA.show2.call(personB)//personA

personA.show3()()//window
personA.show3().call(personB)//personB
personA.show3.call(personB)()//window

personA.show4()()//personA
personA.show4().call(personB)//personA
personA.show4.call(personB)()//personB
  • show1是一般函数的this,调用和绑定时候均正常显示,personA和personB
  • show2为箭头函数的this,直接调用的时候,this指向箭头函数所在的上下文的this,所在上下文的this.name = name即为personA;call绑定的时候,由于箭头函数this指向无法更改,还是指向所在上下文,所以还是personA。
  • personA.show3()调用之后,return了一个包含this的普通函数,普通函数调用,默认this绑定window。

      包含this的普通函数进行call绑定,this指向personB。

personA.show3得到的是包含return的function函数,call绑定该函数。再调用运行,最后返回一个console.log。默认绑定window。

  • personA.show4()得到箭头函数,箭头函数this指向show4的function的函数内部的上下文,也就是person的作用域,因此调用箭头函数后显示personA

箭头函数无法绑定this,因此指向的还是function中的上下文,和上一个一样,是personA。

personA.show4得到function函数,function函数绑定this为personB,call执行完毕后得到立即函数,调用后为personB。