深刻Javascript之this

前言

近期准备好好的读一读《你不知道的JavaScript(上卷)》这本书,俗话说的好,好记性不如烂笔头,读到this这章感受是时候须要一些笔记了。文中若有错误之处,欢迎指出。javascript

什么是this?

什么是this,咱们先来看看做者的回答。前端

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

this的4种绑定方式

默认绑定

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

var a = 2;

foo(); // 2

这段代码输出2 , 说明this默认绑定到了全局对象java

隐式绑定

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

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

obj.foo(); // 2

这段代码this绑定到了obj对象,当函数引用有上下文对象(context)时,隐式绑定规则会把this绑定到这个上下文对象。react

隐式丢失问题git

// 例子1111
function foo() { 
    console.log( this.a );
}

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

var bar = obj.foo; // 函数别名!

var a = "oops, global"; // a是全局对象的属性”

bar();  // "oops, global"

//2222


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

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

var a = "oops, global"; // a是全局对象的属性

setTimeout( obj.foo, 100 ); // "oops, global

例子1111虽然bar是obj.foo的一个引用,可是实际上,它引用的是foo函数自己,所以此时的bar()实际上是一个不带任何修饰的函数调用,所以应用了默认绑定。例子222在setTimeout函数中丢失了this绑定,道理是同样的,咱们能够把参数传递当作一种隐式赋值。es6

显式绑定

在分析隐式绑定时,咱们必须在一个对象内部包含一个指向函数的属性,并经过这个属性间接引用函数,从而把this间接(隐式)绑定到这个对象上。github

那么若是咱们不想在对象内部包含函数引用,而想在某个对象上强制调用函数,该怎么作呢?windows

学过js的估计对 call,apply和bind都不陌生,js提供的这些原生方法就给咱们提供了显式绑定this的途径。闭包

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

var obj = { 
    a:2
};

foo.call( obj ); // 2

经过foo.call(..),咱们能够在调用foo时强制把它的this绑定到obj上。

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

“从this绑定的角度来讲,call(..)和apply(..)是同样的,它们的区别体如今其余的参数上,可是如今咱们不用考虑这些。”

硬绑定-bind的实现

咱们已经知道了this绑定的4种方式,可是使用call和apply咱们仍是不能解决隐式丢失的问题,思考如下例子:

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

var obj = { 
    a:2
};

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

bar(); // 2
setTimeout( bar, 100 ); // 2

bar.call(windows) //没法再修改this

经过call咱们在函数bar内强制指定了foo的this,以后不论如何调用bar,它总会在obj上手动调用foo,这种绑定是一种显示强制绑定,所以称为硬绑定。这就是bind的产生来由(由于硬绑定比较经常使用,es5中新增了bind方法),经过硬绑定制定this,上面代码如今就变成了

...略
var bar = foo.bind(obj)
bar(); // 2
setTimeout( bar, 100 ); // 2

new绑定

关于new,咱们首先梳理一下基础知识。

new操做符干了什么?

  1. 建立(或者说构造)一个全新的对象。

  2. 这个新对象会被执行[[原型]]链接。

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

  4. 若是函数没有返回其余对象,那么new表达式中的函数调用会自动返回这个新对象”

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

var bar = foo(2)   
var baz = new foo(2) 

bar.a //Cannot read property 'a' of undefined

baz.a  // 2  this绑定到了foo

绑定规则优先级和es6的this新特性

若是要判断一个运行中函数的this绑定,就须要找到这个函数的直接调用位置。找到以后就能够顺序应用下面这四条规则来判断this的绑定对象。

  1. 由new调用?绑定到新建立的对象。
  2. 由call或者apply(或者bind)调用?绑定到指定的对象。
  3. 由上下文对象调用?绑定到那个上下文对象。
  4. 默认:在严格模式下绑定到undefined,不然绑定到全局对象。

ES6中的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法做用域来决定this,具体来讲,箭头函数会继承外层函数调用的this绑定(不管this绑定到什么)。

几个小例子看懂this

function showThis () {
  console.log(this)
}
function showStrictThis () {
  'use strict'
  console.log(this)
}
showThis() // window
showStrictThis() // undefined

优先级最小的this对象,默认绑定。

思考下面的例子:

var person = {
  name: '11',
  showThis () {
    return this
  }
}

person.showThis() === person  //true


var person2 = {
 name: '22',
  showThis () {
    return person.showThis()
  }
}

var person3 = {
 name: '33',
  showThis () {
    var retrunThis = person.showThis
    return retrunThis()
  }
}

person.showThis()  //person
person2.showThis()  //?
person3.showThis()  //?

咱们首先要找到调用位置,在2里是这句return person.showThis(),隐式绑定了一个person对象,因此输出person ,3 是return retrunThis() ,this默认绑定到全局,返回window.

function showThis () {
  return this
}
var person = { name: 'person' }
showThis() // window
showThis.call(p1) // person
showThis.apply(p1) // person

经过显式绑定指定了context object。

function showThis () {
  return this
}
var person = { name: 'person' }

var personBind = showThis.bind(person)

personBind()   //person

var person2 = { name: 'person2' }

personBind.call(person2) //person

bind方法强绑定了this,已经没法再经过显式绑定切换this。

function showThis () {
  return this
}
var person = { name: 'person' }
var person2 = { name: 'person2' }

var personBind = showThis.bind(person)

personBind()    //person
new personBind()  //showThis

new优先级高于bind,因此能够覆盖this。

function foo() { 
    setTimeout(() => {
        // 这里的this在词法上继承自foo()
        console.log( this.a ); 
    },100);
}

var obj = { 
    a:2
};

foo.call( obj ); // 2

箭头函数并非使用function关键字定义的,而是使用被称为“胖箭头”的操做符=>定义的。箭头函数不使用this的四种标准规则,而是根据外层(函数或者全局)做用域来决定this。箭头函数的绑定没法被修改,包括new。 关于箭头函数网上有不少详细全面的讲解。这里再也不展开。

后记

原型链,闭包,堆栈结构等等。。。javascript入门咱们都以为是比较简单的,找一份敲代码的工做真不难,只要努力搬砖就行了。可是若是不去深刻了解js的底层机制,这条道路恐怕是走不远的。没有好的基础,咱们能够学会使用react,使用vue,使用别人的插件,可是本身造轮子仍是远远达不到的。

前端水很深,须要学的的东西太多太杂,真要学,几乎都能学都有用,都不学,照样敲代码没什么大问题,工做业务积累应付工做仍是足够的,不够?那就加个班呗。加油吧~为了避免被淘汰,共勉~

若是以为本文对你有所帮助,就star一下吧~大传送之术! 个人博客Github

相关文章
相关标签/搜索