坚持周总结系列第二周(2020.4.25)
JavaScript重学
JavaScript类型
JavaScript语言规定了7中语言类型:
UndefinedNullBooleanStringNumberSymbolObject
Undefined、Null
Undefined类型表示未定义,它的类型只有一个值,就是undefined。任何变量在赋值前是Undefined类型、值为undefined,一般我们可以用全局变量undefined(就是名为undefined的这个变量)来表达这个值,或者void运算来把任意一个表达式变成undefined值。- 但是呢,因为
JavaScript的代码undefined是一个变量,而并非是一个关键字,所以,我们为了避免无意中被篡改,我建议使用void 0来获取undefined值。 Null类型也只有一个值,就是null,它的语义表示空值,与undefined不同,null是JavaScript关键字,所以在任何代码中,你都可以放心用null关键字来获取null值。
String
String用于表示文本数据。String有最大长度是2^53 - 1,但是所谓最大长度,并不完全是你理解中的字符数。- 因为
String的意义并非“字符串”,而是字符串的UTF16编码,我们字符串的操作charAt、charCodeAt、length等方法针对的都是UTF16编码。所以,字符串的最大长度,实际上是受字符串的编码长度影响的。
Number
JavaScript中有 +0 和 -0,“忘记检测除以 -0,而得到负无穷大”的情况经常会导致错误,而区分 +0 和 -0 的方式,正是检测1/x是Infinity还是-Infinity。- 非整数的
Number类型无法用 ==(=== 也不行) 来比较,正确的比较方法是使用JavaScript提供的最小精度值:
console.log( Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON);
Symbol
Symbol是ES6中引入的新类型,它是一切非字符串的对象key的集合,在ES6规范中,整个对象系统被用Symbol重塑。Symbol可以具有字符串类型的描述,但是即使描述相同,Symbol也不相等。- 创建
Symbol:var mySymbol = Symbol("my symbol");
Object
- 在
JavaScript中,对象的定义是“属性的集合”。属性分为数据属性和访问器属性,二者都是key-value结构,key可以是字符串或者Symbol类型。
类型转换
-
因为
JS是弱类型语言,所以类型转换发生非常频繁,大部分我们熟悉的运算都会先进行类型转换。 -
StringToNumber- 在不传入第二个参数的情况下,
parseInt只支持 16 进制前缀“0x”,而且会忽略非数字字符,也不支持科学计数法,所以建议传入parseInt的第二个参数。 parseFloat则直接把原字符串作为十进制来解析,它不会引入任何的其他进制。
- 在不传入第二个参数的情况下,
-
NumberToString -
在较小的范围内,数字到字符串的转换是完全符合你直觉的十进制表示。当
Number绝对值较大或者较小时,字符串表示则是使用科学计数法表示的。 -
装箱转换
- 每一种基本类型
Number、String、Boolean、Symbol在对象中都有对应的类,所谓装箱转换,正是把基本类型转换为对应的对象,它是类型转换中一种相当重要的种类。 Object.prototype.toString是可以准确识别对象对应的基本类型的方法,它比instanceof更加准确。
- 每一种基本类型
-
拆箱转换
- 在
JavaScript标准中,规定了ToPrimitive函数,它是对象类型到基本类型的转换(即拆箱转换)。 - 拆箱转换会尝试调用
valueOf和toString来获得拆箱后的基本类型。如果valueOf和toString都不存在,或者没有返回基本类型,则会产生类型错误TypeError。 - 在
ES6之后,还允许对象通过显式指定@@toPrimitive Symbol来覆盖原有的行为。
o[Symbol.toPrimitive] = () => {console.log("toPrimitive"); return "hello"} - 在
JavaScript对象
对象的概念
- 一个可以触摸或者看见的东西
- 人的智力可以理解的东西
- 可以指导思考或行动的东西
对象的特征
- 对象具有唯一标识性:即使完全相同的两个对象,也并非同一个对象
- 对象具有状态:同一个对象可能处于不同状态
- 对象具有行为:对象的状态,可能因为它的行为产生变迁
- 在
JavaScript中,将状态和行为统一抽象为“属性”
JavaScript 对象的两类属性
JavaScript对象具有高度的动态性,这是因为 JavaScript赋予了使用者在运行时为对象添改状态和行为的能力。
数据属性
数据属性有四个特征:
value:属性值writeable:属性能否被赋值enumerable:属性是否可枚举configurable:属性是否可删除或者改变特征值
访问器属性
访问器属性也有四个特征:
getter:取值时调用setter:设置值时调用enumerable:属性是否可枚举configurable:属性是否可删除或者改变特征值
查看对象属性特征:Object.getOwnPropertyDescripter
改变对象属性的特征:Object.defineProperty
Object.defineProperty(o, "b", {
value: 2,
writable: false,
enumerable: false,
configurable: true
});
Javascript原型
Javascript原型系统简单概括:
- 如果所有对象都有私有字段[[prototype]],就是对象的原型
- 读一个对象的属性,如果本身没有,则会继续访问对象的原型,直到原型为空或者找到为止
ES6之后,提供的操纵对象原型的三个方法:
Obejct.create根据指定的原型创建对象,原型可以是nullObject.getPrototypeOf获得一个对象的原型Object.setPrototypeOf设置一个对象的原型
var cat={
say(){
console.log('meow~')
},
jump(){
console.log('jump')
}
}
var tiger=Object.create(cat,{
say:{
writeable:true,
configurable:true,
enumerable:true,
value:function(){
console.log('roar~')
}
}
})
var anotherCat=Object.create(cat)
anotherCat.say()// meow~
var anotherTiger=Object.create(tiger)
anotherTiger.say()// roar~
早期版本中的类与原型
早期版本中,类的定义是一个私有属性[[class]],使用者唯一可以访问[[class]]属性的方式是通过Object.prototype.toString。
var o = new Object;
var n = new Number;
var s = new String;
var b = new Boolean;
var d = new Date;
var arg = function(){ return arguments }();
var r = new RegExp;
var f = new Function;
var arr = new Array;
var e = new Error;
console.log([o, n, s, b, d, arg, r, f, arr, e].map(v => {
return Object.prototype.toString.call(v)
}))
/* ["[object Object]", "[object Number]", "[object String]", "[object Boolean]", " [object Date]", "[object Arguments]", "[object RegExp]", "[object Function]", " [object Array]", "[object Error]"]
*/
在ES5开始,[[class]]私有属性被Symbol.toStringTag代替。
var o = { [Symbol.toStringTag]: "MyObject" }
console.log(o + "");// [object MyObject]
console.log(Object.prototype.toString.call(o))// [object MyObject]
这里使用字符串加法触发了Obejct.prototype.toString的调用。
new运算符
new运算符接受一个构造器和一组调用参数,实际上做了下面几件事情:
- 以构造器的
prototype属性为原型,创建新对象 - 将
this和调用参数传给构造器,执行 - 如果构造器返回的是对象,则返回,否则返回第一步创建的对象
new的行为提供了两种模拟类的方法:
- 在构造器中添加属性
- 在构造器的
prototype属性上添加属性
function c1(){
this.p1=1
this.p2=function(){
console.log(this.p1)
}
}
var o1=new c1
o1.p2()// 1
function c2(){}
c2.prototype.p1=1
c2.prototype.p2=function(){
console.log(this.p1)
}
var o2=new c2
o2.p2()// 1
ES6中的类
类的基本写法:
class Rectangle{
constructor(height,width){
this.height=height
this.width=width
}
get area(){
return this.calArea()
}
calArea(){
return this.height*this.width
}
}
类的继承能力:
class Animal{
constructor(name){
this.name=name
}
speak(){
console.log(this.name+' makes a nise.')
}
}
class Dog extends Animal{
constructor(name){
super(name)
}
speak(){
console.log(this.name+' breaks.')
}
}
let d=new Dog('Mitize')
d.speak()// Mitize breaks.
Javascript对象分类
Javascript中对象分类:
- 宿主对象(host Objects):由JavaScript宿主环境提供的对象,它们的行为完全由宿主环境决定
- 内置对象(Built-in Objects):由JavaScript语言提供的对象
- 固有对象(Intrinsic Objects):由标准规定,随着JavaScript运行时创建而自动创建的对象实例
- 原生对象(Native Objects):可以由用户通过Array、
RegExp等内置构造函器或者特殊语法创建的对象 - 普通对象(Ordinary Objects):由语法
{}、Object构造器或者class关键字定义类创建的对象,他们能够被原型继承
宿主对象
最常见的就是浏览器环境,其中全局对象window上的属性,一部分来自浏览器环境,一部分来自JavaScript语言。
内置对象.固有对象
固有对象在任何 JavaScript 代码执行前就已经被创建出来了,它们通常扮演者类似基础库的角色。我们前面提到的“类”其实就是固有对象的一种。ECMA标准为我们提供了一份固有对象表,里面含有 150+ 个固有对象,链接地址。
内置对象.原生对象

几乎所有这些构造器的能力都是无法用纯JavaScript代码实现,它们也无法用class/extends语法来继承。这些构造器创建的对象多数使用了私有字段,例如:
Error: [[ErrorData]]Boolean: [[BooleanData]]Number: [[NumberData]]Date: [[DateValue]]RegExp: [[RegExpMatcher]]Symbol: [[SymbolData]]Map: [[MapData]]
这些字段使得原型继承方法无法正常工作。
用对象来模拟函数与构造器:函数对象与构造器对象
- 函数对象的定义是:具有
[[call]]私有字段的对象 - 构造器对象的定义是:具有私有字段
[[construct]]的对象 - 任何对象只需要实现
[[call]],它就是一个函数对象,可以去作为函数被调用 - 任何对象只要能实现
[[construct]],它就是一个构造器对象,可以作为构造器被调用
构造函数和函数表现不一致的特殊情况
- 内置对象Date作为构造器调用时产生新对象,作为函数调用时,产生字符串
console.log(typeof(new Date))// object
console.log(typeof(Date()))// string
- Image构造器,不允许以函数形式调用
console.log(new Image())
console.log(Iamge())// 报错
- 基本类型(String、Number、Boolean),它们的构造器被当做函数调用,会产生类型转换效果
ES6之后,箭头函数创建的就仅仅是函数,不能作为构造器使用- 用户创建的函数或者构造器,
[[call]]和[[constructor]]是一致的
function f(){
return 1
}
var v=f()
var o=new f()
[[constructor]]的执行过程:
- 以
Object.prototype为原型创建一个新对象 - 以新对象为this,执行函数的
[[call]] - 如果
[[call]]的返回值是对象,则返回这个对象,否则返回第一步创建的新对象
那么如果我们的构造器返回了一个新的对象,此时 new 创建的新对象就变成了一个构造函数之外完全无法访问的对象,这一定程度上可以实现“私有”。
function cls(){
this.a=100
return{
getValue:()=>this.a
}
}
var o=new cls()
o.getValue()// 100
// a在外面永远无法访问到
特殊行为的对象
Array:Array的length属性根据最大的下标自动发生变化Object.prototype:作为所有正常对象的默认原型,不能再给它设置原型String:为了支持下标运算,String的正整数属性访问会去字符串里查找Arguments:arguments的非负数整型下标属性跟对应的变量联动- 模块的
nameSpace对象:尽量只用于import - 类数组和数组缓冲区:根内存块关联,下标运算比较快
bind后的function:跟原来的函数相关联
JavaScript固有对象
三个值:Infinity、NaN、undefined
九个函数:eval、isFinite、isNaN、parseInt、parseFloat、decodeURI、decodeURIComponent、encodeURI、encodeURIComponent
一些构造器:Array、Date、RegExp、Promise、Proxy、Map、WeakMap、Set、WeakSet、Function、Boolean、String、Number、Symbol、Object、Error、EvalError、RangeError、ReferenceError、SyntaxError、TypeError、URIError、ArrayBuffer、SharedArrayBuffer、DataView、Typed Array、Float32Array、Float64Array、Int8Array、Int16Array、Int32Array、UInt8Array、UInt16Array、UInt32Array、UInt8ClampedArray
四个用于当作命名空间的对象:Atomics、JSON、Math、Reflect
JavaScript执行
宏观任务和微观任务
宏观任务队列就相当于事件循环,每个宏观任务中又包含一个微观任务队列。
Promise
基本用法:
function sleep(duration){
return new Promise(function(resolve,reject){
setTimeout(resolve,duration)
})
}
sleep(1000).then(()=>console.log('finished'))
异步执行顺序:
- 首先分析有多少宏任务
- 在每个宏任务中,分析有多少个微任务
- 根据调用次序,确定宏任务中的微任务执行顺序
- 根据宏任务的触发顺序和调用次序,确定宏任务执行顺序
- 确定整个执行顺序
function sleep(duration){
return new Promise(function (resolve,reject){
console.log('b')
setTimeout(resolve,duration)
})
}
console.log('a')
sleep(1000).then(()=>console.log('c'))
新特性async/await
async函数强大之处在于,它是可以嵌套的。
function sleep(duration){
return new Promise(function (resolve,reject){
console.log('b')
setTimeout(resolve,duration)
})
}
async function foo(name){
await sleep(1000)
console.log(name)
}
async function foo2(){
await foo('a')
await foo('b')
}
闭包和执行上下文

闭包
Javascript中的闭包
- 环境部分
- 环境:函数的词法环境(执行上下文的一部分)
- 标识符列表:函数中用到的未声明的变量
- 表达式部分:函数体
JavaScript 中的函数完全符合闭包的定义。它的环境部分是函数词法环境部分组成,它的标识符列表是函数中用到的未声明变量,它的表达式部分就是函数体。
执行上下文
ES2018中的执行上下文:
lexical environment:词法环境,当获取变量或者 this 值时使用variable environment:变量环境,当声明变量时使用code evaluation state:用于恢复代码执行位置Function:执行的任务是函数时使用,表示正在被执行的函数ScriptOrModule:执行的任务是脚本或者模块时使用,表示正在被执行的代码Realm:使用的基础库和内置对象实例Generator:仅生成器上下文有这个属性,表示当前生成器
var声明与赋值
- var 声明作用域函数执行的作用域。也就是说,var 会穿透 for 、if 等语句
- 立即执行的函数表达式(
IIFE),通过创建一个函数,并且立即执行,来构造一个新的域,从而控制 var 的范围,形成块级作用域。
void function(){
var a
}()// 推荐的IIFE函数写法
- with会改变作用域
var b
void function(){
var env={b:1}
b=2
console.log('in function b:',b)// 2
with(env){
console.log('in with b:',b)// 1
}
}()
console.log('in golbal b:',b)// 2
let声明
产生let块级作用域语句:
- for、if、switch、try、catch、finally
Realm
在最新的标准(9.0)中,JavaScript 引入了一个新概念 Realm,它的中文意思是“国度”“领域”“范围”。Realm 中包含一组完整的内置对象,而且是复制关系。
浏览器环境中获取来自两个 Realm 的对象,它们跟本土的 Object 做 instanceOf 时会产生差异:
var iframe = document.createElement('iframe')
document.documentElement.appendChild(iframe)
iframe.src="javascript:var b = {};"
var b1 = iframe.contentWindow.b;
var b2 = {};
console.log(typeof b1, typeof b2); //object object
console.log(b1 instanceof Object, b2 instanceof Object); //false true
函数
函数分类
- 普通函数,用function关键字定义的函数
- 箭头函数
- 方法,在class中定义的函数
- 生成器函数,用function*定义的函数
- 类:用class定义的类,实际上也是函数
- 异步函数:普通函数加上
async
this关键字
普通函数的this值由调用它所使用的的引用决定,因为我们获取函数的表达式,是加上他返回的并非函数本身,而是一个Reference类型。
Reference类型由两部分组成:一个对象和一个属性值。
调用函数时使用的引用,决定了函数执行时的this值。
在JavaScript中,定义了函数用来保存定义时上下文的私有属性[[Environment]]。当一个函数执行时,会创建一条新的执行环境记录,记录的外层词法环境(outer lexical environment)会被设置成函数的[[Environment]],这就是切换上下文。
var a=1
foo()
// 在别处定义了foo
var b=2
function foo(){
console.log(b)// 2
console.log(a)// error
}
[[thisModel]]私有属性的三个取值:
- lexical:表示从上下文中找this,对应箭头函数
- global:表示当this为undefined时,取全局对象,对应普通函数
- strict:当严格模式时使用,this严格按照调用时传入的值,可能为null或者undefined
操作this的内置函数
Function.prototype.call和Function.prototype.apply可以指定函数调用时传入的 this 值,call 和 apply 作用是一样的,只是传参方式有区别。Function.prototype.bind它可以生成绑定过的函数,这个函数的 this 值固定了参数。- call、bind 和 apply 用于不接受 this 的函数类型如箭头、class 都不会报错。这时候,它们无法实现改变 this 的能力,但是可以实现传参。
语句
Completion Record 表示一个语句执行完之后的结果,它有三个字段:
- [[type]] 表示完成的类型,有 break continue return throw 和 normal 几种类型;
- [[value]] 表示语句的返回值,如果语句没有,则是 empty;
- [[target]] 表示语句的目标,通常是一个 JavaScript 标签(标签在后文会有介绍)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-82zEvCpV-1587799627636)(https://static001.geekbang.org/resource/image/98/d5/98ce53be306344c018cddd6c083392d5.jpg)]
普通语句:
普通语句执行时,从前到后顺序执行,没有任何分支或者重复逻辑。
语句块:
语句块就是拿大括号括起来的一组语句,它是一种语句的复合结构,可以嵌套。
语句块本身并不复杂,我们需要注意的是语句块内部的语句的 Completion Record 的[[type]] 如果不为 normal,会打断语句块后续的语句执行。
控制型语句:
控制型语句分为两部分:
- 对内部造成影响,如:if、switch、while/for、try
- 对外部造成影响,如:break、continue、return、throw
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KgGezpq4-1587799627638)(https://static001.geekbang.org/resource/image/77/d3/7760027d7ee09bdc8ec140efa9caf1d3.png)]
因为 finally 中的内容必须保证执行,所以 try/catch 执行完毕,即使得到的结果是非 normal 型的完成记录,也必须要执行 finally,也就是说在try语句中执行了return,finally中的语句还是会执行。
带标签的语句:
大部分时候,这个东西类似于注释,没有任何用处。唯一有作用的时候是:与完成记录类型中的 target 相配合,用于跳出多层循环。
outer:while(true){
inner:while(true){
break:outer
}
}
console.log('finished')
JavaScript文法
词法定义
WhiteSpace空白字符LineTerminator换行符Comment注释Token词IdentifierName标识符名称,如变量名、关键字Punctuator符号NumericLiteral数字直接量StringLiteral字符串直接量Template字符串模板
空白字符WhiteSpace
JavaScript支持的空白符号:
- 缩进符
- 垂直方向缩进符
- 分页符
- 普通空格
- 非断行空格
- 零宽非断行空格
- Unicode中的空格
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ik4foZ6G-1587799627643)(https://static001.geekbang.org/resource/image/dd/60/dd26aa9599b61d26e7de807dee2c6360.png)]
换行符
JavaScript提供了4种换行符:、、、
注释
单行注释、多行注释
标识符名称 IdentifierName
IdentifierName可以以美元符“$”、下划线“_”或者 Unicode 字母开始,除了开始字符以外,IdentifierName中还可以使用 Unicode 中的连接标记(汉字)、数字、以及连接符号。
IdentifierName可以是Identifier、NullLiteral、BooleanLiteral或者keyword,在ObjectLiteral中,IdentifierName还可以被直接当做属性名称使用。
符号Punctuator
{ ( ) [ ] . ... ; , < > <= >= == != === !== + - * % ** ++ -- << >> >>> & | ^ ! ~ &&
数字直接量
JavaScript中数值直接量可以支持四种写法:十进制数、二进制数、八进制数、十六进制数
12.toString()这时候12. 会被当做省略了小数点后面部分的数字而看成一个整体,所以我们要想让点单独成为一个 token,就要加入空格12. toString()。
字符串直接量
JavaScript 中的 StringLiteral支持单引号和双引号两种写法。
- 转义字符:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sfvG69Gd-1587799627646)(https://static001.geekbang.org/resource/image/02/75/022c2c77d0a3c846ad0d61b48c4e0e75.png)]
正则表达式直接量RegularExpressionLiteral
- 正则表达式由 Body 和 Flags 两部分组成
- 其中 Body 部分至少有一个字符,第一个字符不能是 *(因为 /* 跟多行注释有词法冲突)。
- 正则表达式并非机械地见到/就停止,在正则表达式[ ]中的/就会被认为是普通字符。
字符串模板Template
在JavaScript中,a${b}c${d}e被拆成了五个部分:
- a${ 这个被称为模板头
- }c${ 被称为模板中段
- }e 被称为模板尾
- b 和 d 都是普通标识符
模板支持添加处理函数的写法,这时模板的各段会被拆开,传递给函数当参数:
function f(){
console.log(arguments)
}
var a='world'
f`Hello ${a}!`// [["Hello","!"],world]
一个四则运算解释器
分析
定义四则运算的步骤:
- 定义四则运算:产出四则运算的词法定义和语法定义
- 词法分析:把输入的字符串变成token
- 语法分析:把token变成抽象语法树
AST - 解释执行:后序遍历
AST,执行的出结果
定义四则运算
- Token
Number:1 2 3…9 0的组合Operator:+、-、*、/ 之一
WhiteSpace:LineTerminator:、
语法定义:
<Expression> ::=
<AdditiveExpression><EOF>
<AdditiveExpression> ::=
<MultiplicativeExpression>
|<AdditiveExpression><+><MultiplicativeExpression>
|<AdditiveExpression><-><MultiplicativeExpression>
语法分析:状态机
语法分析,就是把字符串流变成token流。状态机就是根据我们输入的字符判断属于那种状态。
例如输入1024 + 2 * 256最后我们得到的结果是:
1024
+
2
*
256
语法分析:LL
LL 语法分析根据每一个产生式来写一个函数。
假设拿到的token:
var tokens = [{ type:"Number", value: "1024"},
{ type:"+" value: "+"},
{ type:"Number", value: "2"},
{ type:"*" value: "*"},
{ type:"Number", value: "256"},
{ type:"EOF"}];
根据拿到的token转换成AST。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x6uXhvoO-1587799627651)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\1587784424526.png)]
解释执行
得到了 AST 之后,最困难的一步我们已经解决了。这里我们就不对这颗树做任何的优化和精简了,那么接下来,直接进入执行阶段。我们只需要对这个树做遍历操作执行即可。
JavaScript语法
要不要写分号
JavaScript自动插入分号规则
- 要有换行符,且下一个符号是不符合语法的,那么就尝试插入分号
- 有换行符,且语法中规定此处不能有换行符,那么就插入分号
- 源代码结束处,不能形成完整的脚本或者模块结构,那么就自动插入分号
no LineTerminator here规则
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mrjoRKr8-1587799627652)(https://static001.geekbang.org/resource/image/c3/ad/c3ffbc89e049ad1901d4108c8ad88aad.jpg)]
不写分号要注意的情况
- 以括号开头的语句
- 以数组开头的语句
- 以正则表达式开头的语句
- 以Template开头的语句
模块导入导出
脚本和模块
- JavaScript有两种源文件,一种是脚本,一种是模块。脚本是可以通过浏览器或者node环境引入执行,模块只能由JavaScript代码用import引入执行。
- 现代浏览器可以支持用 script 标签引入模块或者脚本,如果要引入模块,必须给 script 标签添加 type=“module”。
<script type="module" src="xxxxx.js"></script>
- 脚本中可以包含语句。模块中可以包含三种内容:import声明,export声明和语句。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8RpSrVmV-1587799627655)(https://static001.geekbang.org/resource/image/43/44/43fdb35c0300e73bb19c143431f50a44.jpg)]
import声明
- 直接import一个模块
- 带from的import,它能引入模块中的部分内容
import 'mod'// 引入一个模块
import v from 'mod'// 把模块默认的导出值放入变量v
- 直接 import 一个模块,只是保证了这个模块代码被执行,引用它的模块是无法获得它的任何信息的。
- 带 from 的 import 意思是引入模块中的一部分信息,可以把它们变成本地的变量。
带 from 的 import 细分又有三种用法:
import x from "./a.js"引入模块中导出的默认值import {a as x, modify} from "./a.js"引入模块中的变量import * as x from "./a.js"把模块中所有的变量以类似对象属性的方式引入
语法要求不带 as 的默认值永远在最前。
导入与一般的赋值不同,导入后的变量只是改变了名字,它仍然与原来的变量是同一个。
export声明
- 独立声明使用export
export {a,b,c}
我们也可以直接在声明型语句前添加 export 关键字,这里的 export 可以加在任何声明性质的语句之前,如:var 、function(含async和generator)、class、let、const
- 和default联合使用
export default 表示导出一个默认变量值,它可以用于 function 和 class。这里导出的变量是没有名称的,可以使用import x from "./a.js"这样的语法,在模块中引入。
var a = {};
export default a;
但是,这里的行为跟导出变量是不一致的,这里导出的是值,导出的就是普通变量 a 的值,以后 a 的变化与导出的值就无关了,修改变量 a,不会使得其他模块中引入的 default 值发生改变。
函数体
函数体有四种形式:
- 普通函数体
function foo(){
...
}
- 异步函数体
async function foo(){
...
}
- 生成器函数体
function *foo(){
...
}
- 异步生成器函数体
async function *foo(){
...
}

预处理
JavaScript 执行前,会对脚本、模块和函数体中的语句进行预处理。预处理过程将会提前处理 var、函数声明、class、const 和 let这些语句,以确定其中变量的意义。
-
var声明:
var 声明永远作用于脚本、模块和函数体这个级别,在预处理阶段,不关心赋值的部分,只管在当前作用域声明这个变量。 -
function声明:
在全局(脚本、模块和函数体),function 声明表现跟 var 相似,不同之处在于,function 声明不但在作用域中加入变量,还会给它赋值。
-
class声明:
class 声明在全局的行为跟 function 和 var 都不一样,在 class 声明之前使用 class 名,会抛错。
指令序言
这里的指令序言最早是为了 use strict 设计的,它规定了一种给 JavaScript 代码添加元信息的方式。
语句
JavaScript中,语句分成两种:声明和语句。
- 普通语句

- 声明型语句
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n0P2qC8S-1587799627662)(https://static001.geekbang.org/resource/image/0e/38/0e5327528df12d1eaad52c4005efff38.jpg)]
- 语句块:语句块就是一对大括号
- 空语句:空语句就是一个独立的分号,实际上没什么大用
- if语句:在满足条件时执行它的内容语句
- switch语句: switch 语句是跳转的变形,所以我们如果要用它来实现分支,必须要加上 break
- 循环语句:
- while循环和do while循环
- 普通for循环
- for in循环
- for of 循环和 for await of 循环
- return
- break语句和continue语句
- with语句
- try 语句和 throw 语句
- debugger语句
- 变量声明语句:
var、let、const、class、函数声明
给任何一个对象添加 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);// 1 2 3 ...9
表达式
表达式语句实际上就是一个表达式,它是由运算符连接变量或者直接量构成的。
Primary Expression 主要表达式
-
Primary Expression 包含了各种“直接量”。
-
在语法层面,function、{ 和 class 开头的表达式语句与声明语句有语法冲突,所以,我们要想使用这样的表达式,必须加上括号来回避语法冲突。
-
Primary Expression 还可以是 this 或者变量,在语法上,把变量称作“标识符引用”。
-
任何表达式加上圆括号,都被认为是 Primary Expression,这个机制使得圆括号成为改变运算优先顺序的手段。
MemberExpression 成员表达式
Member Expression 通常是用于访问对象成员的。它有几种形式:
a.b;
a["b"];
new.target;// 判断函数是否被new调用
super.b;// super 则是构造函数中,用于访问父类属性
f`a${b}c`;
new Cls();
NewExpression NEW 表达式
Member Expression 加上 new 就是 New Expression(当然,不加 new 也可以构成 New Expression,JavaScript 中默认独立的高优先级表达式都可以构成低优先级表达式)。
new new Cls(1) ---> new (new Cls(1))
CallExpression 函数调用表达式
基本形式是 Member Expression 后加一个括号里的参数列表,或者我们可以用上 super 关键字代替 Member Expression。
a.b()
super()
LeftHandSideExpression 左值表达式
New Expression 和 Call Expression 统称 LeftHandSideExpression,左值表达式。左值表达式就是可以放到等号左边的表达式。
AssignmentExpression 赋值表达式
AssignmentExpression赋值表达式也有多种形态,最基本的当然是使用等号赋值。还有一些变体:
*=、/=、%=、+=、-=、<<=、>>=、>>>=、&=、^=、|=、**=
xpression 表达式
在 JavaScript 中,表达式就是用逗号运算符连接的赋值表达式。逗号分隔的表达式会顺次执行,就像不同的表达式语句一样。在很多场合,都不允许使用带逗号的表达式,比如我们在前面课程中提到,export 后只能跟赋值表达式,意思就是表达式中不能含有逗号。
右值表达式(RightHandSideExpression)
对于右值表达式来说,我们可以理解为以左值表达式为最小单位开始构成的。
更新表达式 UpdateExpression
左值表达式搭配 ++ – 运算符,可以形成更新表达式。
一元运算表达式 UnaryExpression
更新表达式搭配一元运算符,可以形成一元运算表达式。
乘方表达式 ExponentiationExpression
乘方表达式也是由更新表达式构成的。它使用**号。
乘法表达式 MultiplicativeExpression
乘方表达式可以构成乘法表达式,用乘号或者除号、取余符号连接。
加法表达式 AdditiveExpression
加法表达式是由乘法表达式用加号或者减号连接构成的。
移位表达式 ShiftExpression
移位表达式由加法表达式构成,移位是一种位运算,分成三种:
- << 向左移位
- >>向右移位
- >>>无符号向右移位
移位运算把操作数看做二进制表示的整数,然后移动特定位数。所以左移 n 位相当于乘以 2 的 n 次方,右移 n 位相当于除以 2 取整 n 次。
关系表达式 RelationalExpression
移位表达式可以构成关系表达式,这里的关系表达式就是大于、小于、大于等于、小于等于等运算符号连接,统称为关系运算。
相等表达式 EqualityExpression
相等表达式是由关系表达式用相等比较运算符(如 ==)连接构成的。
==运算符三条规则:
undefined与null相等- 字符串和
bool都转为数字再比较 - 对象转换成
primitive类型再比较
位运算表达式
位运算表达式共有三种:
-
按位与表达式
BitwiseANDExpression -
按位异或表达式
BitwiseANDExpression -
按位或表达式
BitwiseORExpression
逻辑与表达式和逻辑或表达式
逻辑与表达式由按位或表达式经过逻辑与运算符连接构成,逻辑或表达式则由逻辑与表达式经逻辑或运算符连接构成。逻辑表达式具有短路的特性。
条件表达式 ConditionalExpression
条件表达式由逻辑或表达式和条件运算符构成,条件运算符又称三目运算符,它有三个部分,由两个运算符?和:配合使用。

被折叠的 条评论
为什么被折叠?



