JavaScript中的this - 笔记

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript中的this - 笔记相关的知识,希望对你有一定的参考价值。

之前一直对this的指向很模糊,找了一些别人的博客看,又重新看了一下《你不知道的javascript》,感觉基本上是弄懂了,挑一些重点的地方记录一下,有些地方对我来说书上解释写的不够多,所以自己做下补充以方便理解,有理解错的地方还望指出。

 

一.澄清误区


 

首先你需要知道:

1.this并不指向函数自身

2.this的作用域在任何情况下都不指向函数的词法作用域。

举个例子:

function foo() {
    var a = 2;
    this.bar();
}

function bar() {
    console.log(this.a);
}

foo();//ReferenceError: a is not defined

第一次看这里的时候就掉坑里了,想当然的以为foo()中this指向window,而bar()又在全局中故能调用bar(),但实际上这是错的,不能使用this来引用一个词法作用域内部的东西。

this是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里调用。跟函数声明的位置没有任何关系,要区别于函数的作用域,函数的作用域是在它被定义的时候确定的。

要判断一个运行中函数的this绑定,需要找到这个函数的直接调用位置。

 

二.调用位置


 

调用位置就是函数在代码中被调用到的位置。

this有四条绑定规则:

 

1.默认绑定:非严格模式下this指向全局对象,严格模式绑定到undefined。 var bar = foo()

2.隐式绑定:this指向包含它的函数的对象,要注意隐式丢失的情况。 var bar = obj1.foo()

3.显示绑定:this指向call()、apply()和bind()方法指定的对象。 var bar = foo.call(obj2)

4.new绑定:this指向构造的新对象。 var bar = new foo()

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

 

具体看下面:

1.默认绑定

无法应用其他规则时默认使用默认绑定。如果函数独立调用,使用默认绑定。

function foo() {
//看函数体是否处于严格模式,是看这个位置(
"use strict"
  console.log(this.a);
}

var a = 2;

foo(); //2  仅foo()函数本身,为独立调用

在上面的代码中,foo()是使用不带任何修饰的函数引用进行调用的,所以会使用默认绑定,非严格模式下this指向全局对象,严格模式绑定到undefined。不是指调用位置是否处于严格模式,而是函数体是否处于严格模式。

 

2.隐式绑定

考虑函数的调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含。

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

var obj = {
  a: 2,
  foo: foo  //foo()被当作引用属性添加到obj中,此时它被obj对象包含,此时this指向obj
}

obj.foo(); //2

对象属性引用链中只有最后一层会影响调用位置。

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

var obj2 = {
  a: 42,
  foo: foo
}

var obj1 = {
  a: 2,
  obj2: obj2
}

obj1.obj2.foo();  //42  虽然这里有obj1对象和obj2对象,但是obj2才是处于最后一层最接近foo(),所以会指向obj2

 

隐式丢失的情况

来看下面的代码:

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

var obj = {
  a: 2,
  foo: foo
}

var bar = obj.foo;  //虽然bar是obj.foo的一个引用,但实际上bar引用的只是foo函数本身,可以看成bar() = foo(),此时foo()是独立调用故绑定到全局对象
var a = "oops, global";

bar(); //oops, global  

还有一种常见的隐式丢失的情况是传入回调函数

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

function doFoo(fn) {
  fn();
}

var obj = {
  a: 2,
  foo: foo
}

var a = "oops, global";

doFoo(obj.foo); //oops, global  obj.foo作为参数传入实际上隐式赋值给了fn,可以看成fn = obj.foo,
//此时又回到了上一例代码的情况,引用的是foo函数本身,看成fn()= foo(),独立调用指向全局对象

 

3.显示绑定

使用函数的call()和apply()方法可以使用显示绑定,这两个方法的第一个参数都是一个对象,他们会把这个对象绑定到this,在调用函数时指定这个this,因为可以指定绑定对象故称做显示绑定。

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

var obj = {
  a: 2
}

foo.call(obj);

但是这种显示绑定没有解决隐式丢失的问题,要解决这个问题可以使用硬绑定。

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

var obj = {
  a: 2
}

var bar = function() {
  foo.call(obj);
}

bar(); //2

setTimeout(bar, 100) //2

bar.call(window); //2

创建了一个函数bar(),并在它的内部手动调用foo.call(obj),因此强制把foo的this绑定到了obj,之后无论如何调用函数bar,它总会手动在obj上调用foo,这叫做硬绑定。

硬绑定非常常用,ES5中提供了内置的方法Function.prototype.bind,bind()会返回一个硬编码的新函数,它会把参数设置为this的上下文并调用原始函数。

硬绑定的两个典型应用场景:

  • 创建一个包裹函数,传入所有的参数并返回接收到的所有值
function foo(something) { 
  console.log(this.a, something);
  return this.a + something; //返回接收到的所有this.a和something
}

var obj = {
  a: 2
}

var bar = function() {
  return foo.apply(obj, arguments); //this指向obj,arguments为传入的参数3
}

var b = bar(3); //2 3
console.log(b); //5
  • 创建一个可以重复使用的辅助函数
function foo(something) {
  console.log(this.a, something);
  return this.a + something;
}

function bind(fn, obj) {
  return function() {
    return fn.apply(obj, arguments);
  };
}

var obj = {
  a: 2
}

var bar = bind(foo, obj);
var b = bar(3); //2 3
console.log(b); //5

 

4.new绑定

使用new来调用函数,或者说发生构造函数调用时,会执行下面的操作:

①创建(构造)一个全新的对象。

②这个新对象会被执行[[原型]]连接。

③这个新对象会绑定到函数调用的this。

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

function foo(a) {
  this.a = a;
}

var bar = new foo(2);//使用new操作符调用foo(),构造了一个新对象bar并把它绑定到foo()的this上
console.log(bar.a); //2

 

三.绑定例外

有一些例外情况需要注意

1.把null或undefined作为this的绑定对象传入call、apply或bind。

null和undefined在调用时会被忽略,然后应用默认绑定。

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

var a = 2;

foo.call(null);//2

有时你可能选择null作为一个占位值而选择null作为参数,但是总是用null来忽略this的绑定可能产生一些副作用。一种更安全的做法是传入一个空的非委托对象,把this绑定到这个对象不会对你的程序产生任何副作用。

 

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 p.foo = o.foo的返回值是目标函数的引用,故调用的位置是foo(),又会应用默认绑定

 

3.软绑定 softBind()

硬绑定会降低函数的灵活性,使用硬绑定后就无法使用隐式绑定或显示绑定来修改this。

软绑定:给默认绑定指定一个全局对象和undefined以外的值,可以实现和硬绑定相同的效果,同时保留隐式绑定或显示绑定修改this的能力。

除了软绑定外,softBind()的其他原理和bind()类似。首先检查调用时的this,如果this绑定到全局对象或者undefined,就把指定的对象绑定到this,否则不会修改this。

function foo() {
  console.log("name:" + this.name);
}

var obj = {
  name: "obj"
}
var obj2 = {
  name: "obj2"
}
var obj3 = {
  name: "obj3"
}

var fooOBJ = foo.softBind(obj);

fooOBJ();//obj

obj2.foo = foo.softBind(obj);
obj2.foo();//obj2 obj2.foo()调用时,this绑定到obj2上,不是全局对象也不是undefined,所以不会调用指定的obj,而是使用原来的obj2。

fooOBJ.call(obj3);//硬绑定时绑定了obj3,后面不可再修改

setTimeout(obj2.foo, 10);//假设setTimeout(fn,10),fn实际上引用的只是foo(),可以看成fn = obj2.foo,类似前面的隐式绑定丢失,
//会使用默认绑定到全局,这时会发生软绑定,绑定到指定对象obj

 

4.箭头函数

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

function foo() {
  return (a) => {
    console.log(this.a);
  }
}

var obj1 = {
  a: 2
}

var obj2 = {
  a: 3
}

var bar = foo.call(obj1);
bar.call(obj2);//2 箭头函数会捕获调用时foo()的this,foo()的this会显示绑定到obj1,
//bar(引用箭头函数)的this也会绑定到obj1,箭头函数的绑定无法被修改

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

function foo() {
  setTimeout( () => {
    console.log(this.a);//箭头函数会继承外层函数调用的this绑定,这里箭头函数的外层函数是foo(),foo()的this绑定到obj,所以箭头函数的this也绑定到obj。
  }, 100);
}

var obj = {
  a: 2
}

foo.call(obj);//2

 





以上是关于JavaScript中的this - 笔记的主要内容,如果未能解决你的问题,请参考以下文章

sublime text 3 添加 javascript 代码片段 ( snippet )

Node.js JavaScript 片段中的跳过代码

javascript 跟Aaron大神学习jquery源码笔记

Xitrum学习笔记08 - JavaScript and JSON

48个值得掌握的JavaScript代码片段(上)

JavaScript 代码片段