2015年6月发布了ES 6,也就是我们今天广泛使用的版本。这之后每年6月发布一个极小更新的ES版本,比如2016年6月发布的 ES 2016 叫做ES 7,而这个版本只增加了两个新特性。截止目前,ES 11的提案已经定稿了。
2015年6月发布了ES 6,也就是我们今天广泛使用的版本。这之后每年6月发布一个极小更新的ES版本,比如2016年6月发布的 ES 2016 叫做ES 7,而这个版本只增加了两个新特性。截止目前,ES 11的提案已经定稿了。
typeof
一般被用于判断一个变量的类型,我们可以利用 typeof
来判断 number
, string
, object
, boolean
, function
, undefined
,symbol
这七种类型,这种判断能帮助我们搞定一些问题,比如在判断不是 object
类型的数据的时候,typeof
能比较清楚的告诉我们具体是哪一类的类型。但是,很遗憾的一点是,typeof
在判断一个 object
的数据的时候只能告诉我们这个数据是 object
, 而不能细致的具体到是哪一种 object
, 比如
在这里汇总一些 JS 常用技术的源码实现,通过源码了解其内部原理,可以加深对其的理解。
抛开JavaScript自带的class语法糖实现类的继承,如果用原生JavaScript实现类的继承,有以下六种方式,其实现代码与优缺点分析如下
this
是JavaScript世界最让人迷惑的关键字之一,很多人对它一知半解,本文希望详细的把this
说明白。
JavaScript 允许在函数体内部,引用当前执行上下文的其他变量。
1 | function func() { |
如上代码,函数func
引用了当前执行上下文的变量a
,问题是这个函数func
可以在任意其他执行上下文中被调用,因此这个a
可能就指向不同了。正因为如此,JS引擎需要有一个机制,可以依靠其
优雅地、准确地指向当前代码运行时所处的上下文环境(context)。
因此便催生了“this”。
1 | //假设有个对象名字很长,而且有可能会改名 |
iAmALongLongLongNameObject
的方法func2
使用了this
关键字,是不是优雅多了?然后即使以后对象名字变化,func2
内部的代码也不用改变。func1
这种确实也可以实现与func2
同样的功能,但是就显得丑陋、不灵活了。
this
可以准确地指向(某个对象)而不会产生歧义。
1 | //全局变量 |
与“Java等高级语言的this
会指向对象的实例本身”不同,JavaScript的this
指向函数的调用位置的对象,也即调用该函数的对象。你需要知道,JavaScript中所有的函数都有属性,就如对象有属性一样。函数执行阶段(也即执行上下文的执行阶段)会获取this
属性的值,此时this
就是一个变量,储存着调用该函数的对象的值。
1 | var a = "coffe"; |
上面代码中,func
的调用者未通过点操作符.
指明,那它的调用者就是默认的全局对象window
,func
函数作为window
的一个方法,其体内的this.a
就是明确指代window
中属性a
,这种指向是准确而清晰的,不会有歧义。this
的这种灵活性在设计API的时候,会变得很方便和容易被复用。
调用位置就是函数在代码中被调用的位置,而不是声明的位置。研究调用位置,也即搞清“由谁在哪调用了这个函数”的问题。搞清楚了调用位置,才能准确地找到this
的指向。
要找到调用位置,最重要的是要分析是被谁、在哪调用。
1 | var module = { |
如上代码,要找到函数getX
的调用位置,需要先看哪儿调用了它,很明显,有函数有两处位置调用了函数getX()
,接下来分析是谁调用了它。
module
对象的getX
方法被调用。 这种情况被谁调用?很明显是被对象module
调用,this是指向module
。module
对象里面有一个属性x
,它的值是1891
,因此console.log(module.getX())
输出1891
。getX
被调用。 这种情况是被谁调用?我们都知道全局函数可以看作为window
对象的方法,那么,很明显现在getX
是被当做全局对象window
的一个方法被调用。我们搞清楚了调用位置之后,接下来就会着手判断this的指向。
this
既不指向函数自身也不指向函数的作用域,这之前是很多前端工程师容易误解的地方,现在澄清一下。
this
的指向,是在函数被调用的时候确定的,也就是执行上下文被创建时确定的;this
的指向和函数声明的位置没有任何关系,只取决于函数的调用位置(也即由谁、在什么地方调用这个函数);this
的指向就已经被确定了,在执行阶段this
指向不可再被更改。1 | var obj = { |
独立函数调用(无法应用后面其他指向规则时),this
指向全局对象window
。
1 | function func() { |
对于默认指向来说,决定this
指向对象的并不是调用位置是否处于严格模式,而是函数体是否处于严格模式。如果函数体处于严格模式,this
会指向undefined
,否则this
会指向全局对象。
1 | function func() { |
1 | function func() { |
还有一种默认指向,就是在SetTimeout或SetInterval结合使用时。代码示例如下。
1 | var num = 0; |
可以发现在setInterval和setTimeout中传入函数时,函数中的this会指向window对象。
隐式指向是日常开发中最常见的指向。
函数体内this
的指向由调用位置的调用者决定。如果调用者调用的函数,为某以个对象的方法,那么该函数在被调用时,其内部的this
指向该对象。
1 | function func() { |
对象属性引用链中只有最顶层或者说最后一层会影响调用位置,也就是说this
指向最终调用函数的对象。这句话可能说得比较拗口,其实简单通俗地说,this
指向最靠近被调用函数的对象,离得远的不是。举例来说:
1 | function func() { |
再来看看隐式丢失:
1 | function func() { |
JavaScript内置对象Function
的三个原型方法call()
、apply()
和bind()
,它们的第一个参数是一个对象,它们会把这个对象绑定到this
,接着在调用函数时让this
指向这个对象。
1 | var a = "makai"; |
另外,使用bind
可以修正SetTimeout和SetInterval的this指向:
1 | var num = 0; |
在JavaScript 中,构造函数只是一些使用new
操作符时被调用的函数。它们并不会属于某个类,也不会实例化一个类。实际上,它们甚至都不能算是一种特殊的类型(class),它们只是被new
操作符调用的普通函数而已。
使用new
来调用函数,或者说发生构造函数调用时,会自动执行下面的操作:
this
就指向了这个新对象);1 | function func(a) { |
可以将函数的返回值分成三种情况:
1 | 1、返回一个对象 |
所以使用new绑定时,需要判断函数返回的值是否为一个对象,如果是对象,那么this会绑定到返回的对象上
this
的指向判断,可以按照下面的优先级顺序来判断函数在某个调用位置应用的是哪条规则
new
中被调用(new
操作符指向)?如果是的话,this
绑定的是新创建的对象。
1 | function func(name) { |
call
、apply
、bind
显式指向?如果是的话,this
指向的是call、apply、bind三个方法的第一个参数指定的对象。
1 | var obj1 = { |
如果是的话,this
指向的是这个对象。
1 | var obj1 = { |
如果在严格模式下,就绑定到undefined
,否则绑定到全局对象。
1 | var a = "coffe"; //为全局对象window添加一个属性a |
null
或者undefined
作为this
指向的对象传入call
、apply
或者bind
,这些值在调用时会被忽略,实际应用的是默认指向规则。
1 | function func() { |
间接引用最容易在赋值时发生;间接引用时,调用这个函数会应用默认指向规则。
1 | function func() { |
箭头函数并不是使用function
关键字定义的,而是使用被称为“胖箭头”的操作符 =>
定 义的。
箭头函数不遵守this
的四种指向规则,而是根据函数定义时的作用域来决定 this
的指向。何谓“定义时的作用域”?就是你定义这个箭头函数的时候,该箭头函数在哪个函数里,那么箭头函数体内的this就是它父函数的this。
看下面代码加深理解:
1 | function func() { |
这个特性甚至被mozilla的MDN称作“没有this”,这种说法很费解。其实应该这么理解:一般而言,this的指向是在函数运行之后才确定的,而箭头函数的this指向在定义时也即调用之前就定死了,在运行之后无法更改,那相当于当成一个固定值的变量,此时this失去了原来作为“指向当前代码运行时所处的上下文环境(context)”的意义,所以MDN说箭头函数没有了this,我觉得翻译成“把this阉割了”更贴切 🤣 。