笔记-你不知道的JS-this

1 this的概念

this 在任何状况下都不指向函数的词法做用域。做用域“对象”没法经过 JavaScript代码访问,它存在于 JavaScript 引擎内部。数组

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

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

2 this的绑定

函数的执行过程当中调用位置如何决定 this 的绑定对象,绑定规则:oop

1 默认绑定this

function foo(){
  console.log(a);
}
var a=2;
foo();

foo() 是直接使用不带任何修饰的函数引用进行调用的,所以只能使用默认绑定,没法应用其余规则。若是使用严格模式(strict mode),那么全局对象将没法使用默认绑定,所以 this 会绑定
到 undefined。prototype

2 隐式绑定
调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含。当 foo() 被调用时,它的落脚点确实指向 obj 对象。当函数引用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。由于调用 foo() 时 this 被绑定到 obj,所以 this.a 和 obj.a 是同样的。对象属性引用链中只有最顶层或者说最后一层会影响调用位置。code

function fooo(){
  conosle.log(this.a);
}
var obj={
  a:2,
  foo:foo
};
var obj2={
  a:22,
  obj: obj
}
obj.foo();  // 2
obj2.obj.foo();  // 2

隐式丢失
虽然 bar 是 obj.foo 的一个引用,可是实际上,它引用的是 foo 函数自己,所以此时的bar() 实际上是一个不带任何修饰的函数调用,所以应用了默认绑定。对象

当函数被做为参数传递时(如:回调函数),会丢失this,由于参数传递其实就是一种隐式赋值继承

function foo() { 
  console.log( this.a );
}
var obj = { 
  a: 2,
  foo: foo
};
var bar = obj.foo; // 函数别名!
var a = "oops, global"; // a 是全局对象的属性 
bar(); // "oops, global"

3 显式绑定
JavaScript 提供的绝大多数函数以及你本身建立的全部函数均可以使用 call(..) 和 apply(..) 方法。这两个方法是如何工做的呢?它们的第一个参数是一个对象,它们会把这个对象绑定到this,接着在调用函数时指定这个 this。由于你能够直接指定 this 的绑定对象,所以咱们称之为显式绑定。ip

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

若是你传入了一个原始值(字符串类型、布尔类型或者数字类型)来看成 this 的绑定对象,这个原始值会被转换成它的对象形式(也就是new String(..)、new Boolean(..)或者new Number(..))。这一般被称为“装箱”。

4 new 绑定
使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操做。

  1. 建立(或者说构造)一个全新的对象。
  2. 这个新对象会被执行[[原型]]链接。
  3. 这个新对象会绑定到函数调用的this。
  4. 若是函数没有返回其余对象,那么new表达式中的函数调用会自动返回这个新对象。
function foo(a) { 
  this.a = a;
}
var bar = new foo(2); 
console.log( bar.a ); // 2

使用 new 来调用 foo(..) 时,咱们会构造一个新对象并把它绑定到 foo(..) 调用中的 this上。new 是最后一种能够影响函数调用时 this 绑定行为的方法,咱们称之为 new 绑定。

3 判断this绑定


注意:若是你把 null 或者 undefined 做为 this 的绑定对象传入 call、apply 或者 bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。

那么什么状况下你会传入 null 呢?一种很是常见的作法是使用 apply(..) 来“展开”一个数组,并看成参数传入一个函数。相似地,bind(..) 能够对参数进行柯里化(预先设置一些参数),这种方法有时很是有用:

function foo(a,b) {
console.log( "a:" + a + ", b:" + b );
}
// 把数组“展开”成参数
foo.apply( null, [2, 3] ); // a:2, b:3
// 使用 bind(..) 进行柯里化
var bar = foo.bind( null, 2 ); 
bar( 3 ); // a:2, b:3

软绑定会对指定的函数进行封装,首先检查调用时的 this,若是 this 绑定到全局对象或者 undefined,那就把指定的默认对象 obj 绑定到 this,不然不会修改 this。

if (!Function.prototype.softBind) { 
  Function.prototype.softBind = function(obj) {
    var fn = this;
    // 捕获全部 curried 参数
    var curried = [].slice.call( arguments, 1 ); var bound = function() {
      return fn.apply(
      (!this || this === (window || global)) ?
        obj : this
        curried.concat.apply( curried, arguments )
      ); 
    };
    bound.prototype = Object.create( fn.prototype );
    return bound; };
}

4 箭头函数

箭头函数并非使用 function 关键字定义的,而是使用被称为“胖箭头”的操做符 => 定义的。箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)做用域来决定 this。

function foo() {
// 返回一个箭头函数 
  return (a) => {
//this 继承自 foo()
  console.log( this.a ); };
}
var obj1 = { a:2};
var obj2 = { a:3};
var bar = foo.call( obj1 );
bar.call( obj2 ); // 2, 不是 3 !

foo() 内部建立的箭头函数会捕获调用时 foo() 的 this。因为 foo() 的 this 绑定到 obj1,bar(引用箭头函数)的 this 也会绑定到 obj1,箭头函数的绑定没法被修改。(new 也不行!)

相关文章
相关标签/搜索