坚持周总结系列第二周(JavaScript重学)

坚持周总结系列第二周(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 不同,nullJavaScript 关键字,所以在任何代码中,你都可以放心用 null 关键字来获取null值。

String

  • String用于表示文本数据。String有最大长度是 2^53 - 1,但是所谓最大长度,并不完全是你理解中的字符数。
  • 因为 String的意义并非“字符串”,而是字符串的UTF16编码,我们字符串的操作 charAtcharCodeAtlength 等方法针对的都是 UTF16 编码。所以,字符串的最大长度,实际上是受字符串的编码长度影响的。

Number

  • JavaScript 中有 +0 和 -0,“忘记检测除以 -0,而得到负无穷大”的情况经常会导致错误,而区分 +0 和 -0 的方式,正是检测 1/xInfinity还是-Infinity
  • 非整数的 Number 类型无法用 ==(=== 也不行) 来比较,正确的比较方法是使用 JavaScript 提供的最小精度值:
console.log( Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON);

Symbol

  • SymbolES6中引入的新类型,它是一切非字符串的对象 key 的集合,在 ES6规范中,整个对象系统被用 Symbol重塑。
  • Symbol可以具有字符串类型的描述,但是即使描述相同,Symbol 也不相等。
  • 创建Symbolvar 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 函数,它是对象类型到基本类型的转换(即拆箱转换)。
    • 拆箱转换会尝试调用 valueOftoString 来获得拆箱后的基本类型。如果 valueOftoString 都不存在,或者没有返回基本类型,则会产生类型错误 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+ 个固有对象,链接地址

内置对象.原生对象

img

几乎所有这些构造器的能力都是无法用纯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在外面永远无法访问到

特殊行为的对象

  • ArrayArraylength属性根据最大的下标自动发生变化
  • Object.prototype:作为所有正常对象的默认原型,不能再给它设置原型
  • String:为了支持下标运算,String的正整数属性访问会去字符串里查找
  • Argumentsarguments的非负数整型下标属性跟对应的变量联动
  • 模块的nameSpace对象:尽量只用于import
  • 类数组和数组缓冲区:根内存块关联,下标运算比较快
  • bind后的function:跟原来的函数相关联

JavaScript固有对象

三个值:InfinityNaNundefined

九个函数:evalisFiniteisNaNparseIntparseFloatdecodeURIdecodeURIComponentencodeURIencodeURIComponent

一些构造器:ArrayDateRegExpPromiseProxyMapWeakMapSetWeakSetFunctionBooleanStringNumberSymbolObjectErrorEvalErrorRangeErrorReferenceErrorSyntaxErrorTypeErrorURIErrorArrayBufferSharedArrayBufferDataViewTyped ArrayFloat32ArrayFloat64ArrayInt8ArrayInt16ArrayInt32ArrayUInt8ArrayUInt16ArrayUInt32ArrayUInt8ClampedArray

四个用于当作命名空间的对象:AtomicsJSONMathReflect

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')
}

闭包和执行上下文

img

闭包

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.callFunction.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可以是IdentifierNullLiteralBooleanLiteral或者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(){
    ...
}

img

预处理

JavaScript 执行前,会对脚本、模块和函数体中的语句进行预处理。预处理过程将会提前处理 var、函数声明、classconstlet这些语句,以确定其中变量的意义。

  • var声明:
    var 声明永远作用于脚本、模块和函数体这个级别,在预处理阶段,不关心赋值的部分,只管在当前作用域声明这个变量。

  • function声明:

    在全局(脚本、模块和函数体),function 声明表现跟 var 相似,不同之处在于,function 声明不但在作用域中加入变量,还会给它赋值。

  • class声明:

    class 声明在全局的行为跟 function 和 var 都不一样,在 class 声明之前使用 class 名,会抛错。

指令序言

这里的指令序言最早是为了 use strict 设计的,它规定了一种给 JavaScript 代码添加元信息的方式。

语句

JavaScript中,语句分成两种:声明和语句。

  • 普通语句

img

  • 声明型语句

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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 ExpressionCall Expression 统称 LeftHandSideExpression,左值表达式。左值表达式就是可以放到等号左边的表达式。

AssignmentExpression 赋值表达式

AssignmentExpression赋值表达式也有多种形态,最基本的当然是使用等号赋值。还有一些变体:

*=、/=、%=、+=、-=、<<=、>>=、>>>=、&=、^=、|=、**=
xpression 表达式

在 JavaScript 中,表达式就是用逗号运算符连接的赋值表达式。逗号分隔的表达式会顺次执行,就像不同的表达式语句一样。在很多场合,都不允许使用带逗号的表达式,比如我们在前面课程中提到,export 后只能跟赋值表达式,意思就是表达式中不能含有逗号。

右值表达式(RightHandSideExpression

对于右值表达式来说,我们可以理解为以左值表达式为最小单位开始构成的。

更新表达式 UpdateExpression

左值表达式搭配 ++ – 运算符,可以形成更新表达式。

一元运算表达式 UnaryExpression

更新表达式搭配一元运算符,可以形成一元运算表达式。

乘方表达式 ExponentiationExpression

乘方表达式也是由更新表达式构成的。它使用**号。

乘法表达式 MultiplicativeExpression

乘方表达式可以构成乘法表达式,用乘号或者除号、取余符号连接。

加法表达式 AdditiveExpression

加法表达式是由乘法表达式用加号或者减号连接构成的。

移位表达式 ShiftExpression

移位表达式由加法表达式构成,移位是一种位运算,分成三种:

  • << 向左移位
  • >>向右移位
  • >>>无符号向右移位

移位运算把操作数看做二进制表示的整数,然后移动特定位数。所以左移 n 位相当于乘以 2 的 n 次方,右移 n 位相当于除以 2 取整 n 次。

关系表达式 RelationalExpression

移位表达式可以构成关系表达式,这里的关系表达式就是大于、小于、大于等于、小于等于等运算符号连接,统称为关系运算。

相等表达式 EqualityExpression

相等表达式是由关系表达式用相等比较运算符(如 ==)连接构成的。

==运算符三条规则:

  • undefinednull 相等
  • 字符串和 bool 都转为数字再比较
  • 对象转换成 primitive 类型再比较
位运算表达式

位运算表达式共有三种:

  • 按位与表达式 BitwiseANDExpression

  • 按位异或表达式 BitwiseANDExpression

  • 按位或表达式 BitwiseORExpression

逻辑与表达式和逻辑或表达式

逻辑与表达式由按位或表达式经过逻辑与运算符连接构成,逻辑或表达式则由逻辑与表达式经逻辑或运算符连接构成。逻辑表达式具有短路的特性。

条件表达式 ConditionalExpression

条件表达式由逻辑或表达式和条件运算符构成,条件运算符又称三目运算符,它有三个部分,由两个运算符?和:配合使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值