Promise
介紹和基本使用
Promise是ES6引入的異步編程的新解決方案,主要用來解決回調地獄問題。語法上 Promise是一個構造函數,用來封裝異步操作并可以獲取其成功或失敗的結果。
-
Promise構造函數:new Promise()
-
Promise.prototype.then方法
-
Promise.prototype.catch方法
//創建實例
const p = new Promise(function (resolve, reject) {//模擬主體業務代碼setTimeout(() => {//let data = "成功獲取數據庫中的數據";//如果獲取數據成功,執行resolve()函數,并將返回數據作為參數傳入//resolve(data);let data = "獲取失敗";//如果獲取數據失敗,執行reject()函數,并將返回數據作為參數傳入reject(data);}, 1000);
});
//調用promise實例
p.then(function (value) {//執行resolve函數執行的方法//...
}, function (reason) {//執行reject()函數執行的方法//...
});
這種寫法好處在于代碼簡潔,避免了回調地獄問題。
Promise.prototype.then
調用then方法,then方法的逐回結果是 Promise對象,對象狀態由回調函數的執行結果決定。
- 如果回調函數中返回的結果是非promise 類型的屬性,狀態為成功,返回值為對象的成功的值.
- 如果回調函數中返回的結果是promise 類型的屬性,狀態根據then方法內部Promise返回的狀態決定。
- 如果直接拋出錯誤,返回值也是promise類型的,值為拋出錯誤的值。
//創建實例
const p = new Promise(function (resolve, reject) {//模擬主體業務代碼setTimeout(() => {//let data = "成功獲取數據庫中的數據";//resolve(data);let data = "獲取失敗";reject(data);}, 1000);
});
//調用promise實例
const result = p.then(function (value) {//1.非promise類型的屬性return 'iloveyou';//2.是 promise對象return new Promise((resolve, reject) => {// resolve('ok');reject('error ');});//3.拋出錯誤throw new Error("出錯啦!");
}, function (reason) {console.warn(reason);
});
console.log(result);
由于promise返回的是promise類型,所以可以進行鏈式調用
const fs = require('fs');const p = new Promise(function (resolve, reject) {fs.readFile("./source/為學.md", function (err, data) {if (err) reject(err);resolve(data);});
});p.then(value => {return new Promise((resolve, reject) => {fs.readFile("./source/為學1.md", function (err, data) {resolve([value, data]);});});
}).then(value => {return new Promise((resolve, reject) => {fs.readFile("./source/為學2.md", function (err, data) {value.push(data);return resolve(value);});});
}).then(value => {console.log(value.join('\r\n'));
});
Promise.prototype.catch
通過cache方法可以指定Promise發生錯誤時的回調。
promise-ajax
const p = new Promise(function (resolve, reject) {const xhr = new XMLHttpRequest();xhr.open('GET', 'https://layuion.com/static/json/table/user.json?page=1&limit=10');xhr.send();xhr.onreadystatechange = function () {if (xhr.readyState == 4) {if (xhr.status >= 200 && xhr <= 400) {resolve(xhr.response);} else {reject('獲取失敗');}}};
});
p.then(function (value) {console.log(value);
}, function (reason) {console.log(reason);
})
set
ES6提供了新的數據結構 set(集合)。它類似于數組,但成員的值都是唯一的(聲明時即使有重復,也會去重)。集合實現了Iterator接口,所以可以使用擴展運算符和for ...of...
進行遍歷。
集合的屬性和方法
size
返回集合的元素個數
add
增加一個新元素,返回當前集合
delete
刪除元素,返回boolean值
has
檢測集合中是否包含某個元素,返回boolean值
//聲明集合并賦值
let s1 = new Set(['豬', '狗', '牛', '羊', '狗']);
console.log(s1); //Set(4) {'豬', '狗', '牛', '羊'}
//長度
console.log(s1.size); //4
//添加
s1.add('貓');
console.log(s1); //Set(5) {'豬', '狗', '牛', '羊', '貓'}
//刪除
s1.delete('牛');
console.log(s1); //Set(4) {'豬', '狗', '羊', '貓'}
//has
console.log(s1.has('貓')) //true
console.log(s1.has('雞')) //false
//清空
s1.clear();
console.log(s1);Set(0) {size: 0}
//遍歷
for (const v of s1) {console.log(v);
}
集合實踐
//集合實踐
let a1 = [1, 2, 3, 5, 5, 6, 7, 8, 6, 5];
console.log(a1); //[1, 2, 3, 5, 5, 6, 7, 8, 6, 5]
//去重
let unq = [...new Set(a1)];
console.log(unq);//[1, 2, 3, 5, 6, 7, 8]
//交集
let a2 = [4, 5, 6, 9, 6, 4, 3];
const result = [...new Set(a1)].filter(item => new Set(a2).has(item));
console.log(result); //[3, 5, 6]
//并集
console.log([...new Set([...a1, ...a2])]);
//差集-> a1 和 a2取差集意思是a1里面有,a2沒有的數據; 也就是交集的取反
console.log([...new Set(a1)].filter(item => !new Set(a2).has(item))); //[1, 2, 7, 8]
map
ES6提供了Map數據結構。它類似于對象,也是鍵值對的集合。但是“鍵”的范圍不限于字符串,各種類型的值(包括對象)都可以當作鍵。Map也實現了iterator接口,所以可以使用「擴展運算符』和「 for…of…』進行遍歷。
Map的屬性和方法:
size 返回Map的元素個數,
set 增加一個新元素,返回當前Map
get 返回鍵名對象的鍵值
has 檢測Map 中是否包含某個元素,返回boolean值
clear清空集合,返回undefined
//聲明map
let m1 = new Map();
//添加
m1.set('team', 'IG');
m1.set('jiaolian', 'ssss');
m1.set('lpl', function () {console.log('IG電子競技!');
})
let boos = {'boos': '王思聰'
}
m1.set(boos, {'上單': 'the shy','打野': 'ning','中單': 'rookie','adc': 'jacklove','輔助': 'baolan'
});
//size
console.log(m1.size);
//刪除
m1.delete('jiaolian');
//獲取
console.log(m1.get('team'));
console.log(m1.get('lpl'));
console.log(m1.get(boos));
//清空
m1.clear();
WeakMap
WeakMap
對象是一組鍵/值對的集合,其中的鍵是弱引用的。其鍵必須是對象,而值可以是任意的。它的鍵被弱保持,也就是說,當其鍵所指對象沒有其他地方引用的時候,它會被 GC 回收掉。
為什么使用weakmap?
在 JavaScript 里,map API 可以 通過使其四個 API 方法共用兩個數組(一個存放鍵,一個存放值)來實現。給這種 map 設置值時會同時將鍵和值添加到這兩個數組的末尾。從而使得鍵和值的索引在兩個數組中相對應。當從該 map 取值的時候,需要遍歷所有的鍵,然后使用索引從存儲值的數組中檢索出相應的值。
但這樣的實現會有兩個很大的缺點:
-
首先賦值和搜索操作都是
O(n)
的時間復雜度(n 是鍵值對的個數),因為這兩個操作都需要遍歷全部整個數組來進行匹配。簡單來說就是賦值和搜索都會遍歷整個數組,時間復雜度都是
O(n)
。 -
另外一個缺點是可能會導致內存泄漏,因為數組會一直引用著每個鍵和值。這種引用使得垃圾回收算法不能回收處理他們,即使沒有其他任何引用存在了。
相比之下,WeakMap
它的鍵被弱保持,也就是說,當其鍵所指對象沒有其他地方引用的時候,它會被 GC 回收掉。WeakMap
提供的接口與Map
相同。
與Map
對象不同的是,WeakMap
的鍵是不可枚舉的。不提供列出其鍵的方法。列表是否存在取決于垃圾回收器的狀態,是不可預知的。
class類
ES6提供了更接近傳統語言的寫法,引入了Class(類)這個概念,作為對象的模板。通過class關鍵字,可以定義類。基本上,ES6的class可以看作只是一個語法糖,它的絕大部分功能,ES5都可以做到,新的 class 寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法而已。
/*--------------------之前es5實現類的方法舉例------------------------*/
//手機
function Phone(brand, price) {this.brand = brand;this.price = price;
}
//添加方法
Phone.prototype.call = function () {console.log("我可以打電話!!");
}
//實例化對象
let Huawei = new Phone('華為', 5999);
Huawei.call();
console.log(Huawei);
/*--------------------es6實現類------------------------*/
class shouji {//構造方法名字不能修改constructor(brand, price) {this.brand = brand;this.price = price;}call() {console.log("我可以打電話!!");}
}
let onePlus = new shouji('1+', 1999);
console.log(onePlus);
onePlus.call();
同一作用域的類名和函數名不能相同,例如上述代碼的類名就不能命名為Phone。
類中構造方法不是必須要有。
靜態成員
function Phone() { } //表示函數對象phone,相當于面向對象里面的類。
//給函數對象添加屬性,它的name屬性只屬于Phone對象。相當于面向對象里面的靜態屬性
Phone.name = '手機';
//給函數對象添加方法。它的change方法只屬于Phone對象相當于面向對象里面的靜態方法
Phone.change = function () {console.log("我可以改變世界");
}
//訪問函數對象屬性和方法
console.log(Phone.name);
console.log(Phone.change);//可以通過函數對象的prototype屬性給實例對象添加方法和屬性
Phone.prototype.size = '5.5inch';
let nokia = new Phone(); //實例化一個對象
console.log(nokia.size); //5.5inch
//undefined 實例化對象和函數對象不相同。所以不能訪問。
console.log(nokia.name);
console.log(nokia.change());//caught TypeError: nokia.change is not a function
es6中寫法
class Phone{//靜態屬性static name ='手機';//只能通過類名訪問static change(){ //只能通過類名訪問console.log("我可以改變世界");}
}
let nokia = new Phone();
console.1og(nokia.name);
console.log(Phone.name);
繼承
es5里實現繼承
function Phone(brand, price) {this.brand = brand;this.price = price;
}
Phone.prototype.call = function () {console.log("我可以打電話");
}
//智能手機
function SmartPhone(brand, price, color, size) {Phone.call(this, brand, price);this.color = color;this.size = size;
}
//設置子級構造函數的原型
SmartPhone.prototype = new Phone;
SmartPhone.prototype.constructor = SmartPhone;//矯正,不加也可以
//聲明子類的方法
SmartPhone.prototype.photo = function () {console.log("我可以拍照")
}
SmartPhone.prototype.playGame = function () {console.log("我可以玩游戲");
}
const chuizi = new SmartPhone('錘子', 2499, '黑色', '5.5inch ');
console.log(chuizi);
es6里面類的繼承
class Phone {constructor(brand, price) {this.brand = brand;this.price = price;}call() {console.log('打電話')}
}class SmartPhone extends Phone {constructor(brand, price, color, size) {super(brand, price);this.color = color;this.brand = brand;}photo() {console.log("我可以拍照");}playGame() {console.log('我可以玩游戲');}
}
let xiaomi = new SmartPhone('小米', '799', '粉色', '70inch');
console.log(xiaomi);
es6中只允許繼承一個類。
es6類中普通的方法中不允許調用super
方法
重寫
子類中可以聲明同父類名稱相同的方法。
getter和setter
getter:可以對類屬性綁定一個函數,當調用這個屬性的時候,這個函數被執行,這個函數的返回值就是這個屬性的值。通常對對象的動態屬性進行封裝。
setter:可以對類屬性綁定一個函數,當設置這個屬性的時候,這個函數被執行。可以對屬性的合法性進行判斷
class Phone {//這里的price為類Phone的屬性,后邊的分別是getter和setter綁定的方法get price() {console.log('get price')return 90;}set price(price) {console.log("set price");}
}
let p = new Phone();
console.log(p.price);//get price 90
p.price = 100;//set price
數值擴展
Number.EPSILON
Number.EPSILON
是JavaScript表示的最小精度。
EPSILON
屬性的值接近于2.2204460492503130808472633361816E-16
。
通常用于兩個數字進行比較,如果結果比Number.EPSILON小或等于這個數,就認為這兩個數字相等。
function equal(a, b) {if (Math.abs(a - b) < Number.EPSILON) {return true;} else {return false;}
}
console.log(0.1 + 0.2 === 0.3); //false
console.log(equal(0.1 + 0.2, 0.3)) //true
二進制和八進制
let b = 0b1010; //二進制
let o = 0o777; //八進制
let d = 100; //十進制
let x= 0xff; //16進制
Number.isFinite()
檢測一個數值是否為有限數
console.log(Number.isFinite(100)); //true
console.log(Number.isFinite(100/0)); //false
console.log(Number.isFinite(Infinity));//false
Number.isNaN()
Number.isNaN()
檢測一個數值是否為NaN
Number.parseInt() Number.parseFloat()
字符串轉整數
Number.isInteger()
判斷一個數是否為整數
Math.trunc()
將數字的小數部分抹掉
Math.sign()
判斷一個數到底為正數、負數還是零
對象的方法擴展
object.is()
判斷兩個值是否完全相等。和===
略有不同,判斷NaN的時候不一樣。
console.log(Object.is(120,120));//true
console.log(Object.is(NaN,NaN));//true
console.log(NaN === NaN);//false
Object.assign()
對象的合并。如果合并的對象中有相同的屬性或方法,后邊的會覆蓋前邊的屬性或方法。
get或set原型對象
Object.setPrototype0f 設置原型對象
Object.getPrototypeof 獲取原型對象
模塊化
模塊化是指將一個大的程序文件,拆分成許多小的文件,然后將小文件組合起來。
模塊化的好處
防止命名沖突
代碼復用
高維護性
模塊化規范產品
ES6 之前的模塊化規范有:
CommonJs規范 => NodeJS、Browserify產品
AMD規范 => requireJs產品
CMD規范 => seaJs產品
ES6模塊化語法
模塊功能主要由兩個命令構成: export和 import。
export命令用于規定模塊的對外暴露接口。
/*---./js/m1.js---*/
//分別暴露
export let s = "測試模塊";
export function change() {console.log('用心改變世界');
}
上邊的使用export分別將s
和change
暴露。還有以下兩種暴露方式:
-
統一暴露:在最底部寫入一下代碼
export {s, change}
; -
默認暴露:default里面可以跟任意數據,對象居多。這種方式使用的時候就要多加個default,例如:
m1.default.school
//默認暴露 export default {s: "測試模塊',change: function(){console.log("我們可以改變你!!");} }
import命令用于輸入其他模塊提供的功能
import * as m1 from "./js/m1.js";
console.log(m1);
上邊的代碼使用的是通用導入方式,還有以下兩種導入方式:
-
解構賦值形式
import {s, change} from "./js/m1.js"; import {s as guigu, change} from "./js/m2.js"; //與第一個s重名,可以使用as命名別名 import {default as m3} from " ./js/m3.js";
-
簡便形式,只能針對默認暴路
import m3 from "./js/m3.js"; console.log(m3);
一般引入模塊都是放在一個入口文件里面如:app.js
, 在html中使用時候引入這個文件就可以了
<script src="./src/js/app.js" type="module"></script>
注意:type="module"
.
在js文件中只要使用了import
語句,不管import
之間是否有javascript語句,都會按import
順序先執行import的語句
es6模塊化代碼轉換
由于es6語法對所有的瀏覽器都都不一定支持,所以考慮到兼容性,需要將es6模塊代碼轉換成es5代碼。
-
安裝工具 babel-cli(babel客戶端命令行) babel-preset-env(babel環境) browserify(項目中一般使用webpack打包)
-
使用命令
npx babel src/js -d dist/js --presets=babel-preset-env
,如果全局安裝babel,可以直接使用babel命令:babel src/js -d dist/js --presets=babel-preset-env
. 命令的格式:[npx] babel 要轉換文件路徑 -d 轉換完成后要存儲的文件路徑 --presets=babel-preset-env
-
打包:
npx browserify dist/js/app.js -o dist/bundle.js
。
打包完成后就可以在html引入了。每次更改都需要進行上述2、3步操作。
proxy
Proxy 對象用于創建一個對象的代理,從而實現基本操作的攔截和自定義(如屬性查找、賦值、枚舉、函數調用等)。proxy 是javascript的內置對象,在es6中被引入。
語法
const p = new Proxy(target, handler)
參數
target
: 要使用 Proxy
包裝的目標對象(引用類型:對象,數組,函數,map, set, 甚至另一個代理)。
handler
: 一個通常以函數作為屬性的對象,各屬性中的函數分別定義了在執行各種操作時代理 p
的行為。
let person = {name: "John",age: 21,
};const p = new Proxy(person, {/*** 攔截讀取屬性* @param target 目標對象* @param property 被獲取的屬性名。* @param receiver Proxy 或者繼承 Proxy 的對象*/get(target, property, receiver) {},/*** 攔截賦值屬性* @param target 目標對象* @param property 被設置的屬性名。* @param value 屬性值* @param receiver Proxy 或者繼承 Proxy 的對象* @returns boolean*/set(target, property, value, receiver) {return true;},/*** 攔截方法調用* @param target 目標對象(函數)* @param thisArg 被調用時的上下文對象* @param argumentsList 被調用時的參數數組。* apply方法可以返回任何值。*/apply(target, thisArg, argumentsList) {},/*** 用于攔截 new 操作符* @param target 目標對象* @param argumentsList constructor 的參數列表* @param newTarget 最初被調用的構造函數* construct 方法必須返回一個對象。*/construct(target, argumentsList, newTarget) {return {};},/*** 用于攔截 in 操作符* @param target 目標對象。* @param prop 需要檢查是否存在的屬性。* has 方法返回一個 boolean 屬性的值。*/has(target, prop) {return true;},/*** 法用于攔截 Reflect.ownKeys()* @param target 目標對象*/ownKeys(target) {return Reflect.ownKeys(target);},/*** 用于攔截對對象屬性的 delete 操作。* @param target 目標對象。* @param property 待刪除的屬性名。* @returns boolean*/deleteProperty(target, property) {return true;},
});
Reflect
Reflect 是一個內置的對象,它提供攔截 JavaScript 操作的方法。這些方法與 proxy handler (en-US) 的方法相同。Reflect
不是一個函數對象,因此它是不可構造的。反射,就是將代理的內容反射出去。
與大多數全局對象不同 Reflect
并非一個構造函數,所以不能通過 new 運算符對其進行調用,或者將 Reflect
對象作為一個函數來調用。Reflect
的所有屬性和方法都是靜態的。
注意:Reflect的靜態方法是直接使用的
const p = new Proxy(person, {/*** 攔截讀取屬性* @param target 目標對象* @param property 被獲取的屬性名。* @param receiver Proxy 或者繼承 Proxy 的對象*/get(target, property, receiver) {if (target.age <= 15) {return Reflect.get(target, property, receiver); //直接使用和對象操作一樣} else {return "John成年了!!!";}},
靜態方法
Reflect.apply(target, thisArgument, argumentsList)
對一個函數進行調用操作,同時可以傳入一個數組作為調用參數。和 Function.prototype.apply() 功能類似。Reflect.construct(target, argumentsList[, newTarget])
對構造函數進行 new 操作,相當于執行 new target(...args)。Reflect.deleteProperty(target, propertyKey)
作為函數的delete操作符,相當于執行 delete target[name]。Reflect.get(target, propertyKey[, receiver])
receiver可以理解為上下文this對象
獲取對象身上某個屬性的值,類似于 target[name]。Reflect.getPrototypeOf(target)
類似于 Object.getPrototypeOf()。Reflect.has(target, propertyKey)
判斷一個對象是否存在某個屬性,和 in 運算符 的功能完全相同。Reflect.isExtensible(target)
類似于 Object.isExtensible().Reflect.ownKeys(target)
返回一個包含所有自身屬性(不包含繼承屬性)的數組。(類似于 Object.keys(), 但不會受enumerable 影響).Reflect.preventExtensions(target)
類似于 Object.preventExtensions()。返回一個Boolean。Reflect.set(target, propertyKey, value[, receiver])
將值分配給屬性的函數。返回一個Boolean,如果更新成功,則返回true。
在面向對象中,反射的三種用法:1、查看元數據。2、動態創建對象。3、動態調用方法。
示例
模擬mobx
const list: Set<Function> = new Set();
const autorun = (cb: Function) => {list.add(cb);
};
const observable = <T extends object>(target) => {return new Proxy(target, {set(target, p, newValue, receiver) {const result = Reflect.set(target, p, newValue, receiver);list.forEach((fn) => fn());return result;},});
};
const dog = observable({ name: "阿黃", age: 1 });
autorun(() => {console.log("change");
});dog.name = "阿綠"; //change
dog.age = 2; //change