Javascript核心概念

keynote下载 | pdf下载

Notes

  1. 这次分享一些Javascript或者说ECMAScript中的一些比较基础的知识点。这些东西在我们日常的开发和调试中发挥着非常重要而又容易被忽略的作用。希望这次分享能够让大家有一个更深入的认识。

  2. 对于我们所使用的核心技术,我们要做到知其然知其所以然。这是一个非常重要的态度,只有这样,我们才能更好的理解这些技术背后蕴涵的思想,运作机制,以及优点缺陷,才能免于陷入缤纷表现的漩涡,能够做到透过现象看本质。

  3. 每个人的精力都是有限的,尤其是我这种愚钝的人,所以,把你的精力放在最值得放的一些地方,而不是处处留芳。

  4. 这次我们主要分享这几个知识点:原型链. 构造器. 执行上下文. 变量对象. 作用域链. 闭包和This

  5. 对象是一切的基础,我们从这里起步,用一个个对象构建前端的摩天大楼!对象实际非常简单,它就是一个容器,可以以键值对的形式储藏信息,例如姓名. 性别。对象有一个非常重要的内部属性proto,指向它的原型对象,以实现方法. 属性的复用或着说继承。

  6. 我们写过这样的代码,那它是如何执行的呢?事实上,这里涉及到一个属性查找的问题。[]对象本身没有hasOwnProperty这个方法,于是继续查找proto属性指向Array prototype是否有该方法,依然查找失败,于是继续查找Array prototype对象的proto属性指向的对象...最终找到Object.prototype上有这个方法,执行的结果为true。每个对象通过proto属性与自己的原型对象建立联系,而且这个过程是可以延续的,这就是js里原型链的概念。通过这个例子,我们知道:原型链在JS中的作用是查找对象的属性/方法。

  7. 原型链除了方法的复用还有什么作用呢?一个重要的例子就是instanceof操作符。instanceof操作符就是通过判断第二个操作数的原型对象是否在第一个操作数原型链上来进行判别。

  8. 接下来的一个疑问是:这些对象是怎么得来的呢?答案是构造器。

  9. 构造器,在JS中也就是函数。这里展示下函数的创建过程。这里需要注意的几点是:

    • [[Class]]:因为typeof不准确,现在各个js库都是根据这个属性来判别对象类型的(Object.prototype.toString.call(obj))
    • [[Prototype]]:因为函数也是一个对象,所以也有指向自己原型的内部属性
    • [[Call]]:在执行函数时调用,会产生一个新的执行上下文
    • [[Construct]]:在函数作为构造器调用时调用(通过new操作符)
    • [[Scope]]:保存函数的作用域
    • length:函数期望参数数目
    • prototype:用来为构造器创建对象设置[[prototype]]
  10. 制造对象通过函数对象的内部属性[[Construct]]进行。

    • 创建一个原生对象,这个对象是”纯洁的“
    • 添加这个原生对象的内部属性[[Class]],这个属性是用来判定对象类型的
    • 然后添加这个原生对象的内部属性[[Prototype]],也记做proto
    • 执行函数对象代码,其中的this设置为刚刚创建的原生对象。记执行代码返回值为R
    • 如果R为对象,则返回R,否则返回创建的原生对象O
  11. 一个简单的例子。

  12. 只要记住一点:构造器的prototype属性是用来为创建对象设置proto属性的,构造器作为对象也有自身的proto属性,指向的是Function.prototype对象。

  13. JS中的代码可以划分为三种类型:全局代码. 函数代码和eval代码。eval这个方法我们要尽量少的使用,因为不单有创建一个新解析器的开销,还有安全性方面的问题。

  14. 每种类型的代码在执行时都会在相应类型的上下文中,我们称之为执行上下文。

15-31. 这个例子简单展示了执行上下文栈的运行过程。

  1. 接下来我们详细了解一下执行上下文。执行上下文也可以理解为一个对象,主要的属性有三个:变量对象. 作用域链和thisValue。

    • 变量对象是一个保存当前代码中所有变量. 函数声明等的对象
    • 作用域链是由执行上下文的变量对象和所有父级作用域构成的,可以理解为一个数组
    • thisValue用来指明当前代码中this所代表的值
  2. 变量对象根据执行上下文的不同有一些差异:

    • 在全局执行上下文中,变量对象就是全局对象本身,而this也是指向全局对象的,所以它们三个相等。这个特性非常重要,因为我们可以通过访问全局对象来获得变量对象中储存的变量,这也是唯一可以访问变量对象的情形
    • 在函数执行上下文中,变量对象增加了arguments对象和行参等,这被称为活动对象。我们是不能直接访问到活动对象的
  3. 全局变量对象的一个例子。

  4. 活动对象的一个例子。注意arguments对象是一个array-like的对象而不是一个array。

  5. 代码的执行过程分为两步,首先进入执行上下文,主要是初始化上下文中的三个属性:变量对象. 作用域链和thisValue,然后开始执行代码。

  6. 在进入执行上下文阶段,初始化变量对象的过程是非常值得注意,因为在这个过程中,函数中所有形参. 变量声明. 函数声明都会被保存至变量对象中,进而影响作用域链,最终影响到变量的查找。初始化变量分为三步,这其中要注意的是:

    • 所有函数声明都会将函数名添加为变量对象的一个属性,函数对象为该属性的值,所以我们可以将函数定义在任何位置。为了主体逻辑更加清晰,一般我们都将函数声明放在靠后的位置
    • 所有变量声明的变量名都会作为变量对象的一个属性,其值为undefined。在执行代码阶段,解析器其实不会理会var的。为了防止忘记声明变量,所以建议在函数开始时声明所有局部变量
    • 变量对象属性之间的覆盖问题。函数声明的函数名可以覆盖之前所有VO属性,变量声明的变量只能覆盖之前的重名变量
  7. 声明可以不被覆盖,但是语句仍然会执行。在本例中,形参x没有被变量x覆盖,但执行var x = 10之后x的值仍然会变化。形参y会被函数声明y覆盖。

  8. 一个作用域链的例子。

  9. 作用域链的几个特性,静态作用域的概念非常重要,函数在声明的时候即已确定自身的作用域链内容([[Scope]])。

  10. 闭包实现的基础有两个方面:

    • 函数是一级对象,可以像普通对象一样赋值给变量. 作为函数返回值. 作为参数传递给函数
    • 函数对象会保存声明时的作用域链,也就是函数具有静态作用域

    这两个特性使得JS中每个函数都是一个闭包,为这种语言增加了无限的变化和魔力。

  11. 函数作为返回值,或者称为自底向上的闭包。

  12. 函数作为参数,或者称为自上到下的闭包。

  13. this在进入执行上下文阶段被赋值,在全局执行上下文中就是全局对象,在函数执行上下文中会随着函数的调用方式不同有所变化。

  14. 本文主要参考了Dmitry Soshnikov的ECMA-262-3 in detail系列文章,我本人获益匪浅。

  15. 谢谢大家。

参考

Proudly powered by Express. Designed by Spring.