JavaScript中的this

原文地址javascript

JavaScript中的this

原理

错误的this指向

一般所说的:若是是全局环境中,this指向全局对象,若是是对象的方法,这this指向这个对象。html

例子1:java

var foo = {
  bar: function() {
    console.log(this)
  }
}

foo.bar();
(foo.bar)();

(foo.bar = foo.bar)();
(false || foo.bar)();
(foo.bar, foo.bar)();

例子1前二者为foo,后面都是全局对象。后三者并无指向foo。因此咱们上面的一般说法不精确。git

精确的this指向

在全局环境中,this指向全局对象。而在普通函数调用中,this是由激活上下文的调用者提供,即调用这个函数的父做用域,以及函数调用的语法形式,决定了this的值,这是一个动态可变的值。github

例子2:编程

var foo = {
  bar: function() {
    console.log(this)
    console.log(this === foo)
  }
}

foo.bar() // foo, true

var fn = foo.bar

console.log(fn === foo.bar) // true

fn() // global, false

例子2中,第一次调用指向foo,把foo.bar赋值给fn以后,this没有指向foo。是什么致使this指向的变化呢?数组

this指向的内部原理

this是执行上下文的一个属性:缓存

activeExecutionContext = {
  VO: {...},
  this: thisValue
}

在普通函数调用中,this是由激活上下文的调用者提供,即调用这个函数的父做用域,函数调用的语法形式,决定了this的值,这是一个动态可变的值。闭包

为何会引发这个差别呢?
由于引用类型的不一样处理,是否会获取真实的值,所致使的。app

引用类型存在形式:

1 标识符(变量名,函数名,函数参数名,全局对象属性名)

2 属性访问器(foo.bar(); foo['bar'](), 点标记法;能够动态设置属性名的方括号[]

为了从引用类型中获取真实的值,存在相似getValue的方法。而函数上下文的规则是,函数上下文中this由调用者提供,并由调用形式决定。若是调用的圆括号左侧是一个引用类型,this为这个引用类型,若是是非引用类型,这为null,但为null无心义,被隐式转化为全局对象。

为何有this的特性?

this是一个指针,便于代码的更为简洁地复用。

// 无this
function upper(context) {
  return context.name.toUpperCase()
}
function speak(context) {
  var greeting = "Hello, I'm " + upper(context)
  console.log(greeting)
}

var me = {
  name: 'm'
}

var you = {
  name: 'y'
}

speak(me)

// 利用this

function upper() {
  return this.name.toUpperCase()
}
function speak() {
  var greeting = "Hello, I'm " + upper.call(this)
  console.log(greeting)
}

speak.call(me)

这里this能够简化上下文对象的传递。其余OPP语言中this关键字和OPP密切相关,通常是引用刚建立的对象,但在ECMAScript中,this只限于引用建立过的对象,this的指向和函数调用形式有关,不必定引用类型调用就指向引用类型。

this指向的改变

1 构造函数中的this

function C() {
  console.log(this)
  this.x = 10
}

var a = new C()
console.log(a.x);

new操做符会调用函数的内部的Construct方法,建立对象,以后调用函数的Call方法,把新建立对象做为this值。

2 调用函数时call与apply设置this的值

var b = 10
function a(c) {
  console.log(this.b)
  console.log(c)
}

a(20)
a.call({b: 20}, 30)
a.apply({b: 20}, [40])

call, apply, bind以及箭头函数

call,apply,bind皆为动态的改变this指针的方法。其中call和apply是当Object没有某个方法,可是其它对象有,能够借助call和apply改变this的指向,调用其它对象的方法。bind为绑定this为某个对象。

典型的应用:

将类数组元素转化为数组:
Array.prototype.slice.apply(document.getElementsByTagName('*'))

检查类型:

function isArray(obj) {
  return Object.prototpye.toString.call(obj) === '[object Array]'
}

箭头函数则与前三者不一样。
If kind is Arrow, set the [[ThisModel]] internal slot of F to lexical.If the
value is "lexical", this is an ArrowFunction and does not have a local this

  1. If thisModel is lexical, return NormalCompletion(undefined).

箭头函数没有本身的this绑定,同时在函数执行时绑定this会被直接忽略。其中this老是指向定义时所在的对象,而不是运行时所在的对象。即箭头函数的this值是lexical
scope 的this值。这一特性使得箭头函数在React中的render函数中使用起来很方便。

function foo() {
  setTimeout(() => {
    console.log('id: ', this.id)
  }, 100)
}

var id = 0

foo.call({id: 42})

// 容易误解的地方
// {id: 42}
// 是箭头函数定义所在的对象仍是运行时所在的对象。因为箭头函数位于foo函数内部,只有foo函数运行以后他才会生成,因此foo运行时所在的对象,即箭头函数定义所在的对象。
var f = () => 5;
// 近似等价于
var f = function() {return 5;}.bind(this);

综上,call,apply,bind使得JavaScript具备动态改变this的特性,而箭头函数使得JavaScript具备固定this的指向的特性。一动一静,相得益彰。

在编程中的运用

ES7中的::

this.x = 0
  let module = {
    x: 1,
    getX: function() {
      console.log(this.x)
    }
  }
  module.getX()
  let get = module.getX
  get() // 0
  let boundGetX = get.bind(module)
  boundGetX() // 1
  let ES7boundGetx = module::get
  ES7boundGetx() // 1

super

class P {
  foo() {
    console.log('P.foo')
  }
}

class C extends P {
  foo() {
    super.foo()
  }
}

var c1 = new C()
c1.foo() // P.foo

var D = {
  foo: function() {
    console.log('D.foo')
  }
}

var E = {
  foo: C.prototype.foo
}

Object.setPrototypeOf(E, D)
E.foo() // P.foo

可见super的绑定是静态绑定,建立时即完成绑定。因此E委托了D,但并不能调用到D.foo(),相似于箭头的函数的this绑定。

jQuery中的this

链式调用的实现;

function Constructor() {
  this.art = 0
}

Constructor.prototype.fn_0 = function() {
  console.log('0')
  return this;
}

Constructor.prototype.fn_1 = function() {
  console.log('1')
  return this;
}

new Constructor().fn_0().fn_1()

调用的方法返回this便可。

end()的实现

function end() {
  return this.prevObject || this.constructor(null)
}

// 设置preObject的函数
function pushStack( ele ) {
  // Build a new jQuery macthed element set
  var ret = jQuery.merge( this.constructor(), elems);
  ret.prevObject = this // ret.pervObject 设置为当前jQuery对象引用
  ret.context = this.context
  return ret;
}

pushStack函数在不少涉及DOM操做的函数都有调用,用于缓存了当前的this。因为只存储当前,因此这里只须要一个preObject便可,无需放在一个数组里。

利与弊

this是JavaScript特性之一,具备脚本语言的动态特性,带来不少便捷,同时因为super和箭头函数的特性,使得this具备了静态的特性,在这两种状况下,this是固定且没法改变的。其利与弊都是this的灵活,双刃剑。因此才有了ES2015中super和箭头函数的固定this的特性。

拾遗

this可被从新赋值么?(不能,this是保留字)

问题(答案见原文)

1 call参数为null时,this的指向

function a() {
  console.log(this)
}
a.call(null)

2 调用形式对this的影响

var foo = {
  bar: function() {
    console.log(this)
  }
}

foo.bar();
(foo.bar)();

(foo.bar = foo.bar)();
(false || foo.bar)();
(foo.bar, foo.bar)();

参考资料:

《你所不知道的JavaScript(上卷)》

关于JavaScript的执行域,标识符解析,闭包的研究

深刻ECMA-262-3 第三章、This

JavaScript内部原理实践——真的懂JavaScript吗?

相关文章
相关标签/搜索