let & const

varletconst 区别:

  1. let 声明的变量不被挂载到 globalThis
var a = 'a'
let b = 'b'
 
window.a // 'a'
window.b // undefined
  1. let 变量不可重复声明
var a = 'a'
var a = 'aa'
 
let b = 'b'
let b = 'bb' // SyntaxError: Identifier 'b' has already been declared
  1. let 变量不会变量提升,而是暂时性死区
console.log(a) // undefined
var a = 'a'
console.log(a) // 'a'
 
// ------------------------------
 
console.log(b) // ReferenceError: b is not defined
let b = 'b'
  1. let 可以定义块级作用域(详见:作用域与 this
{
    let a = 'a'
    console.log(a) // 'a'
}
console.log(a) // ReferenceError: a is not defined
  1. constlet 相同,只是 let 定义变量,const 定义常量
// 常量不可更改
const a = 'a'
a = 'aa' // TypeError: Assignment to constant variable.
 
// 声明了就要赋值
const b = 'b'
 
const c // SyntaxError: Missing initializer in const declaration
c = 'c'

ES6 之后请使用 let & constvar 是历史包袱

更多内容详见:变量与函数声明

Array

数组遍历

ES5 中数组遍历有多少种方法?他们有什么优势和缺点?

  1. for 循环
const arr = [1, 2, 3, 4, 5]
 
for (let i = 0; i < arr.length; i++) {
    console.log(i, arr[i])
}
  1. Array#forEach()
arr.forEach(function(item, index) {
    console.log(index, item)
})
// 类似方法:every()、some()、map() 等
 
// 不支持 break 和 continue
// 但可以通过 return 配合 every()、some() 等实现相同效果
  1. for-in

for-in 是为对象而设计的遍历,虽然能遍历数组(数组也是对象),但是有瑕疵

for (let index in arr) {
    console.log(index, arr[index])
}
 
// 瑕疵 1
// 这里的 index 是字符串,不是数字
for (let index in arr) {
  // if (index === 2) break // 没效果,因为不全等
  if (index === '2') break // 而是这样
 
  console.log(index, arr[index])
}
 
// 瑕疵 2
arr.a = 'a' // 数组是对象,所以可以这么写
// for-in 遍历会把 a 也遍历出来

结论:for-in 是遍历对象的,不要用来遍历数组

ES6 的 for-of 用来遍历可迭代对象,数组实现了迭代协议

for (let item of arr) {
    console.log(item)
}

伪数组 → 数组

伪数组:集合(如:setgetElementByTagName())、arguments

  • 该对象按照索引存储数据
  • length 属性

ES5:

var args = [].slice.call(arguments) // collection
var imgs = [].slice.call(document.querySelectorAll('img')) // NodeList

ES6:

  1. Array.prototype.from(arrayLike, mapFn, thisArg)

伪数组 → 数组:

const args = Array.from(arguments)
const imgs = Array.from(document.querySelectorAll('img'))

初始化数组:

// 1. 初始化长度为 5 的数组
let arr1 = Array(5)
 
// 2. 初始化长度为 5 的数组,并填充 1
let arr2 = Array.from({ length: 5 }, () => 1)
  1. 扩展运算符
const args = [...arguments]
const imgs = [...document.querySelectorAll('img')]

创建新数组

ES5:

// 1. 字面量
let arr1 = [1, 2, 3]
 
// 2. 构造函数
let arr2 = Array(5)

ES6:

// 1. Array.prototype.of(...args)
let arr1 = Array.of(1, 2, 3, 4, 5)
 
// 2. Array.prototype.fill(value, start, end) // 包头不包尾
let arr2 = Array(5).fill(1)
 
// 3. Array.prototype.from(arrayLike, mapFn, thisArg)
// 见上文

查找数组中的元素

ES5:

  1. indexOf
const arr = [1, 2, 3]
 
// 如果找到,返回索引(第一个),找到第一个就不会往下找
// 如果没找到,返回 -1
if (arr.indexOf(4) === -1) {
    console.log('没找到')
}
  1. filter
// 本质是过滤,但也可以做查找 -> 判断 find.lenght 是否等于 0
// 但是效率不高 -> 即使查到了也会继续遍历
// 所以:filter 是过滤,不要用 filter 做查找
let find = arr.filter(item => item === 4)
 
console.log(find) // []

ES6:

// Array.prototype.find()
let find = arr.find(item => item === 2)
console.log(find) // 2
 
let find = arr.find(item => item === 4)
console.log(find) // undefined
 
// 返回的是找到的值(第一个),而不是索引
// 找到就返回了,不会继续遍历数组
 
// Array.prototype.findIndex(item):返回的是索引

Class

封装

ES5:

function Animal(type) {
    // 属性
    this.type = type
    // 挂载在实例上的方法,不要这样写
    // 应该写成实例方法(挂到原型上)
    // 不然每个实例都有一个方法,无法复用,占用内存大
    this.walk = function() {}
}
 
// 实例对象的方法
Animal.prototype.eat = function() {
    console.log('i am eat food')
}
 
// 静态方法,不能访问 this
Animal.getClassName = () => 'Animal'
 
let dog = new Animal('dog')
 
// dog.constructor === Animal
// dog.__proto__ === Animal.prototype
 
/*
Animal {type: "dog"}
    type: "dog"
    walk: ƒ ()
    __proto__:
        eat: ƒ ()
        constructor: ƒ Animal(type)
        __proto__: Object
*/

ES6:

// 和 ES5 那样写一模一样,只是语法糖
class Animal {
    constructor(type) {
        this.type = type
        // 挂载在实例上的方法,不要这样写
        // this.walk = function() {}
    }
 
    // 挂载在实例上的方法,不要这样写
    walk = () => {}
 
    // 实例对象的方法
    eat() {
        console.log('i am eat food')
    }
 
    // 静态方法,不能访问 this
    static getClassName() {
        return 'Animal'
    }
}
 
console.log(typeof Animal) // function
 
// ------------------------------------------------
 
// 属性保护 -> setter & getter
// ES5 很难做
class Animal {
    _age = 0
    // 相当于
    // constructor() {
    //      this._age = 0
    // }
 
    get age() {
        return this._age
    }
    set age(val) {
        if (val > this._age) this._age = val
    }
}
 
// 遗憾:没有权限修饰符————public、private

继承

ES5:

function Animal(type) {
    this.type = type
}
 
function Dog() {
    // 初始化父类的构造函数
    Animal.call(this, 'dog')
}
 
Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog

ES6:

class Animal {
    constructor(type) {
        this.type = type
    }
    eat() {
        console.log('i am eat food')
    }
}
 
class Dog extends Animal {
    constructor() {
        // 初始化父类的构造函数
        super('dog')
    }
 
    // override
    eat() {
        console.log('dog eating')
    }
}

多态

JS 是弱类型语言,多态体现没有强类型语言那么明显

详见:Java 中的多态

function

函数参数默认值

ES5:

function bar(a, b) {
    if (b === undefined) b = 'b'
    // 或
    b = b || 'b'
}

ES6:

// 将有默认值的参数放到最后
// 参数默认值可以是表达式
function bar(a, b = 'b', c = a + b) {}
 
// 如果想使 b 保持默认值,改变 c 呢?
bar('a', undefined, 'd')
// 也就是说:这也是语法糖 -> if (b === undefined) b = 'b'
 
// 获取必传形参的个数(不包括有默认值的形参)
bar.length // 1

不确定参数

ES5:

function bar() {
    // 函数内使用 -> 获取实参列表
    arguments // 伪数组: [1, 2, 3]
}
 
bar(1, 2, 3)
 
// ----------------------------------------
const arr = [4, 5, 6]
 
bar(arr[0], arr[1], arr[2])
// 或
bar.apply(this, arr) // 这里的 this 是 window

ES6:

// ES6 已经弃用 arguments
function bar(a, ...args) {
    console.log(a) // 1
    console.log(args) // 数组: [2, 3]
}
 
bar(1, 2, 3)
 
// ----------------------------------------
const arr = [4, 5, 6]
 
bar(...arr) // 扩展运算符

箭头函数

  • 不能作为构造函数,无原型对象和原型链
  • 不能作为 Generator 函数
  • 没有 arguments
  • 没有自己的执行上下文 → this 是外界的 this(所在词法作用域) → this 是写的时候决定的
// 语法
(参数列表) => {函数体}
 
// 有且仅有一个参数,() 可以省略
a => {}
 
// 函数体只有一条语句,{} 和 return 可以同时省略
a => a + 1
 
// return 的是对象,还要省略 -> 用 () 包裹
a => ({ a2: a*2 })
let test = {
    name: 'test',
    say: function() {
        console.log(this.name)
    }
}
test.say() // test
 
// -----------------------------
let test = {
    name: 'test',
    say: () => {
        console.log(this.name, this) // this 指向 window
        // 如果使用 webpack 构建,就会放入 eval 环境,那么 this 就是空对象
    }
}
test.say() // undefined

Object

简写

const a = 'a'
const c = 'cc'
 
const obj = {
    a: a,
    b: function() {}
    bar: function*() {} // generator 函数
}
obj[c] = 'c' // obj { cc: 'c' }
 
// 简写
const obj = {
    a,         // 属性简写
    b() {},    // 方法简写
    *bar() {} // generator 函数
    [c]: 'c',  // 动态 key 简写
    // 甚至可以这样写
    [a + c]: 'z', // obj { acc: 'z' }
}

对象拷贝

const target = {}
const source = { a: 1, b: 2 }
// 浅拷贝
Object.assign(target, source)

Set 数据结构

  • 类比:Array,但性能更高
  • 特点:
    • 存储的值唯一(不重复)
    • 有序:按照添加的顺序
  • WeakSet:和 Set 一样,但是存储的值是对象。弱引用,不影响 GC
const set = new Set()
const set2 = new Set([1, 2, 3]) // 不仅仅是数组,接收参数是可迭代对象
 
// 添加
set.add(1)
set.add(2).add(3) // 级联操作
 
// 删除
set.delete(1)
// 清空
set.clear()
 
// 查
set.has(1) // false
 
// 存了多少值
set2.size // 3
 
// 遍历
set2.keys()     // SetIterator {1, 2, 3}
set2.values()   // SetIterator {1, 2, 3}
set2.entries()  // SetIterator {1 => 1, 2 => 2, 3 => 3}
set2.forEach(item => {})
 
// Set 是可迭代对象,可以用 for...of
for (let item of set2) {
    console.log(item)
}

Map 数据结构

  • 类比:Object,但性能更高
  • 特点:
    • key 唯一(不重复)
    • key 可以是任意值(函数、对象等);对象的值只能是 String 和 Symbol
    • 有序:按照添加的顺序
  • WeakMap:和 Map 一样,但是 key 必须是对象。弱引用,不影响 GC
const map = new Map()
const map2 = new Map([[1, 2], [3, 4]]) // 接收可迭代对象,但迭代项要是 entry
 
console.log(map2) // Map(2) {1 => 2, 3 => 4}
 
// 添加 & 修改
map.set(key, value)
// 没有就添加
// 有就修改
 
// 取
map.get(key)
 
// 删除
map.delete(key)
// 清空
map.clear()
 
// 查
map.has(key) // true | false
 
// 存了多少值
map.size
 
// 遍历
// 返回的都是 MapIterator(可迭代对象)
map.keys()
map.values()
map.entries()
map.forEach((value, key) => {})
 
// Map 是可迭代对象,可以用 for...of
for (let [key, value] of map) {
    console.log(key, value)
}

RegExp

y 修饰符

const str = 'aaa_aa_a'
const r1 = /a+/g
const r2 = /a+/y
 
r1.exec(str) // ["aaa", index: 0, input: "aaa_aa_a", groups: undefined]
r2.exec(str) // ["aaa", index: 0, input: "aaa_aa_a", groups: undefined]
 
r1.exec(str) // ["aa", index: 4, input: "aaa_aa_a", groups: undefined]
r2.exec(str) // null
 
// y 修饰符: 表示粘连(sticky)
// 第一次取 aaa
// 第二次从剩余部分(_aa_a)取
// _aa_a 的第一个字符不符合 /a+/,返回 null

u 修饰符

在正则中处理中文,参考:fileformat.info

// u 修饰符: unicode 编码
let s = '𠮷'
let s2 = '\uD842\uDFB7' // 即:𠮷
console.log(s === s2) // true
 
/^\uD842/.test(s2) // true: 不符合预期,\uD842\uDFB7 表示一个字不能拆开
/^\uD842/u.test(s2)) // false: 正确
 
/^.$/.test(s) // false: 错误:𠮷怎么不是任意一个字符?
/^.$/u.test(s) // true
 
/𠮷{2}/.test('𠮷𠮷') // false
/𠮷{2}/u.test('𠮷𠮷') // true
 
// 总结:要匹配中文字符就使用 u 修饰符
 
// --------------------------------------------------
// \u20BB7 是𠮷的码点
/\u{20BB7}/u.test(s) // true
 
/\u{61}/u.test('a') // true
/\u{61}/.test('a') // false
 
// 总结:使用 unicod 码点要用 u 修饰符

模板字符串

字符串拼接

const a = 20
const b = 10
const c = 'javascript'
 
// ES5
const str = 'my age is ' + (a + b) + ', i love' + c
// ES6
const str2 = `my age is ${a + b}, i love ${c}`

字符串换行

// ES5
const htmlStr = '\n\
    <div>\n\
        content\n\
    </div>\n\
'
 
// ES6
const htmlStr = `
    <div>
        content
    </div>
`

标签函数

// ES5
const retailPrice = 20
const wholeSalePrice = 16
const type = 'retail'
 
let showTxt
if (type === 'retail') {
    showTxt = '您此次购买的单价是: ' + retailPrice
} else {
    showTxt = '您此次购买的批发价是: ' + wholeSalePrice
}
console.log(showTxt) // 您此次购买的单价是: 20
 
// ---------------------------------------------
// ES6
function Price(strings, type) {
    let s1 = strings[0]
    const retailPrice = 20
    const wholeSalePrice = 16
 
    let txt
    if (type === 'retait') {
        txt = '单价是: ' + retailPrice
    } else {
        txt = '批发价是: ' + wholeSalePrice
    }
    return `${s1}${txt}`
}
 
let showTxt = Price`您此次购买的${'retail'}`
console.log(showTxt) // 您此次购买的单价是: 20

解构赋值

数组解构赋值

// ES5
const arr = ['hello', 'world']
const firstName = arr[0]
const surName = arr[1]
 
// ES6
const arr = ['hello', 'world']
const [firstName, surName] = arr
// 跳过某项
const [, surName] = arr
 
// ----------------
// 可迭代对象都可以这么解构
// 字符串
const str = 'abcd'
const [first, , third] = str
first // a
third // c
 
// -----------------
// 变量互换
let a = 1
let b = 2
[a, b] = [b, a]
 
// --------------
// 对对象属性赋值
let user = { name: 's', surname: 't' };
[user.name, user.surname] = [1, 2]
 
// ----------------
let user = { name: 's', surname: 't' }
for (let [k, v] of Object.entries(user)) {
    // 隐式赋值
    console.log(k, v)
}
 
// ---------------------------
let arr = []
let [a, b = 2, ...last] = arr
a // undefined
b // 2
last // []

对象解构赋值

let options = {
    title: 'menu',
    // h: 100,
    width: 100,
    height: 200
}
 
let { width: myWidth, height, h = 130 } = options
 
// -----------------------
let { title, ...last } = options
 
// -------------------------
// 非扁平数据
let options = {
    size: {
        width: 100,
        height: 200
    },
    items: ['Cake', 'Donut']
}
let { size: { width }, items: [, item2] } = options
console.log(width) // 100
console.log(item2) // 'Donut'
console.log(size) // ReferenceError: size is not defined

Promise

详见:异步处理与 Promise手写 Promise

Proxy

new Proxy()

const target = {
    name: 'xiaoming',
    price: 190
}
 
const handler = {
    get(target, key) {
        if (key === 'price') {
            return Reflect.get(target, key) + 20
        } else {
            return Reflect.get(target, key)
        }
    },
    set(target, key, value) {
        return false // 只读
    }
}
 
const p = new Proxy(target, handler)
 
console.log(p.price) // 210
p.name = 'tom' // 不成功
 
// --------------------------------------
// ES5 如何实现?
for (let [key] of Object.entries(target)) {
    Object.defineProperty(target, key, {
        writable: false // 不可写
    })
}
 
// Proxy 和 defineProperty 可以实现相同的效果,但原理完全不同
// Proxy:代理对象
// defineProperty:完全锁死原对象(修饰符)
 
// ES6 后:优先考虑 Proxy
// 这也是 vue2 -> vue3 的一个底层改变

Proxy.revocable()

临时代理

const o = {
    name: 'xiaoming',
    price: 190
}
 
const d = Proxy.revocable(o, {
    get (target, key) {
        if (key === 'price') {
            return target [key] + 20
        } else {
            return target[key]
        }
    }
})
 
console.log(d.proxy.price) // 210
 
setTimeout (() => d.revoke()}, 1000) // 取消代理
 
// d.proxy    // 代理对象
// d.revoke() // 取消代理方法,调用即可取消代理

实际应用

数据校验:

  1. 保证对象结构
  2. 拦截非法输入
let o = {
    name: 'xiaoming',
    price: 190
}
 
let validator = new Proxy(o, {
    get(target, key) {
        return target[key] || '' // undefined -> ''
    },
    set(target, key, value) {
        if (Reflect.has(target, key)) {
            if (key === 'price' && value < 0) {
                return false // 拦截非法输入
                // throw new Error('price must be positive') // 或者抛出错误
            }
            Reflect.set(target, key, value)
        } else {
            return false // 保证对象结构
        }
    }
})

Reflect

详见:元编程

Generator 函数

更多内容详见:Generator 函数

Iterator

  • Iterable:可迭代,是否有 Symbol.iterator 属性
  • iterator:迭代器,返回值必须是这样的对象:
{
    next() {
        return {
            value: xxx,
            done: true | false
        }
    }
}

示例:

const authors = {
  allAuthor: {
    fiction: ['Agla', 'Skks', 'LP'],
    scienceFiction: ['Neal', 'Arthru', 'Ribert'],
    fantasy: ['V.R.Tole', 'J.M.R', 'Terry P.K']
  },
  address: ['shanghai']
}
 
// 手动编写 Iterator 返回的对象
/*
authors[Symbol.iterator] = function () {
  // input: this, 即:authors
  const allAuthor = this.allAuthor
  let keys = Reflect.ownKeys(allAuthor)
  let values = []
 
  // output: return 的对象
  return {
    next() {
      if (!values.length && keys.length) {
        values = allAuthor[keys[0]]
        keys.shift()
      }
      return {
        done: !values.length,
        value: values.shift()
      }
    }
  }
}
*/
 
// 使用 Generator 函数
authors[Symbol.iterator] = function * () {
    const allAuthor = this.allAuthor
    let keys = Reflect.ownKeys(allAuthor)
    let values = []
 
    while (true) {
        if (!values.length) {
            if (keys.length) {
                values = allAuthor[keys[0]]
          keys.shift()
                yield values.shift()
            } else {
                return false
            }
        } else {
            yield values.shift()
        }
    }
}
 
for (let author of authors) {
  console.log(author)
}

更多内容详见:迭代协议

ESModule

导出:

/* module1.js */
export default function () {}
 
// export const a = 'a'
// export function bar() {}
 
// 或
const a = 'a'
function bar() {}
 
export {
    a,
    bar
}

导入:

/* index.js */
// import module1, { a, bar as ber } from './module1'
 
// 或
import * as module1 from './module1'
// module1.default
// module1.a
// module1.bar()
 
// -----------------------------
// 导入同时就导出
// 用于聚集模块
export * from 'foo'
export { foo } from 'foo'
export { foo as bar } from 'foo'
export { foo as default } from 'foo'