likes
comments
collection
share

this上下文就这样理解 😺🐱🐟

作者站长头像
站长
· 阅读数 0

常规this指向

理解this的指向首先要记住一句话 在es5(不包括箭头函数)中 this 指向和定义无关,this 永远指向他的直接调用者。 下面通过几个例子来理解这句话。

var name = 'is name'
function fun1(){
  console.log(this.name)  // is name
}
fun1()

定义在全局的fun1,直接调用等同于调用window.fun1(),this 自然指向window。这里不包括严格模式,关于严格模式的表现在后面会涉及到。

var name = 'is name'
var obj1 = {
  name: 'obj1'
  fun1: function(){
    console.log(this.name)
  },
  obj2: {
    name: 'obj2',
    fun2: function(){
      console.log(this.name)
    }
  }
}
obj1.fun1() // obj1
obj1.obj2.fun2() // obj2
var fun = obj1.obj2.fun2
fun() // is name

再看一遍这句话 this指向和定义无关,this永远指向他的直接调用者, 在 obj1.fun1() 中调用者是 obj1 在 obj1.obj2.fun2() 中直接调用者是obj2,注意是直接调用者这里和前面的obj1没有任何关系。 在 var fun = obj1.obj2.fun2 中虽然fun2是定义在obj1.obj2下面的,之间讲过this指向和定义无关,最终调用者是 window.fun()。

var name = 'is windows'
function fun() {
  var name = 'fun'
  function innerfun() {
    console.log(this.name) // is windows
  }
  innerfun()
}

var obj = {
  name: 'obj',
  fun1: function(){
    function innerfun() {
      console.log(this.name) // is windows
    }
    innerfun()
  },
  fun2: function(){
    setTimeout(function(){
      console.log(this.name)
    }, 0)
  }
}

fun() // is windows
obj.fun1() // is windows
obj.fun2() // is windows

上面的三个案例其实是一种情况,虽然都有相应的调用者但是最终log语句是在一个function里执行的,这个function没有调用者,也没有挂载到window上(在相应的局部作用域里),这种作为一个函数直接调用的function它的this会指向window。同时setTimeout里是一个匿名函数,再记住一句话 通过function关键字(区别箭头函数)声明的匿名函数this指向window

function fun1 (){ 
  console.log(this) 
}
var fun2 = () => { 
  console.log(this) 
}
var arr = [fun1, fun2]
arr[0]() // arr
arr[1]() // window

特殊情况如果this在数组里通过index调用可以把 arr[0] 理解成 arr.0 ,this 自然指向arr,关于箭头函数后面会讲到。

[1,2].map(() => {
  console.log(this) // window
})

[1,2].map(function(){
  console.log(this) // window
})

这个例子会给人一种错觉this指向[1,2],其实是window,map 是一个方法通过[1,2]调用,this 的 console 是在 map 的回调里。因为 匿名函数this指向window 所以 this 是 window。

通过上面几个例子对 this指向和定义无关,this永远指向他的直接调用者 这句话理解的已经七七八八了,但是漏了一个重要的东西就是new关节字,下面分析当出现new的时候this指向怎么确定。

prototype 和 proto

在找 this 之前要先理解 new 到底干了什么,又是一个老生常谈的问题,先看几个前置的知识点:

  • 一个 function 被 new 了我们认为它就是一个构造函数,通过 new 得到的东西就是一个实例。
  • 每个 function 都有一个 prototype 属性,prototype 指向原型。
  • prototype 上有 constructor 属性,constructor 指向构造函数(这个函数本身)。
  • 每个实例都有一个 proto 属性,proto 指向构造函数的原型。
  • 实例对象继承自这个构造函数的原型,原型上的属性和方法是被他的所有实例对象所拥有的。

根据上面的知识点下面两条语句是成立的:

obj.__proto__ === fun.prototype
fun.prototype.constructor === fun

new的本质

以 obj = new fun() 为例,首先 new 关键字会创建一个临时对象假设是 nsitObj,会把 nsitObj.proto 指向 fun.prototype 绑定原型。 再把 fun 里面的 this 指向 nsitObj,并执行方法体。最后根据 fun 方法体的内容 return 回一个值,分为以下几种情况:

  • 如果fun方法体无返回值那么 new 关节字会 return 回 nsitObj,也就是说 obj 其实就是 nsitObj。
  • 如果fun方法体返回了以下任意类型的一种值 new 会无视返回值依然 return 回 nsitObj。 Number、 String、 Boolean、 undefined、 NaN、 Null
  • 如果fun方法体返回了一个 Function 那么 new 关节字会 return 回这个 function。
  • 如果fun方法体返回了一个用户自定义的 object 比如说一个空对象{},那么 new 关节字会 return 回这个 object。

有时我们需要知道 obj(实例) 是被谁 new 出来的所以 fun.prototype 上有一个 constructor 指向 fun。 所以 obj.proto.constructor === fun.prototype.constructor === fun 。 在改 prototype 时要通过 fun.prototype.x 来增加,不能直接 fun.prototype = {},这样会把 constructor 覆盖。

也就是说当一个函数被 new 了之后 this 会指向实例。

function fun(){
  this.a = 1
  console.log(this, this.a)  
}
var obj = new fun() // obj 1
function fun(){
  this.c = 1
  console.log(this, this.c)
  return {
    a: 1,
    b: 2
  }
}
var obj = new fun() // {c: 1}  1

这种情况fun返回了{a: 1, b: 2},就意味着 obj 就是 {a: 1, b: 2},但并不影响 fun 方法体里this指向 nsitObj 的逻辑,只不过我们最终没有得到 nsitObj。

function fun(){
  this.a = 1
  this.fun1 = function(){
    console.log(this, this.a) 
  }
  this.obj = {
    a: 2,
    fun2: function(){
      console.log(this, this.a)
    },
    fun3: function(){
      function fun(){
        console.log(this)
      }
      fun()
    }
  }
}
var myObj = new fun()
myObj.fun1()     // myObj 1
myObj.obj.fun2() // obj 2
myObj.obj.fun3() // window
var f = myObj.obj.fun2
f() // window

myObj.fun1() 函数被 new 了之后 this 会指向实例 是 myObj myObj.obj.fun2() this永远指向他的直接调用者 是 obj myObj.obj.fun3() 作为一个函数直接调用的function它的this会指向window 是 window f() 作为一个函数直接调用的function它的this会指向window 是 window

关于箭头函数

前面所有的结论对箭头函数都不适用,在分析箭头函数的 this 问题之前我们先把它的特点抛出来。

箭头函数特点:

  • 箭头函数如果函数体只有一条语句可以省略花括号

  • 箭头函数省略花括号时默认会return函数体执行结果

  • 箭头函数期望只返回一个对象时可以用小括号包裹花括号

  • 箭头函数只有一条语句省略了花括号且不希望有返回值可以在函数体前加void关节字

  • 箭头函数没有原型prototype

  • 箭头函数没有constructor

  • 箭头函数没有自己的arguments,可用rest参数解决。

  • 箭头函数不支持new.target

  • 箭头函数参数名称不允许重复

  • 箭头函数的this定义时就已经确定且永远不会改变,它只会从定义时的词法作用域来决定this。

  • 箭头函数本身的this指向虽不可改变,但可以修改它继承对象的this(上层this)。

  • 箭头函数不能作为构造函数使用,也就是说箭头函数不能被new。

  • 普通函数有变量提升的机制,箭头函数不会变量提升。(基于 var、let 提升机制)

// 箭头函数如果函数体只有一条语句可以省略花括号
let fun = () => testFun()

// 箭头函数省略花括号时默认会return函数体执行结果
let num = (a, b) => a + b

// 箭头函数期望只返回一个对象时可以用小括号包裹花括号
let obj = (a, b) => ({aName: a ,bName: b})

// 箭头函数只有一条语句省略了花括号且不希望有返回值可以在函数体前加void关节字
let fun = () => void testFun()

// 箭头函数没有原型prototype
let fun = () => {}; console.log(fun.prototype)

// 箭头函数没有constructor
let fun = () => {}; 
console.log(fun.prototype) // undefined
console.log(fun.prototype.constructor) // TypeError: Cannot read property 'constructor' of undefined
let fun2 = function(){}
console.log(fun2.prototype.constructor === fun2) // true

// 箭头函不支持new.target
let fun = () => { console.log(new.target) }  // ReferenceError: fun2 is not defined
let fun2 = function(){ console.log(new.target) } // new.target用于判断当前函数是不是被作为构造函数调用

// 箭头函数没有自己的arguments,可用rest参数解决。
let fun = (...rest) => {
  console.log(arguments) // ReferenceError: arguments is not defined
  console.log(rest) // [1, 2, 3]
}
fun(1,2,3)

// 箭头函数参数名称不允许重复
let fun = (a, a, a) => console.log(a) // SyntaxError: Duplicate parameter name not allowed in this context
let fun2 = function(a, a, a){ console.log(a) }
fun2(1,2,3)  // 3

箭头函数arguments的查找机制

箭头函数本身没有 arguments,但是并不代表箭头函数内部访问不到 arguments,把 arguments 理解成一个变量,箭头函数内部的 arguments 的访问会遵循作用域链的查找机制访问上层通过 function 关键字声明的函数的 arguments,找不到才会报错。

var fun = () => {
  console.log(arguments)
}
fun() // ReferenceError: arguments is not defined

function test(){
  var fun = () => {
    console.log(arguments)
  }
  fun()
}
test(1, 2) // [1, 2]

rest参数相对于arguments的优点

  • 箭头函数和普通函数都可以使用
  • 接收参数的数量完全自定义 let fun = (a, ...rest) => console.log(a, rest); fun(1, 2, 3) // 1 [2,3]
  • 可读性更好
  • rest是一个真正的数组,可以使用数组的API。

箭头函数为什么不能被new

结合上文 new 的本质,在箭头函数中没有 prototype,无法完成绑定原型的操作所以箭头函数不能被 new。

箭头函数的使用场景以及优点

  • 箭头函数可以让this指向固定化,这种特性很有利于封装回调函数。
  • 简化代码,可读性更好,提高开发效率。
  • 适用于 map,filter,api请求 的回调。
  • 简化es5开发中 var _this = this 。

关于箭头函数的this

  1. 箭头函数this指向的固化,并不是因为箭头函数内部没有this绑定机制,实际原因是箭头函数根本没有自己的this。
  2. 箭头函数的this定义时就已经确定且永远不会改变,它只会从自己定义时的作用域链上层继承this(外层代码块)。
  3. 如果上层还是箭头函数那就继续往上找,直到全局的window。
  4. 箭头函数本身的this指向虽不可改变,但可以修改它继承对象的this(上层this)。
var obj = {
  fun: function(){
    // this -> obj
    var innerfun = () => {
      console.log(this)
    }
    innerfun()
  }
}
obj.fun() // obj

这个案例在上文出现过只不过 innerfun 是用 function 声明的,通过 function 声明的函数直接调用 this 指向 window。 如果 innerfun 用箭头函数声明 this 会绑定到上层作用域,也就是 fun,this 指向 obj。

var obj = {
  fun: () => {
    // this -> window
    var innerfun = () => {
      console.log(this)
    }
    innerfun()
  }
}
obj.fun() // window

接着上个例子如果把 fun 换成箭头函数,那 this 的查找会再往上一层到了 window,所以 this 指向 window。

var obj = {
  name: 'obj',
  obj1: {
    name: 'obj1',
    fun: () => {
      console.log(this)
    }
  }
}
obj.obj1.fun() // window

fun 是箭头函数 this 向上找到了 obj,有种错觉this会指向 obj,其实不对,obj 并不是一个 function 不存在作用域,所以最终 this 是指向 window 的。

var obj = {
  name: 'obj',
  obj1: {
    name: 'obj1',
    obj2: {
      name: 'obj2',
      fun: function(){
        var f = () => {
          console.log(this)
        }
        f()
      }
    }
  }
}
obj.obj1.obj2.fun()  // obj2
var myFun = obj.obj1.obj2.fun
myFun() // window

obj.obj1.obj2.fun() 箭头函数向上查找到 fun this 指向是 obj2。 前问说过 箭头函数的this定义时就已经确定且永远不会改变 那为啥 myFun 里 this 指向从 obj2 变成了 window ? 其实 f() 里箭头函数的指向并没有发生变化,还是指向 obj2.fun,只不过 obj2.fun 的调用方式变了,导致 obj2.fun 的this指向变了,这也是对 箭头函数本身的this指向虽不可改变,但可以修改它继承对象的this 这句话的验证。

var obj = {
  fun1: function(){
    [1,2].map(() => {
      console.log(this)
    })
  },
  fun2: function(){
    [1,2].map(function(){
      console.log(this)
    })
  }
}
obj.fun1() // obj
obj.fun2() // window

这俩 map 直接扔到全局 this 都是 window 前文解释过,但是包一层方法该怎么理解? obj.fun1() 箭头函数从自己定义时的作用域链上层继承this obj.fun1 的 this 是 obj。 obj.fun2() 匿名函数this指向window 所以是 window。

function fun(){
  this.fun = function(){
    console.log(this)
  }
}
var obj1 = new fun()
obj1.fun() // obj1

function fun(){
  // this -> obj2
  this.fun = () => {
    console.log(this)
  }
}
var obj2 = new fun()
obj2.fun() // obj2

obj1.fun() fun没有 return object,this 指向实例,也就是 obj1。 obj2.fun() 内部是箭头函数,this 向外查找,这时候很容易误认为是 window,但 new fun() 的时候把 this 指向实例,和内部的箭头函数没有关系,最终 this 还是指向 实例。

var obj = {
  fun: function(){
    console.log(this)
  }
}
obj.fun() // obj

var obj = {
  fun: () => {
    console.log(this)
  }
}
obj.fun() // window

箭头函数与普通函数 this 指向的区别

前面聊的都是this的定义和确定,那this可以修改吗? 来看 apply、call、bind 的用法。

关于arguments

前面有提到 arguments,arguments 是一个类数组,可以理解是参数队列,在说 arguments 的特点之前先了解一下“行参”和“实参”。比如说 function fun(a,b,c){alert(a,b,c)}; fun(1,2) 中 a、b、c 是行参,1、2 是实参。

  • 函数的arguments可以返回参数长度,是实参的长度。
  • 函数有length和name属性,函数体的length返回行参长度,函数体的name返回函数名称。
  • arguments有一个callee属性指向函数体,在函数内部通过 arguments.callee()可以执行本函数。
  • arguments的callee指向函数体,所以arguments.callee.length返回行参的长度。

看一个例子

function fun(m, n, o, p, q){
  alert(this.length) // 4
  alert(arguments.length) // 3
  alert(arguments.callee.length) // 5
}
function f(a, b){
  arguments[0](1, 2, 3)
}
f(fun, 4, 5, 6)

fun 是通过 arguments[0] 调用的,那么 fun 中的 this 指向 f() 的 arguments,arguments.length 为 f() 的实参 [fun、4、5、6] 长度是 4。 arguments.length 表示 fun 的实参 [1, 2, 3] 长度是3。 arguments.callee.length 表示 fun 的行参 [m, n, o, p, q] 长度是5。

apply、call、bind

apply、call、bind 都是用来改变 this 指向的,也就是改变函数执行时的上下文环境。

apply

  • fun.apply( thisArg, [argsArray] ) 调用一个function并指定this的指向(执行上下文),同时提供一个数组或类数组(可选)作为参数解构到被执行 function 的参数里。 Function.prototype.apply MDN

call

  • fun.call( thisArg, arg1, arg2, ... ) 调用一个function并指定this的指向(执行上下文),同时提供一个参数列表(若干个),传递给 function 执行。 Function.prototype.call MDN

bind

  • fun.bind( thisArg, arg1, arg2, ... ) 不会执行 fun,而是返回一个新函数,新函数的 this 指向 bind 第一个参数,bind 的后续参数会追加到新函数参数里。 Function.prototype.bind MDN

相同点

  1. apply、call、bind 都可以改变this的指向,也就是第一个参数thisArg,thisArg理论上是必填的但也允许为空。
  2. 如果thisArg为空 非严格模式下this指向window,严格模式指向undefined。
  3. 如果thisArg指定为 null 或 undefined, 非严格模式下this指向window,严格模式指向传入的null 或 undefined。
  4. 如果thisArg指定为原始值 非严格模式下this指向该原始值的包装对象,严格模式this指向该原始值。

不同点

-参数是否直接执行函数返回值
apply只接收两个参数,第二个参数是 Array 会被解构返回被执行方法的返回值,该方法无返回值则返回 undefined
call允许传入多个参数,第二个开始为参数列表返回被执行方法的返回值,该方法无返回值则返回 undefined
bind允许传入多个参数,第二个开始为参数列表返回新函数

案例

var obj = {
  name: 'obj'
}
function fun(a,b,c,d){
  console.log(this, a,b,c,d)
}
var fun2 = () => {
  console.log(this)
}
fun.apply(null) // window undefined undefined undefined undefined
fun.apply(1) // Number {1} undefined undefined undefined undefined
fun.apply(obj,[1,2,'a','b']) // obj 1 2 a b 
fun.call(obj, 1, 2, 'a', 'b') // obj 1 2 a b 
var myFun = fun.bind(obj, 1, 2)
myFun('a', 'b') // // obj 1 2 a b 

fun2.apply(obj) // window
fun2.call(obj) // window

fun2.apply(obj) 和 fun2.call(obj) this 并没有发生变化,是因为箭头函数的this不能被改变,apply、call、bind对箭头函数无效。

关于严格模式

严格模式下,普通函数严格遵循谁调用this指向谁,定义在全局作用域下的函数不通过window.fun调用this会指向undefined。 严格模式和非严格模式下箭头函数定义在全局作用域,它的this都会指向window,取上层作用域和调用者无关且不可改变。

'use strict'

function fun(){
  console.log(this)
}
fun() // undefined
window.fun() // window
fun.apply({a: 1}) // {a: 1}
fun.apply(undefined) // undefined
fun.apply(null) // null
fun.apply(1) // 1

function fun2(){
  var fun = () => {
    console.log(this)
  }
  fun()
}
fun2() // undefined
window.fun2() // window 
fun2.apply({a: 1}) // {a: 1}

var fun3 = () => {
  console.log(this)
}
fun3() // window
window.fun3() // window
fun3.apply({a: 1}) // window

new、apply、call、bind 都是语法糖

new、apply、call、bind 都是语法糖,既然是语法糖就可以自己实现。

自己实现 new

function myNew(fun, ...args) {
  let nsitObj = {}
  nsitObj.__proto__ = fun.prototype  // Object.setPrototypeOf(nsitObj, fun.prototype)
  let result = fun.apply(nsitObj, args)
  return result instanceof Object ? result : nsitObj
}

基本是把上文提到的 new的本质 用代码描述了出来,上面实现不包含new.target可以考虑加上。要注意的是使用 nsitObj.proto = fun.prototype 修改原型是不推荐的(这里为了方便理解),因为 JavaScript 引擎更改对象的 Prototype 在各个浏览器上都是一个很慢的操作。其在更改继承的性能上的影响是微妙而又广泛的,这不仅仅限于 obj.proto = ... 语句上的时间花费,可能会延伸到任何访问 Prototype 的代码。如果你关心性能,你应该避免设置一个对象的 Prototype。相反,你应该使用 Object.setPrototypeOf 显性的修改语义化更好。

自己实现 apply

Function.prototype.myApply = function(...args) {
  const thisArg = args[0] || window // thisArg是目标this
  const argsArray = args[1] || [] // 参数数组
  thisArg.myApply = this  // 这里的this是原方法
  const res = thisArg.myApply(...argsArray) // 在目标this上调用原方法
  delete thisArg.myApply // 删掉临时存储的原方法
  return res // 将原方法执行结果返回
}

我们自己实现的 myApply 方法,需要和原生 apply 方法一样,通过 fun.myApply 调用,因为 原型上的属性和方法是被他的所有实例对象所拥有的 所以我们需要把 myApply 定义在 Function.prototype 上。通过 fun.myApply 调用,那么 myApply 里的 this 就是 fun 也就是原方法。再根据 apply 传参语法取出目标this 和 参数。最后把原方法写在目标 this 上最后再删除。

自己实现 call

Function.prototype.myCall = function(...args) {
  const thisArg = args[0] || window // thisArg是目标this
  const argsArray = args.slice(1) || [] // 参数数组
  thisArg.myCall = this  // 这里的this是原方法
  const res = thisArg.myCall(...argsArray) // 在目标this上调用原方法
  delete thisArg.myCall  // 删掉临时存储的原方法
  return res  // 将原方法执行结果返回
}

和 apply 一模一样只是参数处理有一些小差异

自己实现 bind

Function.prototype.myBind = function(...args) {
  const thisArg = args[0] || window
  const argsArray = args.slice(1) || []

  const fun = this
  return function(...argsArray2){
    return fun.apply( thisArg, [...argsArray, ...argsArray2] )
  }
}

和 apply 实现原理差不多,注意最后返回一个 function。

总结

我们再接着分析,this通常定义在函数里面,既然是函数就涉及到调用,上文得到的一些结论 this永远指向他的直接调用者 以及 匿名函数this指向window,这不就是用不同的调用方法调用函数吗,在js中函数的调用有下面4种方法:

  • 函数调用: fun()
  • 方法调用: obj.fun()
  • 构造函数: new fun()
  • 通过函数方法调用函数: fun.apply()

上文的一堆 this 指向规则都可以归结到这4种函数调用方法上,在加几种特殊场景基本理解的七七八八了。

  • 函数直接被调用时this则指向window。
  • 函数作为某对象的方法调用时,this指向最终调用者。
  • 构造函数的this指向实例。
  • 而箭头函数没有执行上下文,取决于他就近的外层非箭头函数的this。
  • apply、call、bind 可以改变this的指向(不包括箭头函数)。
  • 在DOM事件里this一般指向事件对象。

最后来看一堆案例加深理解

例1

var obj = {
  foo: 'bar',
  fun: function(){
    var _this = this
    console.log(this.foo)  // bar
    console.log(_this.foo); // bar
    (function(){
      console.log(this.foo)  // undefined 
      console.log(_this.foo) // bar
    }())
  }
}
obj.fun()

例2

var name = 'win'
var obj = {
  name: 'obj',
  fun1: function(){
    return function(){
      return this.name
    }
  },
  fun2: function(){
    return () => {
      return this.name
    }
  }
}
console.log(obj.fun1()()) // win
console.log(obj.fun2()()) // obj

例3

var a = 1
var b = 2 
var obj = {
  a: 10,
  b: this.a + 10,
  c: this.b,
  fun1: function () {
    return [this.a, this.b, this.c]
  },
  fun2: () => {
    return [this.a, this.b, this.c]
  }
}
console.log( obj.b )    // 11
console.log( obj.c )    // 2
console.log( obj.fun1() ) // [10, 11, 2]
console.log( obj.fun2() ) // [1, 2, undefinden]

例4

var a = 10
function fn( ) {
  'use strict';
  var a = 1;
  var obj = {
    a: 2,
    c: this.a + 3 
  }
  return obj.c;
}
console.log( fn() ) // 严格模式下,a指向undefined嘛,undefined.a报错

例5

var a = 10
function fn( ) {
  var a = 1;
  var obj = {
    a: 2,
    c: this.a + 3 
  }
  return obj.c;
}
console.log( fn() ) // 13

例6

var x = 10
var foo = {
  x: 20,
  bar: function(){
    var x = 30
    console.log(this.x)
  }
}
foo.bar(); // 20
(foo.bar)(); // 20 (foo.bar) 取值是 bar 方法
(foo.bar = foo.bar)(); // 10 返回值是 bar 方法
(foo.bar, foo.bar)();  // 10 返回值是 bar 方法

(foo.bar)():虽然foo.bar用小括号括起来了,但是最终的调用者还是foo。 (foo.bar = foo.bar)() 和 (foo.bar, foo.bar)():(foo.bar = foo.bar) 和 (foo.bar, foo.bar) 既然可以在后面加小括号调用就说明他俩的返回值是一个 function,直接运行返回值 this 指向 window。

例7

function fun(){
  console.log(this.a);
}
var a = 2;
var obj = {
  a:3, 
  fun: fun
};
var obj2 = { a:4 };

obj.fun();   // 3
(obj2.fun = obj.fun)(); // 2
obj2.fun = obj.fun;
obj2.fun();  // 4

例8

var name = '1';
var object={
  name: '2',
  getName: function(){
    return this.name
  }
};
console.log( (object.getName)() );  // 2
console.log( (object.getName=object.getName)() ) // 1  

例9

var length = 10
function fn() {
  console.log(this.length)
}
var obj = {
  length: 5,
  method: function(fn) {
    fn() // window 10
    arguments[0]() // arguments 2
  }
}
obj.method(fn, 1)

fn():window调用this指向window arguments0:arguments调用this指向arguments

例10

var a = 5;
var b = 2;
function fn1(){
  var a = 6;
  console.log(a);        // 6
  console.log(this.a);   // 5
  console.log(b);        // 2
}  
function fn2(fn) {
  var a = 7;
  var b = 3;
  fn();
} 
var obj = {
  a: 8,
  getA: fn1
}  
fn2(obj.getA);

fn2 中的 fn() 执行的是 fn1,且是全局调用所以 fn1 中 this 指向 window。 console.log(b) 是作用域的问题和this无关后面会专门开一篇帖子总结作用域。

例11

function Person(name, age) {
  this.name = name;
  this.age = age;
  console.log(this);  // Person 实例
}   
Person.prototype.getName = function () {
  console.log(this);  // Person 实例
}; 
var p1 = new Person("test", 18);
p1.getName();
console.log(p1);  // Person 实例

例12

function test(arg) {
  this.x = arg
  return this
}

var x = test(5);   // x 先等于5,然后被赋值为 window。
var y = test(6);   // x 赋值为6,y 赋值为 window
console.log(x.x);  // undefined,实际上是6.x  是undefined
console.log(y.x);  // 6,实际上是window.x 也就是6

例13

var name = '1'
var obj = {
  name: '2',
  getName: function() {
    return function() {
      console.log(this.name)
    }
  }
}
obj.getName()()

例14

var x = 3;
var y = 4;
var obj = {
  x: 1,
  y: 6,
  getX: function() {
    var x =5;
    return function(){
      return this.x;
    }();
  },
  getY: function() {
    var y =7;
    return this.y;
  }
}
console.log(obj.getX()) // 3
console.log(obj.getY()) // 6

例15

var a = 10; 
var obj = { 
  a: 20, 
  fn: function(){ 
    var a = 30; 
    console.log(this.a)
  }
}
obj.fn();  // 20
obj.fn.call(); // 10
(obj.fn)(); // 20
(obj.fn, obj.fn)(); // 10
new obj.fn(); // undefined

obj.fn.prototype.a = 0
new obj.fn(); // 0

例16

function foo() {
    getName = function () { console.log (1); };
    return this;
}
foo.getName = function () { console.log(2) };
foo.prototype.getName = function () { console.log(3);};
var getName = function () { console.log(4) };
function getName () { console.log(5);}
 
foo.getName();            // 2
getName();                // 4
foo().getName();          // 1
getName ();               // 1
new foo.getName();        // 2
new foo().getName();      // 3
new new foo().getName();  // 3

例17

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

var bar = foo() 
bar();  // undefined

var baz = foo.call(foo);
baz();  // 10 

var obj = { a: 999 }
baz.call(obj); // 10

例18

var people = {
  Name: '海洋饼干',
  getName: function(){
    return ()=>{
      console.log(this.Name);
    }
  }
};
var bar = people.getName();
bar(); // 海洋饼干

箭头函数this定义时决定和调用无关

例19

var obj = {
  that: this,
  bar: function(){
    return ()=>{
      console.log(this);
    }
  },
  baz: ()=>{
    console.log(this);
  }
}
console.log(obj.that);  // window
obj.bar()();            // obj
obj.baz();              // window

例20

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

例21

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

例22

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

bind不会直接执行foo方法,而是返回一个function需要再手动调用

例23

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

var obj1 = {}

var fun1 = foo.bind(obj1);
fun1(2);
console.log(obj1.a); // 2

var fun2 = new fun1(3);
console.log(obj1.a); // 2
console.log(fun2.a); // 3

例24

function foo(p1,p2){
  console.log(this, p1, p2)
  this.val = p1 + p2;
}

var bar = foo.bind(null, 'p1');  // 返回一个只有一个参数的函数 bar('p1')
var baz = new bar('p2')
baz.val;  // p1p2

例25

function foo() {
  console.log('a', this)
  return (a) => {
    console.log('b', this)
  };
}
var obj1 = { a: 2 };
var obj2 = { a: 3 };

var bar = foo.call(obj1)  // 'a' obj1
bar.call(obj2)  // 'a' obj1

例26

var test = {
  a: 5,
  b: 6,
  sum: function(a,b) {
    var self = this;
    function getA() {
      return self.a;
    }
    function getB(){
      return self.b;
    }
    alert(a);
    alert(b);
    return getA() + getB();
  }
}
var obj = {a:2,b:3};
alert(test.sum.call(obj, 4, 5));    // alert顺序4,5,5
alert(test.sum.apply(obj, [6, 7])); // alert顺序6,7,5

var sum = test.sum.bind(obj,8);     // 此处返回一个只有一个参数的函数 sum(8)
alert(sum(9));                      // alert顺序8,9,5