一、课堂主线:
- 浮点数的内存表达及累计误差(遗留问题解析)—>表达式—>从语法角度观察—>从运行时的角度观察—>语句->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位)浮点数来表示数字。
例如: -12.5 在内存中的表示如下:
其中:
计算结果就是
参考上面的计算过程,如果想没有误差的表示一个数,个人觉得:
阶码部分的计算是2的整数次幂,后面的小数位的分母也是2的整数次幂,所以能够准确表示的数应该是一些2的整数次幂的倒数的整数倍。比如12.5=100*1/8。
2.3 累计误差计算
通过上面的计算会发现,大多数浮点数事实上都不是精确的表达的,所以做运算的时候,误差最后会被放大。
当利用递推公式对各部分计算结果进行积分(或累加)时,其误差也随之累加,最后所得到误差总和称为累积误差。
所以如果用非精确表达的浮点数进行多次计算,误差会逐级累加,很多时候是非收敛状态的。使用上一点要小心。
2.4 高精度计算的方案
对于一定要精确计算的场合下我们要怎么做呢?
简单的查了一些资料,没有准确验证过,但是感觉大概的思路是这样的:
2.4.1 使用大整数计算
以0.1+0.2 为例:
- 用字符串存储参与运算的数(保证原始数据准确无精度丢失)
- 根据小数点位置将两个数同乘以10 转换成整数 变成 1+2
- 将计算结果转换为字符串,移动小数点
附上两个成熟的解决方案:
- 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就可以写成0001 0101 1111 0010
- 0010 1111 0011 ——————————————— 0001 0111 1111 0101
0001 -> 1
- 0111 -> 7
- 1111 -> .
- 0101 ->5
三、表达式
到了表达式环节,终于开始和执行相关了,之前主要都是检查是否写对了这样的工作,有了表达式,才开始进入到是否有用的环节。
编程其实是一种表达,向我们的计算机去表达,计算机只能听懂那些指令集里面的话也就是一些二进制的内容,所以我们需要一个翻译来帮助我们流畅的去表达,因此我们重点要考量两件事情:
- 怎样让翻译准确的理解我们的表达,对各种有可能有歧义的地方做出一致性的约定。(用户<->翻译)
- 翻译在转述给计算机的时候发生了什么,是否和我们的预期完全一致。(翻译<->内核)
对应到老师《重学前端》的专栏里面:(小实验)理解编译原理:一个四则运算的解释器 的几个步骤:
- 定义四则运算:产出四则运算的词法定义和语法定义。
- 词法分析:把输入的字符串流变成 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 标准,你会发现它出现在各种场合,凡是需要“可以被修改的变量”的位置,都能见到它的身影。
ECMA-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. 分类
几个说明:
- no LineTerminator here 规则表示它所在的结构中的这一位置不能插入换行符。有的时候会与自动插入分号规则共同作用产生歧义。——《JavaScript语法(预备篇):到底要不要写分号呢?》
- 表达式具有短路的特性,例如:
true || foo();
这里的 foo 将不会被执行,这种中断后面表达式执行的特性就叫做短路。 ==
的行为:类型不同的变量比较时==运算只有三条规则:- undefined 与 null 相等;
- 字符串和 bool 都转为数字再比较;
- 对象转换成 primitive 类型再比较。
- 最佳实践:仅在确认 == 发生在 Number 和 String 类型之间时使用
3.2 从运行时的角度看
3.2.1 typeof 运算
对应到老师《重学前端》的专栏里面:JavaScript类型:关于类型,有哪些你不知道的细节?
“类型”在 JavaScript 中是一个有争议的概念。 一方面,标准中规定了运行时数据类型;另一方面,JavaScript 语言中提供了 typeof 这样的运算,用来返回操作数的类型,但 typeof 的运算结果,与运行时类型的规定有很多不一致的地方。 在下面表格中,多数项是对应的,但是请注意 object——Null 和 function——Object 是特例,我们理解类型的时候需要特别注意这个区别。
3.2.2 this 关键字
对应到老师《重学前端》的专栏里面:JavaScript执行(三):你知道现在有多少种函数吗?
this 关键字的行为
this 是 JavaScript 中的一个关键字,它的使用方法类似于一个变量。this 是执行上下文中很重要的一个组成部分。同一个函数调用方式不同,得到的 this 值也不同,
- 调用函数时使用的引用,决定了函数执行时刻的 this 值。实际上从运行时的角度来看,this 跟面向对象毫无关联,它是与函数调用时使用的表达式相关。
- 改为箭头函数后,不论用什么引用来调用它,都不影响它的 this 值。
- 在方法中,我们看到 this 的行为也不太一样,它得到了 undefined 的结果。
this 关键字的机制
切换上下文:在 JavaScript 标准中,为函数规定了用来保存定义时上下文的私有属性[[Environment]]。当一个函数执行时,会创建一条新的执行环境记录,记录的外层词法环境(outer lexical environment)会被设置成函数的[[Environment]]。
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 是弱类型语言,所以类型转换发生非常频繁,大部分我们熟悉的运算都会先进行类型转换。如图:
从标准里面找一个加法运算的例子:
ECMA-262.pdf P220 12.8.3.1 Runtime Semantics
12.8.3.1 Runtime Semantics:Evaluation AdditiveExpression : AdditiveExpression + MultiplicativeExpression
- Let lref _be the result of evaluating _AdditiveExpression.
- Let lval _be ? GetValue(_lref).
- Let rref _be the result of evaluating _MultiplicativeExpression.
- Let rval _be ? GetValue(_rref).
- Let lprim _be ? ToPrimitive(_lval).
- Let rprim _be ? ToPrimitive(_rval).
- If Type(lprim) is String or Type(rprim) is String, then
- Let lstr _be ? ToString(_lprim).
- Let rstr _be ? ToString(_rprim).
- Return the string-concatenation of lstr _and _rstr.
- Let lnum _be ? ToNumber(_lprim).
- Let rnum _be ? ToNumber(_rprim).
- 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. 内存表示
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 来获得拆箱后的基本类型。如果
valueOf
和toString
都不存在,或者没有返回基本类型,则会产生类型错误 TypeError。 - 在 ES6 之后,还允许对象通过显式指定
@@toPrimitive Symbol
来覆盖原有的行为。
o[Symbol.toPrimitive] = () => {console.log("toPrimitive"); return "hello"}
四、语句
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 指针的感觉,告诉你程序下一步要去哪,同时把上一步存在寄存器中的结果一同展示出来。
4.2 简单语句
- ExpressionStatement
- EmptyStatement
- DebuggerStatement
- ThrowStatement
- ContinueStatement
- BreakStatement
-
4.3 复合语句
BlockStatement
- IfStatement
- SwitchStatement
- IterationStatement
- WithStatement
- LabelledStatement
-
4.3.1 声明与作用域
对应到老师《重学前端》的专栏里面: - JavaScript语法(二):你知道哪些JavaScript语句?
Var 声明
JavaScript 执行前,会对脚本、模块和函数体中的语句进行预处理。预处理过程将会提前处理 var、函数声明、class、const 和 let 这些语句,以确定其中变量的意义。 var 声明var 声明永远作用于脚本、模块和函数体这个级别,在预处理阶段,不关心赋值的部分,只管在当前作用域声明这个变量。 var 声明语句是古典的 JavaScript 中声明变量的方式。而现在,在绝大多数情况下,let 和 const 都是更好的选择。 如果我们仍然想要使用 var,我的个人建议是,把它当做一种“保障变量是局部”的逻辑,遵循以下三条规则:
- 声明同时必定初始化;
- 尽可能在离使用的位置近处声明;
- 不要在意重复声明。
IIFE
因为早年 JavaScript 没有 let 和 const,只能用 var,又因为 var 除了脚本和函数体都会穿透,人民群众发明了“立即执行的函数表达式(IIFE)”这一用法,用来产生作用域,如:
for(var i = 0; i < 20; i ++) {
void function(i){
var div = document.createElement("div");
div.innerHTML = i;
div.onclick = function(){
console.log(i);
}
document.body.appendChild(div);
}(i);
}
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 语句,如
let o = {
[Symbol.iterator]:() => ({
_value: 0,
next(){
if(this._value == 10)
return {
done: true
}
else return {
value: this._value++,
done: false
};
}
})
}
for(let e of o)
console.log(e);
在实际操作中,我们一般使用 generator function。
function* foo(){
yield 0;
yield 1;
yield 2;
yield 3;
}
for(let e of foo())
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
_
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.