【愣錘筆記】溫故而知新--ES6拾遺

ES是一把利器,也是一匹野馬。
紮實好ES基礎,會讓人如虎添翼,你還在猶豫什麼。前端

const和let

  • 暫時性死區:塊級做用域內,在const和let聲明變量以前該變量都是不可以使用的。無論外部是否用同名變量已經聲明。
  • 不存在變量提高
  • 不容許重複聲明
  • es5沒有塊級做用域,es6中使用const或let則自動會造成塊級做用域
  • 塊級做用域能夠達到和IIFE相同的效果
// IIFE 寫法
(function () {
  var num = ...;
  ...
}());

// 塊級做用域寫法
{
  let num = 123;
}
複製代碼
  • es6規範容許在塊級做用域內聲明函數,可是客戶端(其餘環境除外)的實現依舊是相似var的形式。全部考慮到兼容,不要在塊級做用域內聲明函數,即便須要函數也要寫成表達式的形式(let a = function(){}
  • es6的塊級做用域必需要有大括號,不然引擎不會把它做爲塊級做用域:
if (true) let a = 1; // 不存在塊級做用域
複製代碼
  • const常量,指的是常量對應的內存地址不得改變,而不是對應的值不得改變。全部把引用類型的數據設置爲常量,其內部的值是能夠改變的,所以須要當心:
const a = {};
    a.b = 13; // 沒毛病,不會報錯
    const arr = [];
    arr.push(123); // 也沒毛病
    
    // 若是須要,能夠將對象凍結
    const a = Object.freeze({})
    // 除了對象自己,其屬性也應該凍結
    var constantize = (obj) => {
        Object.freeze(obj);
        Object.keys(obj).forEach( (key, i) => {
            if ( typeof obj[key] === 'object' ) {
                constantize( obj[key] );
            }
        });
    };
複製代碼
  • es5的var/function聲明的全局變量會自動掛載到全局對象中,可是es6的const/let/class聲明的全局則不會。

解構

數組解構es6

  • 解構不到時變量的值爲undefined
var [a, c] = [2];
a // 2
c // undefined
複製代碼
  • 等號右側只要具備iterator解構的數據均可以進行數組解構
  • 解構能夠設置默認值,只有嚴格等於undefined時纔會使用默認值:
let [a, b = 1] = [2, null] // 1, null
let [a, b = 1] = [2] // 2 1
let [a, b = 1] = [2, undefined] // 2 1
複製代碼
  • 解構可使用其餘變量,可是須要先聲明
let [x = y, y = 1] = []; // 報錯,使用y時,y還未聲明
let [x = 1, y = x] = []; // x=1; y=1
複製代碼

對象解構web

  • 數組按照位置解構,對象按照同名屬性解構
  • 若是等號右側不是對象或數組,解構時會先將其轉換成對象。因爲undefined/null沒法轉換成對象,對其解構時會報錯:
const {a, b} = undefined || []; // 我的推薦這種這種寫法避免報錯,a undefined, b undefined
複製代碼

解構其餘用法數組

  • 解構函數返回的多個值
// 函數返回多個值只能經過數組或者對象的形式返回
const func = () => [1, {a: 1}, [1,2,3,4]];
const [first, second, third] = func(); // first 1,second {a: 1}, third [1,2,3,4]

const func = () => {
     return {
        first: 1,
        second: { a: 1},
        third: [1, 2, 3, 4, 5]
     }
};
const {first, second, third} = func(); // first 1, second {a: 1}, third [1, 2, 3, 4, 5]
複製代碼
  • 從導入的模塊進行解構
import { moduleA, moduleB } from 'someModule';
複製代碼

字符串

  • 字符串模板中,可使用變量/函數等
var func = function () {return '123'}
var val = '456'
console.log(`this is string ${func()} + ${val}`) // this is string 123 + 456
複製代碼
  • 若是變量的不是字符串,會按照通常規則轉換成字符串,例如對象會調用其toString方法
  • 字符串方法
// 字符串是否包含某個字符串
// 可接受第二個參數表示從下標n開始日後查找
var str = 'abcdefg';
str.includes('c', 1); // true

// 字符開始是否包含某個字符串
// 第二個參數表示從下標n開始是否包含某個字符串
str.startsWith('b') // false
str.startsWith('b', 1) // true

// 結尾是否包含
str.endsWith('b') // false
str.endsWith('b', 2) // true,表示前n個字符的結尾是否包含查詢的字符串
str.endsWith('b', 1) // false

// 返回一個重複n次的新字符串
str.repeat(2) // 'abcdefgabcdefg'

// 補全字符串(返回補全後的新字符串,不改變原字符串)
var str = 'a';
str.padStart(4, 'b') // bbba,在前面補
str.padEnd(4, 'b') // abbb。 後補
str.padStart(4) // ' a', 默認補空格

// 消除前/尾空格 (返回新字符,不改變原字符串)
var str = ' a ';
str.trimStart() // "a "
str.trimEnd() // " a"
複製代碼

數值

  • 新增方法,建議放在Number對象上使用(標準也在逐步減小全局方法,使語言逐漸模塊化)
// 判斷有限數
Number.isFinite(123) // true
Number.isFinite(Infinity) // false
Number.isFinite('123') // false,非數字類型一概返回false

// parseInt和parseFloat移植到了Number對象上
Number.parseInt()
Number.parseFloat()

// 判斷整數
Number.isInteger(2) // true
Number.isInteger(2.0) // true js中整數和浮點數採用的是一樣的儲存方法,因此 2 和 2.0 被視爲同一個值
複製代碼

JavaScript 採用 IEEE 754 標準, 數值存儲爲64位雙精度格式,
數值精度最多能夠達到 53 個二進制位(1 個隱藏位與 52 個有效位)。
若是數值的精度超過這個限度,第54位及後面的位就會被丟棄,這種狀況下,Number.isInteger可能會誤判。bash

  • js中的最小常量, 表示1與大於1的最小浮點數的差值
    Number.EPSILON // 2.220446049250313e-16
    引入該常量是爲浮點數計算,設置一個偏差範圍:
0.1 + 0.2 === 0.3 // false

// 能夠設計以下函數,偏差小於某個差值時自動忽略
const isEqualInAcceptErrorRange = (a, b) => Math.abs(a - b) < Number.EPSILON * Math.pow(2, 2);
isEqualInAcceptErrorRange(0.1 + 0.2, 0.3) // true
複製代碼
  • Math擴展
Math.trunc(1.111) //1, 返回去除小數後的值,等於1.111 | 0
Matn.sign(1.111) // +1,判斷一個數是正數仍是負數,-1負數,0,-0,其餘值反水NaN
複製代碼

函數

  • 函數能夠寫默認參數
// 不能有同名函數參數
// 已聲明的參數變量在函數體內不能夠用let/const重複聲明
// 參數表達式是惰性求值的,每次調用函數時都會從新計算參數的值(也只會在每次調用時才計算參數的值)
function func(a = 1, b = 2, c) {
}
複製代碼
  • 與對象解構一塊兒使用
// 與對象解構一塊兒使用,可是若是函數調用時沒傳參數,
// 那麼從undefined進行解構則會報錯
const func = ({ x, y = 1 }) => {};
func({}) // 正常
func() // 報錯

// 與對象解構+函數參數默認值一塊兒使用
const func2 = ({ x, y = 1 } = {}) => {}; 
func2() // 正常
複製代碼
  • 一旦使用了函數默認參數,函數的length屬性將失真,length本質是函數預期傳入的參數
  • rest參數
// 只能使用在最後,後面不能再有參數
// rest參數是一個真正的數組,arguments是類數組
const func = function (...args) {
    return args
    // 等同於
    // return Array.prototype.slice.call(arguments)
}
複製代碼
  • 箭頭函數返回對象時必須加小括號
const func5 = () => ({a: 1, b: 2})
複製代碼
  • 箭頭函數的this指定義時的上下文中的this(由於箭頭函數沒有this,全部this天然是其外部的this),而普通函數中this則指向運行時的上下文
  • 箭頭函數不能夠做爲構造函數使用
  • 箭頭函數無arguments對象,能夠喲過rest參數替代
  • 不該該使用箭頭函數的場景:
var cat = {
    count: 0,
    add: () => {
        // 因爲對象構不成單獨的做用域,因此若是寫成箭頭函數,
        // 則this在此時指向全局了,而不是該對象
        this.count ++
    }
}
cat.add() // cat.count並無變化

// 事件中須要this指向當前元素時,也不可使用箭頭
button.addEventListener('click', () => {
    // 得不到指望的結果 
    this.classList.toggle('on');
});
複製代碼
  • 箭頭函數能夠嵌套
// 例如定義一個管道函數,即前一個函數返回的值做爲下一個函數的參數
const pipeline = (...funcs) => v => funcs.reduce((a, b) => b(a), v);
複製代碼

數組

  • 數組擴展運算符只能用在函數中,用於將數組轉換成逗號分割的參數序列
  • 擴展運算符能夠替代apply來進行傳遞參數
var arg = [1, 2, 3, 4]
func.apply(null, arg)
func(...arg)

Max.max.apply(null, arg)
Math.max(...arg)
複製代碼
  • 合併數組
// 注意是淺拷貝
var arr = [...arr1, ..arr2, ..arr3]
複製代碼
  • 全部定義了Iterator接口的數據均可以使用擴展運算符轉換成真正的數組
[...Iterator]
複製代碼
  • Array.from()將累數組對象和Iterator接口的數據解構轉化成真正的數組
var divs = document.getElementsByTagName('div')
Array.from(divs)

// 接受第二個參數,用於處理每一項數據,相似於map
Array.from(arrayLike, x => x * x);
複製代碼
  • Array.of()返回參數組成的新數組
  • 擴展方法
// 拷貝數組部份內容覆蓋到數組其餘位置
// 參數,覆蓋開始的位置(含)/拷貝開始的位置(含)/拷貝結束的位置(不含)
// 負數表示倒數
[1,2,3,4,5].copyWithin(1, 2, 3) // [1, 3, 3, 4, 5]

// 找到某個值,不然返回undefined
// 接受第二個參數綁定回調函數的上下文
[1,2,3,4].find((e) => e === 3) // 3

// 找到符合條件的第一個值的下標,不然返回-1
var arr = [1,2,3,4,5]
arr.findIndex(e => e > 4) // 4

// 填充數組
// 參數:填充值,填充開始位置,填充結束位置(不含)
// 只會填充已有的值
[1,3,4,5,6,7].fill('a', 3, 11111) // [1, 3, 4, "a", "a", "a"]

// 遍歷鍵名,arr.keys()
var arr = ['a', 'b' ,'c' ,'d']
for(let index of arr.keys()) {
	console.log(index)
}

// 遍歷值,arr.values()
var arr = ['a', 'b' ,'c' ,'d']
for(let index of arr.values()) {
	console.log(index)
}

// 遍歷鍵值對,arr.entries()
var arr = ['a', 'b' ,'c' ,'d']
for(let index of arr.entries()) {
	console.log(index)
}

// 是否包含某個值
[1, 2, 3].includes(2) // true

// falt數組,默認flat一層
[1, [2,3], [[4, [5, [6]]]]].flat() // 只有第一層 嵌套的被flat
// 等價於
[1, [2,3], [[4, [5, [6]]]]].flat(1)
// 接收一個參數表示要flat的層數,若是想flat所有層級
[1, [2,3], [[4, [5, [6]]]]].flat(Infinity) // [1, 2, 3, 4, 5, 6] Infinity表示flat所有層級

// flatMap先對數組每項的值進行回調函數的處理後再flat
// 只能拉伸一層
[2, 3, 4].flatMap((x) => [x, x * 2])

// 空位
// 空位的值不是undefined,[,,,,]或者new Array(5)都會產生空位
// es6明確規定空位會轉換成undefined
各類方法對空位的處理不一致,因此要絕對避免開發中出現空位
複製代碼

對象

  • 表達式做爲對象的屬性名
var name = 'xiaoming';
var o = {
	['liu'+ name]: 1
}
o.liuxiaoming // 1
複製代碼
  • 獲取屬性的描述信息
Object.getOwnPropertyDescriptor({a: 1}, 'a')
複製代碼
  • 對象屬性的遍歷
var o = {a: 1}

for/in // 自身和繼承的除symbols外的全部屬性
Object.keys(o) // 自身的除Symbols和不可枚舉外的全部屬性
Object.getOwnPropertyNames(o) // 自身的除Symbols外全部屬性
Object.getOwnPropertySymbols(o) // 全部Symbols屬性
Reflect.ownKeys(o) // 自身全部屬性
複製代碼
  • super關鍵字
// es6新增super指向對象的原型對象
// 只能用在對象的方法中,不然報錯
var o = {a: 1}
var o2 = {
	a: 2, 
	b () {
		return super.a
	}
}
// 將o2的原型設置爲o
Object.setPrototypeOf(o2, o)
o2.b() // 1
複製代碼
  • 對象的解構,用法和數組同樣,也是淺拷貝,不能解構undefined/null
  • 解構能夠解構到原型上繼承到值,可是擴展運算符到解構沒法解構到原型上繼承到值
var o = Object.create({a: 1})
var {a} = o // a 1
var {...rest} = {} // rest {}
複製代碼
  • 對象到方法
// 是否相等
Object.is(a, b) // 判斷兩個值是否徹底相等,比===強在+0-0,NaN的判斷

// 對象淺拷貝
Object.assign(target, o2, o3, o4) // 將o234合併到target對象上,同名屬性替換

// 獲取對象單個屬性的描述對象
Object.getOwnPropertyDescriptor(o2, 'a')
// 獲取對象全部屬性的描述對象,注意二者的區別,一個加s,一個沒有s
Object.getOwnPropertyDescriptors(o2)

// 設置對象的原型
var o1 = {}
var o2 = {a: 1}
Object.setPrototypeOf(o1, o2) // 把o1的原型設置爲o2

// 讀取對象的原型對象
Object.getPrototypeOf(o1)
複製代碼

Symbol

  • Symbol是新增的第七種原始數據類型,凡是屬性名爲Symbol類型的,能夠保證不與其餘屬性名衝突
var s1 = Symbol()
var s2 = Symbol('s2') // 爲s2增長一個描述
s2.description // 獲取s2的描述

// 做爲屬性名使用
var o = {
    [s2]: 456
}
o[s1] = 123 // 不能使用點運算符,不然會認爲是字符串
複製代碼
  • Symbol不會被for...infor...of循環,也不會被Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回app

  • Symbol.for()/Symbol.keyfor()dom

// Symbol.for接收一個參數,每次先搜索有沒有存在這個Symbol,有則返回,無則建立再返回
var s1 = Symbol.for('s1')
var s2 = Symbol.for('s1')
s1 === s2

// Symbol.keyFor(s1)返回一個Symbol.for()登記過的值的key
// 參數是一個Symbol.for()註冊的數據類型
Symbol.keyFor(s1) // "s1"
複製代碼
  • 內置的Symbol值
// 對象的Symbol.hasInstance指向對象內部一個方法,使用instanceOf判斷一個變量是不是該對象的實例時,會自動調用該對象的這個方法去驗證
// 好比,foo instanceof Foo在語言內部,實際調用的是Foo[Symbol.hasInstance](foo)

class MyClass {
	[Symbol.hasInstance] (arr) {
		return arr instanceof Array
	}
}
[] instanceof new MyClass() // true
'aa' instanceof new MyClass() // false
var o = {}
o instanceof new MyClass() // false
複製代碼

Set

// 相似與不能重複的數組
var s = new Set()
var s2 = new Set([1, 2, 3])
s2.add(4)

// 利用Set去重
[...new Set([1,2,3,4,5,5,5,5])]

// 獲取長度
s.size

// 刪除
s.delete(1) // 刪除成功返回true,不然返回false
// 清除全部
s.clear()

// 檢測是否含有
s.has(2) // 返回true/false

// 轉換成數組
Array.from(new Set([1,2,3,4]))

// Set能夠直接使用forEach方法
// filter/map等只能間接使用
s.forEach((value, key) => {})
s = new Set([...s].filter(e => {}))

// 利用Set實現並集
var s1 = new Set([1,2,3])
var s2 = new Set([2,3,4])
new Set([...s1, ...s2])
// 交集
new Set([...s1].filter(e => s2.has(e)))
複製代碼

Map

// 鍵值對的集合,相似於對象,可是鍵再也不只是字符串
var m = new Map()
var m2 = new Map([[1, 2], ['key', 345]])

// 若是鍵是基本數據類型,只要嚴格相等則認爲是同一個鍵
// 若是鍵是引用類型,只有是同一個引用才斷定是同一個鍵

// 新增數據,set返回當前map對象,因此支持鏈式調用
// 有則覆蓋,無則生成
m.set(2, 3)
m.set(['aa'], 1234)
m.set({}, 543).set('b', 123)

// 讀取鍵值,無則返回undefined
m.get(2)

// 獲取長度
m.size

// 刪除
m.delete(鍵名) // 返回true/false

// 清除全部
m.clear()

// 判斷是否含有某個鍵
m.has()

// 遍歷,遍歷順序就是插入順序
m.forEach()
m.keys()
m.values()
m.entries()

複製代碼

Proxy

  • Proxy代理,給原目標設置一個代理器來控制訪問。代理器只對Proxy返回的實例有效,對原目標對象無效
var obj = {x: 1}
var proxyObj = new Proxy(obj, {
    get () {
        return 2222;
    }
})
console.log(proxyObj.x); // 2222

// Proxy的實例能夠做爲其餘對象的原型對象使用
var obj2 = Object.create(proxyObj)
console.log(obj2.x) // 2222
複製代碼
  • 代理器攔截設置(即new Proxy的第二個參數)
// get攔截對象屬性的讀取
// 接收三個參數:攔截目標,攔截屬性,proxy實例
// 例如,利用proxy生成dom節點的函數
const dom = new Proxy({}, {
    get (target, key, proxySelf) {
        return function (attrs = {}, ...children) {
            let el = document.createElement(key);
            for (let i of Object.keys(attrs)) {
                el.setAttribute(i, attrs[i])
            }
            for (let child of children) {
                if (typeof child === 'string') {
                    child = document.createTextNode(child)
                }
                el.appendChild(child)
            };
            return el;
        }
     }
});
// 調用生成節點
const el = dom.div({},
    'Hello, my name is ',
    dom.a({href: '//example.com'}, 'Mark'),
    '. I like:',
    dom.ul({},
        dom.li({}, 'The web'),
        dom.li({}, 'Food'),
        dom.li({}, '…actually that\'s it') ) ); document.body.appendChild(el); // set攔截對象屬性的設置 // 接收四個參數:目標對象,設置的屬性,屬性值,proxy實例 var proxyHandler = { get(target, key, value, proxySelf) { if (key[0] === '_') { throw new Error(`${key}是一個內部屬性`) } return target[key] }, set(target, key, value, proxySelf) { if (key[0] === '_') { throw new Error(`${key}是一個內部屬性`) } target[key] = value } } var objProxy = new Proxy({}, proxyHandler) objProxy.a = 123 objProxy.a // 123 objProxy._a // 報錯內部屬性 // has攔截HasProperty操做,例如典型的'a' in obj,可是不對for/in生效 // 第一個參數源對象,第二個參數屬性 // construct 攔截new操做 // 三個參數:原目標對象/函數參數/proxy實例自己 var Person = function() {} var PersonProxy = new Proxy(Person, { construct (target, args, proxySelf) { console.log('攔截了new操做') return new target(args) } }) new PersonProxy() // deleteProperty攔截delete操做,若是返回false則沒法刪除 // 參數 target,key // defineProperty攔截Object.defineProperty操做 // 返回false則該操做無效 // 參數: target, key, proxySelf // getOwnPropertyDescriptor攔截Object.getOwnPropertyDescriptor()操做 // 其餘攔截方法 - getPrototypeOf - isExtensible - ownKeys - preventExtensions - setPrototypeOf // 取消proxy的代理器,用於在完成代理後,當即收回代理權 // Proxy.revocable返回一個對象,該對象的proxy屬性是該代理的實例,revoke是一個能夠收回proxy代理權的函數。 var Person = function() {} var {proxy, revoke} = Proxy.revocable(Person, { construct (target, args, proxySelf) { console.log('攔截了new操做') return new target(args) } }) new proxy() // 正常實例化 revoke() // 取消其proxy的代理權 new proxy() // 沒法實例化 // proxy實例以後,實例中的this指代proxy實例 複製代碼

class類

const getname = "GET_USER_NAME";

class Person {
    // 實例屬性也能夠寫在最頂部,此時不須要加this
    time = new Date();
    // 構造函數裏面的內容
    constructor (x, y) {
        this.x = x;
        this.y = y;
        console.log(new.target === Person) //
    }
    // 類原型上的方法
    toString () {
        console.log(this.x + ' - ' + this.y);
    }
    // 靜態方法,只有類能訪問的,實例不能訪問
    // 所以這裏的x,y都是undefined,由於x,y屬性都是實例屬性
    static toString () {
        console.log('靜態方法:', this.x + ' - ' + this.y)
    }
    
    // 可使用表達式命名
    [getName] () {
        
    }
}
const p1 = Person('mack', 25);
p1.toString(); // mack - 25
Person.toString() // 靜態方法: undefined - undefined
    
複製代碼
  • class中的this指向類的實例
  • 類的屬性名能夠採起表達式
  • 能夠對屬性設置get/set攔截
  • 若是不定義constructor函數,則類會默認增長一個constructor函數,並返回類的實例(即this)。若是在constructor中顯示返回一個對象,會致使實例結果再也不是類的實例。
  • es6爲new引入了target屬性,返回實例的構造函數,若是不是經過new調用的,則返回undefined
function Person () {
    // 之前的寫法
    if (!(this instanceof Person)) {
        return new Person()
    }
    // 如今能夠經過new.target
    if (new.target !== Person) {
        return new Person()
    }
}
複製代碼

extends繼承

// 定義父類
class Parent {
    constructor (name, age) {
        this.name = name;
        this.age = age;
    }
    static getName () {
        console.log(this.name);
        return this.name;
    }
    getAge () {
        console.log('age: ', this.age);
    }
}
// 定義子類,子類繼承父類
class Child extends Parent {
    constructor (...arg) {
        // 經過super繼承父類的屬性和方法
        // 必須經過super繼承後,才能使用子類本身的this
        // 不然報錯,得不到子類的this對象
        super(...arg); // es5的實現方式Parent.apply(this, arg)
        // this.name = name;
        // this.age = age;
     }
}

var p1 = new Child('xiaoming', 24);
Child.getName() // 父類的靜態方法,會被子類繼承
p1.getAge() // 父類的原型方法,會被子類的實例繼承
複製代碼
  • Object.getPrototypeOf(Child)獲取父類,能夠用來判斷一個類是否繼承另外一個類
  • super做爲函數時指代父類的constructor,只能用在constructor中
  • super做爲對象使用時,在普通方法中指代父類原型,在靜態方法中,指代父類。
  • 經過super對象調用父類方法時,父類方法中的this,此時指向子類。

導入導出

const a = 5
const b = function(){}
const c = class {}

// 導出
export const age = '123'
export { a, b, c } // 導出多個,建議採起該方式,放在文件底部,一眼就看清楚有哪些導出
export { a as bobyAge } // 導出時定義別名

// 導入
import { a, b, c } from './someJs' // 須要和導出的變量名對應
import { a as otherName } './someJs' // 能夠起一個別名
import * as types from './someJs' // 導入全部變量,並起一個types變量名
複製代碼
  • 屢次導入同一個文件,只會執行一次
  • export導出的內容,import時須要加{}
  • import在編譯時執行,不是在運行時執行,所以不能使用變量
  • import存在聲明提高,所以最好不要和require時使用還對其有依賴關係
const a = 123;
const f = function () {}
const o = {}
const C = Class {}

// 默認導出
// 一個模塊文件只能有一個默認導出
export default a;
// 或
export default f
// 或
export default {
    a,
    f,
    o,
    C
}

// 導入
import newName from './some.js' // 導入時不用和導出的變量名對應,能夠隨便起一個名字
import _ from 'underscore';

// 同時導入默認內容和其餘內容
import o, {a, b, c} from './some.js'
複製代碼
  • 導入導出的複合寫法
// 先導入後導出,此時並無把a,b導入到當前模塊,只至關於對外作了一個轉發
export {a, b} from './some.js'
// 也可使用別名
export {a as otherName} from './some.js' 
複製代碼

結束語

本文內容爲溫習es6內容,對遺漏知識點進行記錄,便於往後翻閱、加深記憶。 內容拜讀的阮一峯大大的ES6入門模塊化

百尺竿頭、日進一步。
我是愣錘,一名前端愛好者。
歡迎批評與交流。函數

相關文章
相關標籤/搜索