Skip to main content

JavaScript 的设计思想

函数是一等公民

JavaScript 中的对象的核心本质是由一组组属性和值组成的集合。

JavaScript 中的函数本质也是对象

可以被赋值,作为参数,作为返回值。但函数是一种特殊的对象,含有两个隐藏属性:name 和 code 。

name 就是函数名称,如果是匿名函数,name 的默认值是 anonymous。

code 表示函数代码,以字符串的形式存在内存中。但执行到函数调用语句时,V8 便从函数对象中取出code的属性值,然后解释执行这段代码。

函数可被调用

因为函数可被调用的特性,使得函数在被赋值、作为传参、作为返回值时有一点麻烦:

因为函数在执行过程中,V8 会为其维护一个作用域链,当使用这个函数赋值、作为传参、作为返回值,就必须确保函数引用的变量的存在,即作用域链是能正常工作的。

这中将外部变量和函数绑定的技术就是闭包。

对象中的快属性和慢属性

线性数据结构的查询效率会高于非线性数据结构。

对象的常规属性和排序属性

对象中的数字属性成为排序属性,打印属性时先打印出来,切实按大小排序的。V8 中称为 elements

常规属性就是字符串属性,打印顺序是按照定义时的顺序。V8 中称为 properties。

快属性和慢属性

V8 中的对象包含两个隐藏属性:elements 和 properties,分别指向elements对象和properties对象。elements对象存储排序属性,properties对象存储常规属性

elements中的属性是以线性结构存储的,查询效率高。

对于常规属性:

当数量少于10个时,直接存储在对象本身,称为对象内属性(in-object properties),结构也是线性的

当属性过多时,除了10个对象内属性,剩余的会存储在properties中,结构式非线性的。

所谓快慢是指存储的结构,导致查询的速度的快慢。

V8 中的对象表示

函数声明和函数表达式

语句和表达式

语句是操作值的式子,例如var x,表达式是表示值的式子,例如x = 2

V8 在编译阶段只会解析基础的语句,不会执行表达式。

函数声明和函数表达式

函数声明属于语句,在编译阶段,和变量声明一样存在变量提升。变量提升的本质是编译阶段这些变量会被放到创建的作用域中,以便执行阶段查找。

具体到var x变量声明,x 会被放到作用域中并被初始化为undefined;函数声明也会被放到作用域中,函数对象则被存储到内存中。

函数表达式则是表达式,编译阶段是不会被执行的。

立即调用函数也属于表达式,因此编译阶段不存在变量提升,不会创建函数对象,这样的好处就是函数和函数内部的变量不会被其他部分代码访问。

() 之间必须是表达式,其返回表达式的值。因此如果放入的函数,则返回函数对象。

原型、原型链、继承

JavaScript 中对象都有一个隐藏属性:__proto__,该属性指向的就是这个对象的原型对象。

而原型链就是通过__proto__属性实现的。

继承,简单理解就是一个对象可以访问另一个对象的属性和方法。JavaScript中通过原型就可以实现继承。

理论上可以直接修改一个对象的__proto__属性实现继承,但是实际项目中不能使用:1. 这是隐藏属性,不是标准定义;2. 使用该属性会造成严重的性能问题。

因此我们使用构造函数实现继承。使用new关键字调用函数。new 调用函数的背后流程:

// 构造函数是AniamlFactory
var obj = {}
obj.__protp__ = AniamlFactory.prototype
AnimalFactory.call(obj, args)

prototype 属性也是是函数的隐藏属性。构造函数实现继承,就是在 prototype 属性中添加需要继承的属性和方法。

作用域、作用域链、闭包、this

作用域链是由词法环境决定的。词法作用域又叫动态作用域。

闭包实际上是作用域链中的一环,即引用闭包的函数在调用时,这个函数先查找自己的 Local 作用域,然后查找闭包(Closure),然后查找上一层作用域

this实际上也是 Local 作用域的一个属性,访问它,也是从作用域中查找。但是this是动态的:call、apply、bind可修改,显示的通过对象调用、new关键字。

类型转换

a + b

V8 提供一个 ToPrimitive 方法,其作用是将 a 和 b 转换为原生基本数据类型然后再运算。

如果 a 和 b 都是基本类型,则不转换,如果是引用类型,则转换流程如下:

  1. 如果对象中存在 valueOf 方法,就调用 valueOf ,如果返回基本类型,就用这个值进行转换

  2. 如果 valueOf 没有转换成功,就会使用 toString 方法的返回值

  3. 如果两个方法都不返回基本类型,便会出发 TypeError 的错误