坚持周总结系列第二周(2020.4.25)
JavaScript
重学
JavaScript
类型
JavaScript
语言规定了7中语言类型:
Undefined
Null
Boolean
String
Number
Symbol
Object
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
根据指定的原型创建对象,原型可以是null
Object.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
条件表达式由逻辑或表达式和条件运算符构成,条件运算符又称三目运算符,它有三个部分,由两个运算符?和:配合使用。