一、课堂主线:

  • 浮点数的内存表达及累计误差(遗留问题解析)—>表达式—>从语法角度观察—>从运行时的角度观察—>语句->Completion Record—>简单语句—>复合语句—>对象相关

P.S. 坦白地说,因为之前完全没有接触过前端的内容,更不用说完整的参与过一个具体的项目,完全是处在能读懂简单的业务逻辑的水平上。对整个体系都是似是而非的感觉。 所以上课的时候,很多内容都是消化不了的,对老师的讲解,很多都是听着挺开心,好像也听懂了,但是就是弄不明白什么要讲这个,老师是怎么从上一个问题过渡到这个地方的,讲这个是为了解决什么问题的…… 所以这个学习总结努力按照自己的想法去组织老师给出来的线索,为了更好的理解老师的讲述,这几天努力的把《重学前端》专栏里面“模块一:Javascript”部分看完了,然后再回过头来相互印证一下,感觉条理清晰了很多。以后专栏的学习一定要优先于老师上课的内容,很多东西先知道结果,再去听老师说为什么,学习效果会好很多。

二、浮点数(遗留问题)

2.1 内存表达

演示网站:https://baseconvert.com/ieee-754-floating-point
参考标准: IEEE754 Double Float

IEEE二进制浮点数算术标准(IEEE 754)是20世纪80年代以来最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用。这个标准定义了表示浮点数的格式(包括负零-0)与反常值(denormal number)),一些特殊数值(无穷(Inf)与非数值(NaN)),以及这些数值的“浮点数运算符”;它也指明了四种数值舍入规则和五种例外状况(包括例外发生的时机与处理方式)。 IEEE 754规定了四种表示浮点数值的方式:单精确度(32位)、双精确度(64位)、延伸单精确度(43比特以上,很少使用)与延伸双精确度(79比特以上,通常以80位实现)。只有32位模式有强制要求,其他都是选择性的。

在JS中,我们使用的是双精度(64位)浮点数来表示数字。
W03-H2 第三周学习总结 - 图1
例如: -12.5 在内存中的表示如下:
image.png
其中:

  • S:1位,符号位,编号63,0表示正,1表示负
  • E:11位,阶码位,编号62-52,
  • M:52位,小数位,编号51-0

    2.2 二进制表示下的可整除

    二进制表示和十进制结果的关系:
    W03-H2 第三周学习总结 - 图3
    比如对照我们上面提到的-12.5,

  • S=1

  • E=1026

计算结果就是
W03-H2 第三周学习总结 - 图4

参考上面的计算过程,如果想没有误差的表示一个数,个人觉得:
阶码部分的计算是2的整数次幂,后面的小数位的分母也是2的整数次幂,所以能够准确表示的数应该是一些2的整数次幂的倒数的整数倍。比如12.5=100*1/8。

2.3 累计误差计算

通过上面的计算会发现,大多数浮点数事实上都不是精确的表达的,所以做运算的时候,误差最后会被放大。

当利用递推公式对各部分计算结果进行积分(或累加)时,其误差也随之累加,最后所得到误差总和称为累积误差。

所以如果用非精确表达的浮点数进行多次计算,误差会逐级累加,很多时候是非收敛状态的。使用上一点要小心。

2.4 高精度计算的方案

对于一定要精确计算的场合下我们要怎么做呢?
简单的查了一些资料,没有准确验证过,但是感觉大概的思路是这样的:

2.4.1 使用大整数计算

以0.1+0.2 为例:

  1. 用字符串存储参与运算的数(保证原始数据准确无精度丢失)
  2. 根据小数点位置将两个数同乘以10 转换成整数 变成 1+2
  3. 将计算结果转换为字符串,移动小数点

附上两个成熟的解决方案:

  • https://github.com/MikeMcl/big.js
  • https://www.npmjs.com/package/js-big-decimal

    2.4.2 使用BCD编码进行计算

    BCD编码(一般指8421BCD码形式)。用4位二进制数来表示1位十进制数中的0~9这10个数。
    有了准确的十进制表示,然后基本上就是小学学的列算式计算的编码实现了。
    比如我们用1111代替小数点,15.2+2.3就可以写成

    1. 0001 0101 1111 0010
    • 0010 1111 0011 ——————————————— 0001 0111 1111 0101
  • 0001 -> 1

  • 0111 -> 7
  • 1111 -> .
  • 0101 ->5

    三、表达式

    到了表达式环节,终于开始和执行相关了,之前主要都是检查是否写对了这样的工作,有了表达式,才开始进入到是否有用的环节。
    编程其实是一种表达,向我们的计算机去表达,计算机只能听懂那些指令集里面的话也就是一些二进制的内容,所以我们需要一个翻译来帮助我们流畅的去表达,因此我们重点要考量两件事情:
  1. 怎样让翻译准确的理解我们的表达,对各种有可能有歧义的地方做出一致性的约定。(用户<->翻译)
  2. 翻译在转述给计算机的时候发生了什么,是否和我们的预期完全一致。(翻译<->内核)

对应到老师《重学前端》的专栏里面:(小实验)理解编译原理:一个四则运算的解释器 的几个步骤:

  • 定义四则运算:产出四则运算的词法定义和语法定义。
  • 词法分析:把输入的字符串流变成 token。
  • 语法分析:把 token 变成抽象语法树 AST。
  • 解释执行:后序遍历 AST,执行得出结果。

之前我们已经讨论过BNF定义和token了,所以后面我们要学习的主要是从语法的角度去看,和从运行时的角度去看。

3.1 从语法的角度看

表达式的最小单位,Primary Expression。它所涉及的语法结构也是优先级最高的。

PrimaryExpression : this IdentifierReference Literal ArrayLiteral ObjectLiteral FunctionExpression ClassExpression GeneratorExpression AsyncFunctionExpression AsyncGeneratorExpression RegularExpressionLiteral TemplateLiteral CoverParenthesizedExpressionAndArrowParameterList

3.1.1 左值表达式(LeftHandSideExpression)

对应到老师《重学前端》的专栏里面:JavaScript语法(三):什么是表达式语句?

左值表达式最经典的用法是用于构成赋值表达式,但是其实如果你翻一翻 JavaScript 标准,你会发现它出现在各种场合,凡是需要“可以被修改的变量”的位置,都能见到它的身影。

W03-H2 第三周学习总结 - 图5ECMA-262.pdf P201 12.3 Left-Hand-Side Expression

LeftHandSideExpression: NewExpression CallExpression

NewExpression : MemberExpression new NewExpression

MemberExpression: PrimaryExpression MemberExpression [ Expression] MemberExpression . IdentifierName MemberExpression TemplateLiteral SuperProperty MetaProperty new MemberExpression Arguments

SuperProperty : super [ Expression] super . IdentifierName

MetaProperty : NewTarget

NewTarget : new . target

CallExpression : CoverCallExpressionAndAsyncArrowHead SuperCall CallExpression Arguments CallExpression [ Expression ] CallExpression . IdentifierName CallExpression TemplateLiteral

SuperCall : super Arguments

3.2.3 右值表达式(RightHandSideExpression)

对应到老师《重学前端》的专栏里面:JavaScript语法(四):新加入的**运算符,哪里有些不一样呢?

在一些通用的计算机语言设计理论中,能够出现在赋值表达式右边的叫做:右值表达式(RightHandSideExpression),而在 JavaScript 标准中,规定了在等号右边表达式叫做条件表达式(ConditionalExpression),不过,在 JavaScript 标准中,从未出现过右值表达式字样。

1. 运算符优先级及左右结合性

对运算符来说的“优先级”,如果从我们语法的角度来看,那就是“表达式的结构”。讲“乘法运算的优先级高于加法”,从语法的角度看就是“乘法表达式和加号运算符构成加法表达式”。

参考: MDN:运算符优先级

下面的表将所有运算符按照优先级的不同从高(20)到低(1)排列。

优先级 运算类型 关联性 运算符
20 圆括号 n/a(不相关) ( … )
19 成员访问 从左到右 … . …
需计算的成员访问 从左到右 … [ … ]
new (带参数列表) n/a new … ( … )
函数调用 从左到右 … ( … )
可选链(Optional chaining) 从左到右 ?.
18 new (无参数列表) 从右到左 new …
17 后置递增(运算符在后) n/a … ++
后置递减(运算符在后) … --
16 逻辑非 从右到左 ! …
按位非 ~ …
一元加法 + …
一元减法 - …
前置递增 ++ …
前置递减 -- …
typeof typeof …
void void …
delete delete …
await await …
15 从右到左 … ** …
14 乘法 从左到右 … * …
除法 … / …
取模 … % …
13 加法 从左到右 … + …
减法 … - …
12 按位左移 从左到右 … << …
按位右移 … >> …
无符号右移 … >>> …
11 小于 从左到右 … < …
小于等于 … <= …
大于 … > …
大于等于 … >= …
in … in …
instanceof … instanceof …
10 等号 从左到右 … == …
非等号 … != …
全等号 … === …
非全等号 … !== …
9 按位与 从左到右 … & …
8 按位异或 从左到右 … ^ …
7 按位或 从左到右 `… …`
6 逻辑与 从左到右 … && …
5 逻辑或 从左到右 `… …`
4 条件运算符 从右到左 … ? … : …
3 赋值 从右到左 … = …
… += …
… -= …
… *= …
… /= …
… %= …
… <<= …
… >>= …
… >>>= …
… &= …
… ^= …
`… = …`
2 yield 从右到左 yield …
yield* yield* …
1 展开运算符 n/a ...
0 逗号 从左到右 … , …

2. 分类

W03-H2 第三周学习总结 - 图6 几个说明:

  • no LineTerminator here 规则表示它所在的结构中的这一位置不能插入换行符。有的时候会与自动插入分号规则共同作用产生歧义。——《JavaScript语法(预备篇):到底要不要写分号呢?
  • 表达式具有短路的特性,例如:true || foo();这里的 foo 将不会被执行,这种中断后面表达式执行的特性就叫做短路。
  • == 的行为:类型不同的变量比较时==运算只有三条规则:
    1. undefined 与 null 相等;
    2. 字符串和 bool 都转为数字再比较;
    3. 对象转换成 primitive 类型再比较。
    • 最佳实践:仅在确认 == 发生在 Number 和 String 类型之间时使用

3.2 从运行时的角度看

3.2.1 typeof 运算

对应到老师《重学前端》的专栏里面:JavaScript类型:关于类型,有哪些你不知道的细节?

“类型”在 JavaScript 中是一个有争议的概念。 一方面,标准中规定了运行时数据类型;另一方面,JavaScript 语言中提供了 typeof 这样的运算,用来返回操作数的类型,但 typeof 的运算结果,与运行时类型的规定有很多不一致的地方。 在下面表格中,多数项是对应的,但是请注意 object——Null function——Object 是特例,我们理解类型的时候需要特别注意这个区别。

W03-H2 第三周学习总结 - 图7

3.2.2 this 关键字

对应到老师《重学前端》的专栏里面:JavaScript执行(三):你知道现在有多少种函数吗?

this 关键字的行为

this 是 JavaScript 中的一个关键字,它的使用方法类似于一个变量。this 是执行上下文中很重要的一个组成部分。同一个函数调用方式不同,得到的 this 值也不同,

  • 调用函数时使用的引用,决定了函数执行时刻的 this 值。实际上从运行时的角度来看,this 跟面向对象毫无关联,它是与函数调用时使用的表达式相关。
  • 改为箭头函数后,不论用什么引用来调用它,都不影响它的 this 值。
  • 在方法中,我们看到 this 的行为也不太一样,它得到了 undefined 的结果。

this 关键字的机制

切换上下文:在 JavaScript 标准中,为函数规定了用来保存定义时上下文的私有属性[[Environment]]。当一个函数执行时,会创建一条新的执行环境记录,记录的外层词法环境(outer lexical environment)会被设置成函数的[[Environment]]。

W03-H2 第三周学习总结 - 图8
JavaScript 用一个栈来管理执行上下文,这个栈中的每一项又包含一个链表。当函数调用时,会入栈一个新的执行上下文,函数调用结束时,执行上下文被出栈。

this 则是一个更为复杂的机制,JavaScript 标准定义了 [[thisMode]] 私有属性。[[thisMode]] 私有属性有三个取值。

  • lexical:表示从上下文中找 this,这对应了箭头函数。
  • global:表示当 this 为 undefined 时,取全局对象,对应了普通函数。
  • strict:当严格模式时使用,this 严格按照调用时传入的值,可能为 null 或者 undefined。
    • 方法的行为跟普通函数有差异,恰恰是因为 class 设计成了默认按 strict 模式执行。

函数创建新的执行上下文中的词法环境记录时,会根据[[thisMode]]来标记新纪录的[[ThisBindingStatus]]私有属性。
代码执行遇到 this 时,会逐层检查当前词法环境记录中的[[ThisBindingStatus]],当找到有 this 的环境记录时获取 this 的值。这样的规则的实际效果是,嵌套的箭头函数中的代码都指向外层 this。

操作 this 的内置函数
  • Function.prototype.call 和 Function.prototype.apply 可以指定函数调用时传入的 this 值,
  • Function.prototype.bind 它可以生成一个绑定过的函数,这个函数的 this 值固定了参数:

3.2.3 运算前的类型转换

对应到老师《重学前端》的专栏里面:JavaScript类型:关于类型,有哪些你不知道的细节?

因为 JS 是弱类型语言,所以类型转换发生非常频繁,大部分我们熟悉的运算都会先进行类型转换。如图:
image.png
从标准里面找一个加法运算的例子:
ECMA-262.pdf P220 12.8.3.1 Runtime Semantics

12.8.3.1 Runtime Semantics:Evaluation AdditiveExpression : AdditiveExpression + MultiplicativeExpression

  1. Let lref _be the result of evaluating _AdditiveExpression.
  2. Let lval _be ? GetValue(_lref).
  3. Let rref _be the result of evaluating _MultiplicativeExpression.
  4. Let rval _be ? GetValue(_rref).
  5. Let lprim _be ? ToPrimitive(_lval).
  6. Let rprim _be ? ToPrimitive(_rval).
  7. If Type(lprim) is String or Type(rprim) is String, then
    1. Let lstr _be ? ToString(_lprim).
    2. Let rstr _be ? ToString(_rprim).
    3. Return the string-concatenation of lstr _and _rstr.
  8. Let lnum _be ? ToNumber(_lprim).
  9. Let rnum _be ? ToNumber(_rprim).
  10. Return the result of applying the addition operation to lnum _and _rnum.

3.2.4 值类型和引用类型

1. 值类型

基本数据类型:Number、String、Boolean、Null、 Undefined、Symbol(ES6),

  • 这些类型可以直接操作保存在变量中的实际值。
  • 存放在栈中的简单数据段,数据大小确定,内存空间大小可以分配,它们是直接按值存放的,所以可以直接按值访问

2. 引用类型

引用数据类型,存放在堆内存中的对象,变量其实是保存的在栈内存中的一个指针(保存的是堆内存中的引用地址),这个指针指向堆内存。
引用类型数据在栈内存中保存的对象在堆内存中的引用地址。通过这个引用地址可以快速查找到保存中堆内存中的对象。

3. 内存表示

W03-H2 第三周学习总结 - 图10

4. 访问机制

参考如下示例:

var a = [1,2,3,4,5]; var b = a; // 传址 ,对象中传给变量的数据是引用类型的,会存储在堆中; var c = a[0]; // 传值,把对象中的属性/数组中的数组项赋值给变量,这时变量C是基本数据类型,存储在栈内存中;改变栈中的数据不会影响堆中的数据

因为a是数组,属于引用类型,所以它赋予给b的时候传的是栈中的地址(相当于新建了一个不同名“指针”),而不是堆内存中的对象。因此,b中的修改都会影响到a。
而c仅仅是从a堆内存中获取的一个数据值,并保存在栈中。所以a、b修改的时候,会根据地址回到a堆中修改,c则直接在栈中修改,并不会影响到a堆内存中的数据。

5. 深拷贝/浅拷贝
  • 浅拷贝:在定义一个对象或数组时,变量存放的往往只是一个地址。当我们使用对象拷贝时,如果属性是对象或数组时,这时候我们传递的也只是一个地址。因此拷贝出来的新对象在访问该属性时,会根据地址回溯到原始对象指向的堆内存中,即新对象与原始对象发生了关联,两者的属性值会指向同一内存空间。
  • 深拷贝:我们不希望新对象与原始对象之间产生关联,那么这时候可以用到深拷贝。既然属性值类型是数组或对象时只会传址,那么我们就用递归来解决这个问题,把原始对象中所有属性类型为对象的值都遍历一遍并赋值给新对象。

3.2.5 装箱与拆箱

对应到老师《重学前端》的专栏里面:JavaScript类型:关于类型,有哪些你不知道的细节?

装箱(基本类型—>对应的对象)

每一种基本类型 Number、String、Boolean、Symbol 在对象中都有对应的类,所谓装箱转换,正是把基本类型转换为对应的对象,它是类型转换中一种相当重要的种类。

  • 我们可以利用一个函数的 call 方法来强迫产生装箱。

var symbolObject = (function(){ return this; }).call(Symbol("a"));

  • 使用内置的 Object 函数,我们可以在 JavaScript 代码中显式调用装箱能力。

var symbolObject = Object(Symbol("a"));

  • 每一类装箱对象皆有私有的 Class 属性,这些属性可以用 Object.prototype.toString 获取:在 JavaScript 中,没有任何方法可以更改私有的 Class 属性,因此 Object.prototype.toString是可以准确识别对象对应的基本类型的方法,它比 instanceof更加准确。
  • call 本身会产生装箱操作,所以需要配合 typeof 来区分基本类型还是对象类型。

拆箱(对象类型—>基本类型)

在 JavaScript 标准中,规定了 ToPrimitive 函数,它是对象类型到基本类型的转换(即,拆箱转换)。

  • 对象到 String 和 Number 的转换都遵循“先拆箱再转换”的规则。通过拆箱转换,把对象变成基本类型,再从基本类型转换为对应的 String 或者 Number。
  • 拆箱转换会尝试调用 valueOf 和 toString 来获得拆箱后的基本类型。如果 valueOftoString都不存在,或者没有返回基本类型,则会产生类型错误 TypeError。
  • 在 ES6 之后,还允许对象通过显式指定 @@toPrimitive Symbol 来覆盖原有的行为。

o[Symbol.toPrimitive] = () => {console.log("toPrimitive"); return "hello"}

四、语句

引用老师专栏里面图片:
W03-H2 第三周学习总结 - 图11

4.1 Completion Record

对应到老师《重学前端》的专栏里面:JavaScript执行(四):try里面放return,finally还会执行吗?

JavaScript 语句执行机制涉及的一种基础类型:Completion 类型。 这一机制的基础正是 JavaScript 语句执行的完成状态,我们用一个标准类型来表示:Completion Record。 Completion Record 表示一个语句执行完之后的结果,它有三个字段:

  • [[type]] 表示完成的类型,有 break continue return throw 和 normal 几种类型;
  • [[value]] 表示语句的返回值,如果语句没有,则是 empty;
  • [[target]] 表示语句的目标,通常是一个 JavaScript 标签。

JavaScript 正是依靠语句的 Completion Record 类型,方才可以在语句的复杂嵌套结构中,实现各种控制。

整体的感觉就像当你做完一件事情,然后高歌一曲“敢问路在何方?”,忽然天上传来空灵的声音,“当前任务你得到了60分,请继续向前走”
有一种 PC 指针的感觉,告诉你程序下一步要去哪,同时把上一步存在寄存器中的结果一同展示出来。

W03-H2 第三周学习总结 - 图12

4.2 简单语句

Var 声明

JavaScript 执行前,会对脚本、模块和函数体中的语句进行预处理。预处理过程将会提前处理 var、函数声明、class、const 和 let 这些语句,以确定其中变量的意义。 var 声明var 声明永远作用于脚本、模块和函数体这个级别,在预处理阶段,不关心赋值的部分,只管在当前作用域声明这个变量。 var 声明语句是古典的 JavaScript 中声明变量的方式。而现在,在绝大多数情况下,let 和 const 都是更好的选择。 如果我们仍然想要使用 var,我的个人建议是,把它当做一种“保障变量是局部”的逻辑,遵循以下三条规则:

  • 声明同时必定初始化;
  • 尽可能在离使用的位置近处声明;
  • 不要在意重复声明。

IIFE

因为早年 JavaScript 没有 let 和 const,只能用 var,又因为 var 除了脚本和函数体都会穿透,人民群众发明了“立即执行的函数表达式(IIFE)”这一用法,用来产生作用域,如:

  1. for(var i = 0; i < 20; i ++) {
  2. void function(i){
  3. var div = document.createElement("div");
  4. div.innerHTML = i;
  5. div.onclick = function(){
  6. console.log(i);
  7. }
  8. document.body.appendChild(div);
  9. }(i);
  10. }

4.3.2 可枚举对象、生成器

对应到老师《重学前端》的专栏里面:JavaScript语法(二):你知道哪些JavaScript语句?

for in 循环for in 循环枚举对象的属性,这里体现了属性的 enumerable 特征。 `Object.defineProperty(o, “c”, {enumerable:false, value:30})``` 给它添加了不可枚举的属性 c,之后我们用 for in 循环枚举它的属性,我们会发现,无法输出c。如果我们定义 c 这个属性时,enumerable 为 true,则 for in 循环中也能枚举到它。

iterator 机制。我们可以给任何一个对象添加 iterator,使它可以用于 for of 语句,如

  1. let o = {
  2. [Symbol.iterator]:() => ({
  3. _value: 0,
  4. next(){
  5. if(this._value == 10)
  6. return {
  7. done: true
  8. }
  9. else return {
  10. value: this._value++,
  11. done: false
  12. };
  13. }
  14. })
  15. }
  16. for(let e of o)
  17. console.log(e);

在实际操作中,我们一般使用 generator function。

  1. function* foo(){
  2. yield 0;
  3. yield 1;
  4. yield 2;
  5. yield 3;
  6. }
  7. for(let e of foo())
  8. console.log(e);

五、对象

5.1 对象的一种抽象方式

对应到老师《重学前端》的专栏里面:JavaScript对象:面向对象还是基于对象?

对象并不是计算机领域凭空造出来的概念,它是顺着人类思维模式产生的一种抽象(于是面向对象编程也被认为是:更接近人类思维模式的一种编程范式)。 不论我们使用什么样的编程语言,我们都先应该去理解对象的本质特征(参考 Grandy Booch《面向对象分析与设计》)。总结来看,对象有如下几个特点。

  • 对象具有唯一标识性:即使完全相同的两个对象,也并非同一个对象。
  • 对象有状态:对象具有状态,同一对象可能处于不同状态之下。
  • 对象具有行为:即对象的状态,可能因为它的行为产生变迁。

5.2 JS中的对象

  • JavaScript 创始人 Brendan Eich 在“原型运行时”的基础上引入了 new、this 等语言特性,使之“看起来更像 Java”。
  • JavaScript 标准对基于对象的定义,这个定义的具体内容是:“语言和宿主的基础设施由对象来提供,并且 JavaScript 程序即是一系列互相通讯的对象集合”。
  • 实际上 JavaScript 对象的运行时是一个“属性的集合”,属性以字符串或者 Symbol 为 key,以数据属性特征值或者访问器属性特征值为 value。对象是一个属性的索引结构。
  • JavaScript 中对象独有的特色是:对象具有高度的动态性,这是因为 JavaScript 赋予了使用者在运行时为对象添改状态和行为的能力。

5.2.1 原型链

对应到老师《重学前端》的专栏里面:JavaScript对象:我们真的需要模拟类吗?

原型是一种更接近人类原 始认知的描述对象的方法。 我们并不试图做严谨的分 类,而是采用“相似”这 样的方式去描述对象。 任何对象仅仅需要描述它自己与原型的区别即可。

“基于原型”的编程看起来更为提倡程序员去关注一系列对象实例的行为,而后才去关心如何将这些对象,划分到最近的使用方式相似的原型对象,而不是将它们分成类。

基于原型的面向对象系统通过“复制”的方式来创建新对象。

原型系统的“复制操作”有两种实现思路:

  • 一个是并不真的去复制一个原型对象,而是使得新对象持有一个原型的引用;
  • 另一个是切实地复制对象,从此两个对象再无关联。

历史上的基于原型语言因此产生了两个流派,显然,JavaScript 显然选择了前一种方式。

原型系统可以说相当简单,我可以用两条概括:

  • 如果所有对象都有私有字段[[prototype]],就是对象的原型;
  • 读一个属性,如果对象本身没有,则会继续访问对象的原型,直到原型为空或者找到为止。

    ES6 以来,JavaScript 提供了一系列内置函数,以便更为直接地访问操纵原型。三个方法分别为:

  • Object.create 根据指定的原型创建新对象,原型可以是 null;
  • Object.getPrototypeOf 获得一个对象的原型;
  • Object.setPrototypeOf 设置一个对象的原型。

利用这三个方法,我们可以完全抛开类的思维,利用原型来实现抽象和复用。

new 运算接受一个构造器和一组调用参数,实际上做了几件事:

  • 以构造器的 prototype 属性(注意与私有字段[[prototype]]的区分)为原型,创建新对象;
  • 将 this 和调用参数传给构造器,执行;
  • 如果构造器返回的是对象,则返回,否则返回第一步创建的对象。

new 这样的行为,试图让函数对象在语法上跟类变得相似,但是,它客观上提供了两种方式,一是在构造器中添加属性,二是在构造器的 prototype 属性上添加属性。

ES6 中引入了 class 关键字,并且在标准中删除了所有[[class]]相关的私有属性描述,类的概念正式从属性升级成语言的基础设施,从此,基于类的编程方式成为了 JavaScript 的官方编程范式。

在新的 ES 版本中,我们不再需要模拟类了:我们有了光明正大的新语法。而原型体系同时作为一种编程范式和运行时机制存在。
**

5.2.2 值属性与访问器属性

在JavaScript运行时,原生对象的 描述方式非常简单,我们只需要关心原型和属性两个部分。

对 JavaScript 来说,属性并非只是简单的名称和值,JavaScript 用一组特征(attribute)来描述属性(property)。 数据属性:它比较接近于其它语言的属性概念。数据属性具有四个特征。

  • value:就是属性的值。
  • writable:决定属性能否被赋值。
  • enumerable:决定 for in 能否枚举该属性。
  • configurable:决定该属性能否被删除或者改变特征值。

在大多数情况下,我们只关心数据属性的值即可。 访问器(getter/setter)属性,它也有四个特征。

  • getter:函数或 undefined,在取属性值时被调用。
  • setter:函数或 undefined,在设置属性值时被调用。
  • enumerable:决定 for in 能否枚举该属性。
  • configurable:决定该属性能否被删除或者改变特征值。

访问器属性使得属性在读和写时执行代码,它允许使用者在写和读属性时,得到完全不同的值,它可以视为一种函数的语法糖。

5.2.3 内置对象

作业: 找出 JavaScript 标准里所有的对象,分析有哪些对象是我们无法实现出来的,这些对象都有哪些特性?写一篇文章,放在学习总结里。



ECMA-262.pdf P127 9.4 Built-in Exotic Object Internal Methods and Slots
_

  1. Bound Function Exotic Objects
    • A bound function is an exotic object that wraps another function object. A bound function is callable (it has a [[Call]] internal method and may have a [[Construct]] internal method). Calling a bound function generally results in a call of its wrapped function.
  2. Array Exotic Objects
    • An Array object is an exotic object that gives special treatment to array index property keys (see 6.1.7). A property whose property name is an array index is also called an element. Every Array object has a non-configurable “length“ property whose value is always a nonnegative integer less than 232. The value of the “length“ property is numerically greater than the name of every own property whose name is an array index; whenever an own property of an Array object is created or changed, other properties are adjusted as necessary to maintain this invariant.
  3. String Exotic Objects
    • A String object is an exotic object that encapsulates a String value and exposes virtual integer-indexed data properties corresponding to the individual code unit elements of the String value. String exotic objects always have a data property named “length“ whose value is the number of code unit elements in the encapsulated String value. Both the code unit data properties and the “length“ property are non-writable and non-configurable.
  4. Arguments Exotic Objects
    • Arguments exotic objects have the same internal slots as ordinary objects. They also have a [[ParameterMap]] internal slot. Ordinary arguments objects also have a [[ParameterMap]] internal slot whose value is always undefined. For ordinary argument objects the [[ParameterMap]] internal slot is only used by Object.prototype.toString to identify them as such.
  5. Integer-Indexed Exotic Objects
    • An Integer-Indexed exotic object is an exotic object that performs special handling of integer index property keys. Integer-Indexed exotic objects have the same internal slots as ordinary objects and additionally [[ViewedArrayBuffer]],[[ArrayLength]], [[ByteOffset]], and [[TypedArrayName]] internal slots.
  6. Module Namespace Exotic Objects
    • A module namespace object is an exotic object that exposes the bindings exported from an ECMAScript Module (See 15.2.3). There is a one-to-one correspondence between the String-keyed own properties of a module namespace exotic object and the binding names exported by the Module. The exported bindings include any bindings that are indirectly exported using export * export items. Each String-valued own property key is the StringValue of the corresponding exported binding name. These are the only String-keyed properties of a module namespace exotic object. Each such property has the attributes { [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: false }. Module namespaceobjects are not extensible.
  7. Immutable Prototype Exotic Objects
    • An immutable prototype exotic object is an exotic object that **has a [[Prototype]] internal slot that will not change once it is initialized.

**

参考资料