5. JavaScript中的this

先看一段代码

  1. function foo(num) {
  2. console.log("foo:" + num)
  3. this.count++
  4. }
  5. foo.count = 0
  6. var i
  7. for(i=0;i<10;i++) {
  8. if(i > 5){
  9. foo(i)
  10. }
  11. }
  12. console.log(foo.count) // 0
  13. // 谁调用 this指向谁
  14. console.log(this.count) // NaN
  15. // 如果先定义一个var count = 0; console.log(this.count) 是多少?
  16. // 能否用其他方法获取count的值 foo.count call

需要注意的是 this与作用域是不同的概念

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

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

foo() // 报错

你不知道的JavaScript上

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

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

规则

  1. 默认绑定
    ```javascript function foo () { console.log(this.a) }

var a = 2 foo() // 2

// 如果加了严格模式 “use strict” foo() // TypeError: this is undefined


2.  隐式绑定  
```javascript
function foo() {
    console.log( this.a )
}
var obj = {
  a: 2,
  foo,
}
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


// 隐式丢失
function foo() {
    console.log( this.a );
}
var obj = {
  a: 2,
  foo: foo
}
var bar = obj.foo
var a = "oops, global"
bar() // "oops, global"
  1. 显示绑定
    1. 硬绑定
      ```javascript function foo() { console.log( this.a ); } var obj = { a:2 }; var bar = function() { foo.call( obj ); }; bar(); // 2 setTimeout( bar, 100 ); // 2 // 硬绑定的 bar 不可能再修改它的 this bar.call( window ); // 2

// 使用场景 // 1 function foo(something) { console.log( this.a, something ); return this.a + something; } var obj = { a:2 }; var bar = function() { return foo.apply( obj, arguments ); }; var b = bar( 3 ); // 2 3 console.log( b ); // 5 // 2 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 // bind function foo(something) { console.log( this.a, something ); return this.a + something; } var obj = { a:2 }; var bar = foo.bind( obj ); var b = bar( 3 ); // 2 3 console.log( b ); // 5

```javascript
// 实现一个bind
Function.prototype._bind = function(ctx = window) {
  if(typeof this !== 'function') {
          throw new TypeError ( this + 'is not a function')
  }
  var _this = this,
    args = [].slice.call(arguments,1),
    emptyFn = function (){},
    binded = function () {
     var newArgs = [].slice.call(arguments);
     _this.apply(this instanceof binded ? this : ctx , newArgs.concat(args))
   }
  emptyFn.prototype = this.prototype;
  binded.prototype = emptyFn.prototype;
  return binded;
}
// 实现一个apply
Function.prototype._apply = function (ctx = window,arr){
    ctx.fn = this;
  if( typeof arr !== 'object' && typeof arr !== 'function' && typeof arr !== 'undefined'){
    throw TypeError ('CreateListFromArrayLike called on non-object')
  }
  if( !arr || Object.prototype.toString.call(arr) !== 'object Array'){
    delete ctx.fn;
 return ctx.fn()
  }
  let res = ctx.fn(...arr)
  return res;
}
// 实现一个call
Function.prototype._call = function (ctx = window) {
  ctx.fn = this;
  let args = [...arguments].slice(1);
  let res = ctx.fn(...args);
  delete ctx.fn;
  return res;
}
  1. API调用的“上下文”
    function foo(el) {
    console.log( el, this.id );
    }
    var obj = {
    id: "awesome"
    };
    // 调用 foo(..) 时把 this 绑定到 obj
    [1, 2, 3].forEach( foo, obj );
    // 1 awesome 2 awesome 3 awesome
    
  1. new绑定
function foo(a) {
  this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2
// 实现一个new
// 规则
/*
    1. 创建(或者说构造)一个全新的对象。
    2. 这个新对象会被执行 [[ 原型 ]] 连接。
  3. 这个新对象会绑定到函数调用的 this。
  4. 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。
*/

function _new() {
  var constructor = [].shift.call(arguments),
      _this = {};
  _this.__proto__ = constructor.prototype
  var res = constructor.apply(_this,arguments)
  return typeof res === 'object' ? res : _this
}
  1. 箭头函数

根据当前的词法作用域来决定

this,具体来说,箭头函数会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。

优先级

// 显式与隐式绑定
function foo() {
    console.log( this.a );
}
var obj1 = {
  a: 2,
  foo: foo
};
var obj2 = {
  a: 3,
  foo: foo
};
obj1.foo(); // 2
obj2.foo(); // 3
obj1.foo.call( obj2 ); // 3
obj2.foo.call( obj1 ); // 2

// 隐式绑定与new
function foo(something) {
  this.a = something;    
}
var obj1 = {
  foo: foo
};
var obj2 = {};

obj1.foo( 2 );
console.log( obj1.a ); // 2
obj1.foo.call( obj2, 3 );
console.log( obj2.a ); // 3

var bar = new obj1.foo( 4 );
console.log( obj1.a ); // 2
console.log( bar.a ); // 4

// 显式绑定与new
function foo(something) {
  this.a = something;
}
var obj1 = {};
var bar = foo.bind( obj1 );
bar( 2 );

console.log( obj1.a ); // 2
var baz = new bar(3);
console.log( obj1.a ); // 2
console.log( baz.a ); // 3

面试题

// 1
var fullName = 'language'
var obj = {
  fullName: 'javascript',
  prop: {
    getFullName: function () {
      return this.fullName
    }
  }
}
console.log(obj.prop.getFullName())
var test = obj.prop.getFullName
console.log(test())
// 2 *
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();
// 3
const a = {
  b: 2,
  foo: function () { console.log(this.b) }
}

function b(foo) {
  // 输出什么?
  foo()
}

b(a.foo)