- 學習序
- 1. 環境搭建
- 2. let 與 const
- 3. 解構賦值
- 4. Symbol
- 5. Map 與 Set
- 6. 字符串
- 7. 對象
- 8. 數組
- 9. 函數
- 10. Class 類
- 11. 模塊的導入導出
- 12. Promise 對象
- 13. Generator 函數
- 14. async 函數
- 15. Proxy 與 Reflect
學習序
1.前端技術
十年前說前端,一定是Flash、Html、Js、Css,這是我工作的時候,前端接觸最普遍的技術,Jquery可以說是很新的技術了。
但發展到今天的前端是一個范圍很廣的技術領域,發展迭代之快,衍生框架之多,可以說其他語言是不能媲美的,這也說明了前端技術是一個活躍的、標準難制定的領域。
這也直接推動了前端技術棧的發展,包括JS的UI框架、模塊化的MVVM框架及構建工具、JavaScript 編譯器、CSS的預處理器、HTML5技術。
常用的JS相關框架:
Ext.js、Jquery、Jquery MiniUI、Jquery EasyUI (解決瀏覽器兼容)
React、Angular 2、 Vue(MVVM框架)
RequireJS、 SeaJS、Webpack
Babel、CoffeeScript、 TypeScript
Less、Saas
2.移動開發技術
App(Application 應用程序)一般是指手機端的軟件。手機端的操作系統有iOS、Android、Windows Phone、Fuchsia(Google下一代手機操作系統)
各系統的開發語言有:
iOS --- Object-C、Swift
Android ----Java、Kotlin
Fuchsia ---- Dart
上面都可以稱為原生開發,移動端技術發展也是提高生產力的過程,出現了跨平臺開發的框架,一套代碼多端運行,現在常見的開發框架有H5、小程序、uni-app(VUE跨平臺框架)、Vue-Native、weex、React-Native、Flutter
3.學習基礎的重要性
學習好這些基礎是寫出合理代碼的重要支撐。
實際工作中可能受限于工期和領導的壓力,我們在基礎的學習上都是蜻蜓點水,急急忙忙的做出效果,導致代碼頻出bug,這點我是親身經歷過的痛。
去年由原生開發轉Native的同事,es6看了一禮拜,告訴我已經學的差不多了,然后讓他加入了項目組開始開發,一天的折騰后,臨下班前說界面輸入的文本,在傳參的時候獲取不到,找不到問題所在,然后加入log輸入參數
我和他一起看打印出的參數,他義正言辭的說沒問題,我看了知道是結構賦值的問題。讓他重新看了看解構賦值,多想想以前的json取值
結果再過兩天又犯了同樣的錯誤,抱怨半天。其實還是基礎沒理解好,這會很影響開發效率。
廢話不多說,祝君學習順利 !
1. 環境搭建
Node.js是運行在服務端的JavaScript環境,基于Google的V8引擎,下載地址Node.js 安裝包及源碼下載地址為:https://nodejs.org/en/download/。
Node.js 歷史版本下載地址:https://nodejs.org/dist/
具體的安裝教程:https://www.runoob.com/nodejs/nodejs-install-setup.html,
安裝完成以后,以windows為例,打開cmd窗口 執行node -v查看,輸出版本號說明安裝成功。
成功之后就可以使用 npm 命令來管理安裝的依賴包了。也可使用 yarn
npm install -g yarn
由于某些安裝包有可能安裝失敗的風險,做React Native的同學應該深有體會,原因當然是天朝網絡的問題了,所以我們需要安使用國內源,cmd窗口執行
npm config set registry https://registry.npm.taobao.org --global
npm config set disturl https://npm.taobao.org/dist --global
或
yarn config set registry https://registry.npm.taobao.org --global
yarn config set disturl https://npm.taobao.org/dist --global
好多人推薦使用 yarn,原因是npm的版本管理有個安裝不一致的問題,比如 ~1.0.3 指的是 1.0.X的最新版,^1.0.3是指1.X.X的最新版,很容易導致一個月前項目跑起來好好的,來一個新同事,項目跑起來有問題,我相信入坑多年的都有這種經歷。yarn的出現也解決了這個痛點(
其實npm 已經用package-lock.json解決了這個問題。用哪個看你喜好
這是常用的操作命令
npm | yarn |
---|---|
npm install | yarn |
npm install react --save | yarn add react |
npm uninstall react --save | yarn remove react |
npm install react --save-dev | yarn add react --dev |
npm update --save | yarn upgrade |
2. let 與 const
代碼塊有效
var PI = "a";
{
console.log(PI) // 報錯
let a = 0;
var b = 1;
}
console.log(b)
let 只能聲明一次 var 可以聲明多次
let a = 1;
let a = 2; // 報錯
var b = 3;
var b = 4;
console.log(b)
const 是聲明常量,相當于java中final聲明
const PI = "3.1415926";
PI = "dadada" // 報錯
console.log(PI)
3. 解構賦值
-
數組模型的解構(Array)
情景1: let [a, b, c] = [1, 2, 3]; // a = 1 // b = 2 // c = 3
情景2: let [a, [\[b], c]] = [1, [\[2], 3]]; // a = 1 // b = 2 // c = 3
情景3: let [a, ...b] = [1, 2, 3] //a = 1 //b = [2, 3]
情景4: let [a, b, c] = [1, 2, 3]; // a = 1 // b = 2 // c = 3
-
對象模型的解構(Object)
情景1: let { football, basketball } = { football: 'CSL', basketball: 'NBA' } // football = 'CSL' // basketball = 'NBA'
情景2: const {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40}; // a = 10 // b = 20 // rest = {c: 30, d: 40}
4. Symbol
這是一種新的基礎數據類型,和Object、 Number 、 String 、 Boolean同級
-
基本使用
let sy = Symbol("zk"); console.log(sy); // Symbol(zk) typeof(sy); // "symbol" let sy1 = Symbol("zk"); // 相同參數 Symbol() 返回的值不相等 sy === sy1; // false
-
適用場景1
使用symbol作為對象屬性名(key)const param3 = Symbol() let obj = { param1: 100, "param2": "egg", [param3]:"我是params33" } obj["param1"] // 100 obj["param2"] // 'egg' const param4 = Symbol() obj[param4] = "我是param444"
Symbol可同樣用于對象屬性的定義和訪問:
let obj = { [Symbol('name')]: '老師', age: 43, dept: '軟件學院' } Object.keys(obj) // ['age', 'title']
-
適用場景2
注冊和獲取全局Symbol,Symbol.for() 類似單例模式let instance = Symbol("Instance"); let instance1 = Symbol.for("Instance"); instance === instance1; // false let instance2 = Symbol.for("Instance"); instance1 === instance2; // true
5. Map 與 Set
Map 的鍵值可以是任意值,開發通用是String 字符串
var myMap = new Map();
var keyString = "a string";
myMap.set(keyString, "和鍵'a string'關聯的值");
myMap.get(keyString); // "和鍵'a string'關聯的值"
myMap.get("a string"); // "和鍵'a string'關聯的值"
-
Map 迭代
for...ofvar myMap = new Map(); myMap.set(0, "zero"); myMap.set(1, "one"); // 將會顯示兩個 log。 一個是 "0 = zero" 另一個是 "1 = one" for (var [key, value] of myMap) { console.log(key + " = " + value); } // 將會顯示兩個log。 一個是 "0" 另一個是 "1" for (var key of myMap.keys()) { console.log(key); } /* 這個 keys 方法返回一個新的 Iterator 對象, 它按插入順序包含了 Map 對象中每個元素的鍵。 */ // 將會顯示兩個log。 一個是 "zero" 另一個是 "one" for (var value of myMap.values()) { console.log(value); } /* 這個 values 方法返回一個新的 Iterator 對象,它按插入順序包含了 Map 對象中每個元素的值。 */
forEach()
var myMap = new Map(); myMap.set(0, "zero"); myMap.set(1, "one"); // 將會顯示兩個 logs。 一個是 "0 = zero" 另一個是 "1 = one" myMap.forEach(function(value, key) { console.log(key + " = " + value); }, myMap)
-
Map 與 Array的轉換
var kvArray = \[["key1", "value1"], ["key2", "value2"]]; // Map 構造函數可以將一個 二維 鍵值對數組轉換成一個 Map 對象 var newMap = new Map(kvArray); // 使用 Array.from 函數可以將一個 Map 對象轉換成一個二維鍵值對數組 var outArray = Array.from(newMap);
-
Set
Set 對象允許你存儲任何類型的唯一值,無論是原始值或者是對象引用。+0 與 -0 在存儲判斷唯一性的時候是恒等的,所以不重復;
undefined 與 undefined 是恒等的,所以不重復;let mySet = new Set(); mySet.add(1); // Set(1) {1} mySet.add(5); // Set(2) {1, 5} mySet.add(5); // Set(2) {1, 5} 這里體現了值的唯一性 mySet.add("some text"); // Set(3) {1, 5, "some text"} 這里體現了類型的多樣性 var o = {a: 1, b: 2}; mySet.add(o); mySet.add({a: 1, b: 2}); // Object即使一樣,所以mySet的結構是 Set(5) {1, 5, "some text", {…}, {…}}
-
Set 與 Array
// Array 轉 Set var mySet = new Set(["value1", "value2", "value3"]); // 用...操作符,將 Set 轉 Array var myArray = [...mySet]; // String 轉 Set var mySet = new Set('hello'); // Set(4) {"h", "e", "l", "o"}
數組去重的使用
var mySet = new Set([1, 2, 3, 4, 4]); [...mySet]; // [1, 2, 3, 4]
6. 字符串
ES6 之前判斷字符串是否包含子串,用 indexOf 方法,ES6 新增了子串的識別方法。
includes():返回布爾值,判斷是否找到參數字符串。
startsWith():返回布爾值,判斷參數字符串是否在原字符串的頭部。
endsWith():返回布爾值,判斷參數字符串是否在原字符串的尾部。
let string = "apple,banana,orange";
string.includes("banana"); // true
string.startsWith("apple"); // true
string.endsWith("apple"); // false
string.startsWith("banana",6) // true
這三個方法只返回布爾值,如果需要知道子串的位置,還是得用 indexOf 和 lastIndexOf 。
-
模板字符串
模板字符串相當于加強版的字符串,用反引號 `,除了作為普通字符串,還可以用來定義多行字符串,還可以在字符串中加入變量和表達式。let name = 'zk'; let age = 18; let info =\`我的名字是 \${name},我今年 \${age+1} 歲了\`;
7. 對象
ES6允許對象的屬性直接寫變量,這時候屬性名是變量名,屬性值是變量值。
const age = 12;
const name = "Amy";
const person = {age, name};
//等同于
const person = {"age": age, "name": name}
-
方法名也可以簡寫
const person = { sayHi(){ console.log("Hi"); } } person.sayHi(); //"Hi" //等同于 const person = { sayHi:function(){ console.log("Hi"); } } person.sayHi();//"Hi"
-
拓展運算符(...)用于取出參數對象所有可遍歷屬性然后拷貝到當前對象
let person = {name: "Amy", age: 15}; let someone = { ...person }; someone; //{name: "Amy", age: 15}
如果是相同的屬性,后加入的屬性覆蓋前面的
let person = {name: "Amy", age: 15}; let someone = {name: "Mike", age: 17, ...person}; someone; //{name: "Amy", age: 15}
或
let person = {name: "Amy", age: 15}; let someone = {...person,name: "Mike", age: 17}; someone; //{name: "Mike", age: 17}
-
Object.assign()方法使用
assign的方法定義是 Object.assign(target, source1,source2, ···)
用于將源對象的所有可枚舉屬性復制到target對象中。let sourceObj = { a: { b: 1}}; let targetObj = {c: 3}; Object.assign(targetObj, sourceObj); targetObj.a.b = 2; sourceObj.a.b; // 2 // 這里體現的是assign的淺拷貝
8. 數組
-
1. 數組數據的基本使用
const arr = ["人民幣","美元"] // 初始化數組 arr.push("歐元","英鎊") arr// ["人民幣","美元","歐元","英鎊"] arr[2] = "港元" ; arr[4] = "盧布" ; arr// ["人民幣","美元","港元","英鎊",'盧布']
-
2. 轉換可迭代對象
轉換 map
let map = new Map(); map.set('key0', 'value0'); map.set('key1', 'value1'); console.log(Array.from(map));// \[['key0', 'value0'],['key1', // 'value1']]
轉換 set
let arr = [1, 2, 3]; let set = new Set(arr); console.log(Array.from(set)); // [1, 2, 3]
轉換字符串
let str = 'abc'; console.log(Array.from(str)); // ["a", "b", "c"]
-
3. 數組元素的增加與刪除
array.push(e1, e2, ...eN) 將一個或多個元素添加到數組的末尾,并返回新數組的長度。
const array = [1, 2, 3]; const length = array.push(4, 5);
array.unshift(e1, e2, ...eN) 將一個或多個元素添加到數組的開頭,并返回新數組的長度。
const array = [1, 2, 3]; const length = array.unshift(4, 5);
array.pop() 從數組中刪除最后一個元素,并返回最后一個元素的值,數組為空時返回undefined。
const array = [1, 2, 3]; const poped = array.pop(); // array: [1, 2]; poped: 3
array.shift() 刪除數組的第一個元素,并返回第一個元素,原數組的第一個元素被刪除。數組為空時返回undefined。
const array = [1, 2, 3]; const shifted = array.shift(); // array: [2, 3]; shifted: 1
array.splice(start[, deleteCount, item1, item2, ...]) 從數組中添加/刪除元素,返回值是由被刪除的元素組成的一個新的數組,如果只刪除了一個元素,則返回只包含一個元素的數組。如果沒有刪除元素,則返回空數組。
const array = [1, 2, 3, 4, 5]; const deleted = array.splice(2, 0, 6);*// 在索引為2的位置插入6* *// array 變為 [1, 2, 6, 3, 4, 5]; deleted為[]*
-
4. 數組的截取與合并
- 數組的截取 - array.slice(start, end) 方法,start (必填), end(選填)
slice()通過索引位置,從數組中返回start下標開始,直到end下標結束(不包括)的新數組,該方法不會修改原數組,只是返回一個新的子數組。
end參數如果不填寫,默認到數組最后// 獲取僅包含最后一個元素的子數組 let array = [1,2,3,4,5]; array.slice(-1); // [5] // 獲取不包含最后一個元素的子數組 let array2 = [1,2,3,4,5]; array2.slice(0, -1); // [1,2,3,4]
注意:該方法并不會修改數組,而是返回一個子數組,如果想刪除數組中的一段元素,應該使用方法 array.splice()。
- 數組的截取 - array.slice(start, end) 方法,start (必填), end(選填)
-
- 數組的合并 - array.concat([item1[, item2[, . . . [,itemN]]]])方法
conact()是將多個數組(也可以是字符串,或者是數組和字符串的混合)連接為一個數組,返回連接好的新的數組。
const array = [1,2].concat(['a', 'b'], ['name']); // [1, 2, "a", "b", "name"]
- 數組的合并 - array.concat([item1[, item2[, . . . [,itemN]]]])方法
-
5. 數組元素的排序
-
- array.sort()方法
按照數值大小進行排序-升序
按照數值大小進行排序-降序[1, 8, 5].sort((a, b) => { return a-b; // 從小到大排序 }); // [1, 5, 8]
[1, 8, 5].sort((a, b) => { return b-a; // 從大到小排序 }); // [8, 5, 1]
- array.sort()方法
-
- array.reverse()方法
reverse() 方法將數組中元素的位置顛倒
let arr = [1,2,3,4,5] console.log(arr.reverse()) // [5,4,3,2,1] console.log(arr) // [5,4,3,2,1]
- array.reverse()方法
-
-
6. 數組的遍歷與迭代
- array.filter(callback, thisArg)方法使用指定的函數測試所有元素,并創建一個包含所有通過測試的元素的新數組。
// callback定義如下,三個參數: element:當前元素值;index:當前元素下標; array:當前數組 function callback(element, index, array) { // callback函數必須返回true或者false,返回true保留該元素,false則不保留。 return true || false; } const filtered = [1, 2, 3].filter(element => element > 1); // filtered: [2, 3];
- array.map(callback[, thisArg])方法返回一個由原數組中的每個元素調用callback函數后的返回值組成的新數組。
let a = [1, 2, 3, 4, 5]; let bbb = a.map((item) => { const key = `key${item}` const object = {} object[key] =item return object }); console.log(bbb);
- array.forEach(callbak)為數組的每個元素執行對應的方法。
let a = [1, 2, 3, 4, 5]; let b = []; // item:當前元素值;index:當前元素下標; array:當前數組 a.forEach((item,index,array) => { b.push(item + 1); }); console.log(b); // [2,3,4,5,6]
- array.reduce(callback[, initialValue])方法返回針對數組每項調用callback函數后產生的累積值。
const total = [0, 1, 2, 3].reduce((sum,currentValue, currentIndex, array) => { return sum + currentValue; }, 0); // total is 6 const flattened = [[0, 1], [2, 3], [4, 5]].reduce((a, b) => { return a.concat(b); }, []); // flattened is [0, 1, 2, 3, 4, 5]
9. 函數
-
默認參數
function fn(name,age=17){ console.log(name+","+age); } fn("Amy",18); // Amy,18 fn("Amy",""); // Amy, fn("Amy"); // Amy,17
只有在未傳遞參數,或者參數為 undefined 時,才會使用默認參數,null 值被認為是有效的值傳遞。
fn("Amy",null); // Amy,null
-
不定參數或可變參數
function f(...values){ console.log(values.length); } f(1,2); //2 f(1,2,3,4); //4
-
箭頭函數
基本語法是: 參數 => 函數體var f = v => v; //等價于 var f = function(a){ return a; } f(100); //100
如果箭頭函數返回的對象,必須使用 () 括起來
var f = (id,name) => ({id, name}); f(6,2); // {id: 6, name: 2}
箭頭函數體中的 this 對象,是定義函數時的對象,而不是使用函數時的對象。
function fn(){ setTimeout(()=>{ // 定義時,this 綁定的是 fn 中的 this 對象 console.log(this.a); },0) } fna = ()=>{ console.log("我是fna 函數"); } var a = 20; // fn 的 this 對象為 {a: 20} fn.call({a: 18}); // 18
上面代碼中的call是替換this的對象,大白話就是可以使用箭頭函數外層的變量,包括方法,實際的應用場景中 ...展開運算符可以替代這個方法。
箭頭函數常用的一個場景是:回調函數需要使用外層的this,改變變量值,或使用方法**
10. Class 類
class (類)作為對象的模板被引入,可以通過 class 關鍵字定義類。
class 的本質是 function。
它可以看作一個語法糖,使用起來更像面向對象編程的語法。
- 類聲明
class Example {
constructor(a) {
this.a = a;
}
}
Example 只能聲明一次,名字不能重復聲明
- 實例化對象
class Example {
constructor(a, b) {
this.a = a;
this.b = b;
console.log('Example');
}
sum() {
return this.a + this.b;
}
}
let exam1 = new Example(2, 1);
let exam2 = new Example(3, 1);
console.log(exam1.sum(),exam2.sum()); // 3, 4
exam1.__proto__.sub = function() {
return this.a - this.b;
}
console.log(exam1.sub()); // 1
console.log(exam2.sub()); // 2
使用實例的proto屬性改寫原型,必須相當謹慎,不推薦使用,因為這會改變“類”的原始定義,影響到所有實例。
-
decorator
decorator 是一個函數,用來修改類的行為,在代碼編譯時產生作用。
上面是類修飾,以下是方法修飾,定義修飾函數有3個參數:target(類的原型對象)、name(修飾的屬性名)、descriptor(該屬性的描述對象)。@testable export class Example2 {} // target就是class本身的實例 function testable(target) { target.isTestable = true; } // 多參數修飾 function makeSure(isOk,isCancle) { return function(target) { target.isOk=isOk; target.prototype.isCancle = isCancle } } @makeSure(true,false) export class Example3 {}
修飾器執行順序:由外向內進入,由內向外執行。class Math { @log add(a, b) { return a + b; } } function log(target, name, descriptor) { var oldValue = descriptor.value; descriptor.value = function() { console.log(`Calling ${name} with`,target, descriptor,arguments); return oldValue.apply(this, arguments); }; return descriptor; } const math = new Math(); // passed parameters should get logged now math.add(2, 4);
class Example { @logMethod(1) @logMethod(2) sum(a, b){ return a + b; } } function logMethod(id) { console.log('先執行 logMethod'+id); return (target, name, desctiptor) => console.log('后執行 logMethod '+id); }
- 構造函數與繼承
class Example{
constructor(a, b) {
this.a = a; // 實例化時調用 set 方法
this.b = b;
}
get a(){
console.log('getter');
return this.a;
}
set a(a){
console.log('setter');
this._a = a; // 正確定義
<!-- this.a = a; // 自身遞歸調用 -->
}
}
extends 通過 extends 實現類的繼承。getter和setter 必須同時出現
class Father1 {
constructor(){}
// 或者都放在子類中
get a() {
return this._a;
}
set a(a) {
this._a = a;
}
}
class Child1 extends Father1 {
constructor(){
super(); // 必須放在第一行,后面是相應的代碼
this.child = {name:"child"}
}
}
let test1 = new Child1();
test1.a = 2;
console.log(test1.a); // 2
11. 模塊的導入導出
ES6 的模塊化分為導出(export) @與導入(import)兩個模塊。模塊中可以導入和導出各種類型的變量,如函數,對象,字符串,數字,布爾值,類等。
-
export 與 import
// demo10.js let myName = "Tom"; let myAge = 20; let myfn = function(){ return "我的名字叫" + myName + "! 我今年" + myAge + "歲了" } let MyClass = class MyClass { static a = "hello word!"; } export { myName, myAge, myfn, MyClass }
使用的時候有幾種導入方式
import { myName, myAge, myfn, MyClass } from "./demo10.js"; console.log(myfn());// 我的名字是 Tom! 我今年 20 歲了. console.log(myAge);// 20 console.log(myName);// Tom console.log(MyClass.a );// hello world! 或 import * as all from "./demo10.js"; console.log(all.myfn());// 我的名字是 Tom! 我今年 20 歲了. console.log(all.myAge);// 20 console.log(all.myName);// Tom console.log(all.MyClass.a );// hello world!
注意點:import 是靜態執行,所以不能使用表達式和變量。
-
export default 命令
- 在一個文件或模塊中,export、import 可以有多個,export default 僅有一個。
- export default 中的 default 是對應的導出接口變量。
- 通過 export 方式導出,在導入時要加{ },export default 則不需要。
- export default 向外暴露的成員,可以使用任意變量來接收。
// export-default.js export default function () { console.log('foo'); }
上面代碼是一個模塊文件export-default.js,它的默認輸出是一個函數。
其他模塊加載該模塊時,import命令可以為該匿名函數指定任意名字。
// import-default.js import customName from './export-default'; customName(); // 'foo'
export default命令的本質是將后面的值,賦給default變量
12. Promise 對象
Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。
所謂Promise,簡單說就是一個容器,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果。從語法上說,Promise 是一個對象,從它可以獲取異步操作的消息。Promise 提供統一的 API,各種異步操作都可以用同樣的方法進行處理。
Promise 狀態
狀態的特點
Promise 異步操作有三種狀態:pending(進行中)、fulfilled(已成功)和 rejected(已拒絕或已失敗)。除了異步操作的結果,任何其他操作都無法改變這個狀態。
Promise 對象只有:從 pending 變為 fulfilled 和從 pending 變為 rejected 的狀態改變。只要處于 fulfilled 和 rejected ,狀態就不會再變了即 resolved(已定型或已完成)。-
then 方法
then 方法接收兩個函數作為參數,第一個參數是 Promise 執行成功時的回調,第二個參數是 Promise 執行失敗時的回調,兩個函數只會有一個被調用。
我們首先創建一個Promise實例const promise = new Promise(function(resolve, reject) { // ... 業務代碼 if (/* 異步操作成功 */){ resolve(value); } else { reject(error); } });
Promise構造函數接受一個函數作為參數,該函數的兩個參數分別是resolve和reject。它們是兩個函數,由 JavaScript 引擎提供,不用自己部署。
Promise實例生成以后,可以用then方法分別指定resolved狀態和rejected狀態的回調函數。定義方式是這樣 then(function1,function2)
promise.then(function(value) { // 這是定義的 resolve }, function(error) { // 這是定義的 rejected });
下面我們用js中的延遲函數setTimeout模擬異步操作
function timeout(ms) { return new Promise((resolve, reject) => { setTimeout(resolve, ms, '我延遲了兩秒'); }); } timeout(2000).then((value) => { console.log(value); });
如果調用resolve函數和reject函數時帶有參數,那么它們的參數會被傳遞給回調函數。reject函數的參數通常是Error對象的實例,表示拋出的錯誤;resolve函數的參數除了正常的值以外,還可能是另一個 Promise 實例,比如像下面代碼:
const p1 = new Promise(function (resolve, reject) { setTimeout(() => reject(new Error('fail')), 3000) }) const p2 = new Promise(function (resolve, reject) { setTimeout(() => resolve(p1), 1000) }) p2.then(result => console.log(result)) .catch(error => console.log(error))
上述代碼中p2的resolve方法的參數是p1的Promise的實例,這時p1的狀態就會傳遞給p2,也就是說,p1的狀態決定了p2的狀態,p1的狀態是resolved或者rejected時,p2的回調函數執行
? -
Promise 的常規用法
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
這是最常用的方式, 我們通過一個例子來輸出看一下執行的順序const promise = new Promise((resolve, reject)=> { console.log('我第一個執行') console.log(' 這里是寫邏輯代碼的地方1')// 這里是邏輯代碼 resolve({key:"我是返回的數據1"}); }); promise.then(result => { console.log(result) return {key:"我是then之后,返回的數據2"} }).then(result => { console.log(' 這里是寫邏輯代碼的地方2')// 這里是邏輯代碼 console.log(result) }) .catch(error => { console.log(error) })
-
Promise.all
Promise.all方法接受一個數組作為參數,Promise.all([p1, p2, p3])。
p1、p2、p3都是 Promise 實例,如果不是,就會先調用下面講到的Promise.resolve方法,將參數轉為 Promise 實例,再進一步處理。const p1 = new Promise((resolve, reject) => { resolve('hello1'); }) .then(result => result) .catch(e => e); const p2 = new Promise((resolve, reject) => { <!-- throw new Error('報錯了'); --> resolve('hello2'); }) .then(result => result) .catch(e => e); Promise.all([p1, p2]) .then(result => console.log(result)) .catch(e => console.log(e));
13. Generator 函數
Generator 函數可以通過 yield 關鍵字,把函數的執行流掛起,為改變執行流程提供了可能,從而為異步編程提供解決方案。
其中 * 用來表示函數為 Generator 函數,yield 用來定義函數內部的狀態。
- 基本使用
我們來定義一個示例函數
function* test(){
console.log("one");
yield '1';
console.log("two");
yield '2';
console.log("three");
return '3';
}
let funResult = test()
f.next();
// one
// {value: "1", done: false}
f.next();
// two
// {value: "2", done: false}
f.next();
// three
// {value: "3", done: true}
f.next();
// {value: undefined, done: true}
這段代碼的執行順序:
第一次調用 next 方法時,從 Generator 函數的頭部開始執行,先是打印了 one ,執行到 yield 就停下來,并將yield 后邊表達式的值 '1',作為返回對象的 value 屬性值,此時函數還沒有執行完, 返回對象的 done 屬性值是 false。
第二次調用 next 方法時,同上步 。
第三次調用 next 方法時,先是打印了 three ,然后執行了函數的返回操作,并將 return 后面的表達式的值,作為返回對象的 value 屬性值,此時函數已經結束,多以 done 屬性值為true 。
第四次調用 next 方法時, 此時函數已經執行完了,所以返回 value 屬性值是 undefined ,done 屬性值是 true 。如果執行第三步時,沒有 return 語句的話,就直接返回 {value: undefined, done: true}。
- for...of循環
for...of循環可以自動遍歷 Generator 函數運行時生成的Iterator對象,且此時不再需要調用next方法。
function print(){
console.log('執行了打印')
return "我才是打印"
}
function* sendParameter(){
console.log("start");
var x = yield print();
console.log("one:" + x);
var y = yield '繼續打印';
console.log("two:" + y);
console.log("final:" + (x + y));
}
// 使用 for... of 相當于無參數自動執行該方法
var sendp1 = sendParameter();
for (let v of sendp1) { // v 是迭代對象 也就是yield 后面的數字或字符串或function的返回值
console.log('for...of',v);
}
<!-- 執行的打印結果為:
start
執行了打印
for...of 我才是打印
one:undefined
for...of 繼續打印
two:undefined
final:NaN // undefined 參與了加法計算,所以值成了NaN
-->
- next()傳參
function getData(){
console.log('從網絡get獲取數據')
setTimeout(() =>{sendp2.next({data:"我是get返回的數據"})}, 1000)
}
function postData(){
console.log('從網絡post獲取數據')
setTimeout(() =>{sendp2.next({data:"我是post返回的數據"})}, 1000)
}
function* requestDataFromServer(){
console.log("start");
var x = yield getData();
console.log("get到的數據:",x);
var y = yield postData();
console.log("post到的數據:",y);
}
var sendp2 = requestDataFromServer();
sendp2.next();
<!--打印結果為:
start
從網絡get獲取數據
get到的數據: { data: '我是get返回的數據' }
從網絡post獲取數據
post到的數據: { data: '我是post返回的數據' }
-->
- 打斷Generator函數
結束Generator函數有兩種方式,return 和 throw
return 方法返回給定值:
- 方法提供參數時,返回該參數;
- 不提供參數時,返回 undefined
throw 是通用關鍵字,不單純用在Generator函數,也可以放在普通的方法里做打斷或者驗證的功能
用法如下:
function* foo(){
try{
yield 1;
yield 2;
yield 3;
}catch (e) {
console.log('catch inner', e);
}
}
var f = foo();
f.next();
//返回的值是 {value: 1, done: false}
f.return("foo");
//返回的值 {value: "foo", done: true} , done為true,說明迭代完成
try {
f.throw('a');
f.throw('b');
} catch (e) {
console.log('catch outside', e);
}
<!--
執行結果為:
catch outside a
-->
實際是怎么樣應用的,考慮以下這樣的場景:
打開淘寶登錄后,我們看到的了什么信息?
- 我的個人信息
- 我的訂單信息
- 我關注的商家自動為我推薦的商品
也就是說我們在登錄的時候需要獲取到這三種信息,這三種信息被淘寶封裝成了三個獲取接口,我們以 url1,url2,url3分別代表三個接口地址
登錄方式通過Token的方式,方法命名為 login()
獲取三個信息的接口分別為 getUserInfo(),getOrderInfo(),getGoods()
function getToken(token){
console.log('獲取Token')
setTimeout(() =>{loginVal.next("token122222222")}, 500)
}
function getUserInfo(token){
console.log(`使用${token}獲取用戶信息`)
setTimeout(() =>{loginVal.next({userName:'coding'})}, 500)
}
function getOrderInfo(){
console.log(`使用${token}獲取訂單信息`)
setTimeout(() =>{loginVal.next({orderName:'玩具槍'})}, 500)
}
function getGoods(){
console.log(`使用${token}獲取商品信息`)
setTimeout(() =>{loginVal.next({goodsName:'嬰兒玩具'})}, 500)
}
//為了保證登錄后信息的完整性,需要將三種數據都獲取到,我們會這樣寫
function* login(){
try {
const token = yield getToken();
console.log(token);
const userInfo = yield getUserInfo(token);
const orderInfo = yield getOrderInfo(token);
const goodsInfo = yield getGoods(token);
console.log(userInfo,orderInfo,goodsInfo);
} catch (e) {
// 處理這個過程遇到的錯誤
}
}
var loginVal;
// 點擊按鈕時執行的方法
function submitClick(){
loginVal = login();
loginVal.next();
}
14. async 函數
async 是 ES7 才有的與異步操作有關的關鍵字,和 Promise , Generator 有很大關聯的。
- 基本使用
// 這是一個沒有參數的例子
async function helloAsync(){
return "helloAsync";
}
console.log(helloAsync()) // Promise {<resolved>: "helloAsync"}
helloAsync().then(v=>{
console.log(v); // helloAsync
})
- await
await 操作符用于等待一個 Promise 對象, 它只能在異步函數 async function 內部使用。
下面是一個實現紅綠燈變換的例子
let time = 8
let intervalId
let newColor ='red 紅燈'
function changeColor(lightColor) {
return new Promise(resolve => {
if(intervalId)
clearInterval(intervalId)
intervalId = setInterval(() => {
console.log('倒計時:'+time+'秒')
time -= 1
if(time<1&&lightColor){
if(lightColor.includes('red'))
newColor = "green 綠燈"
else
newColor = "red 紅燈"
resolve(newColor);
}
}, 1000);
});
}
async function changeLightAsync() {
var x = await changeColor(newColor);
console.log(x);
}
changeLightAsync()
setInterval(() => {
time = 8
changeLightAsync();
},9000)
15. Proxy 與 Reflect
- Proxy
一個 Proxy 對象由兩個部分組成: target 、 handler 。
var proxy = new Proxy(target, handler);
Proxy 對象的所有用法,都是上面這種形式,不同的只是handler參數的寫法。
其中,new Proxy()表示生成一個Proxy實例,target參數表示所要攔截的目標對象,handler參數也是一個對象,用來定制攔截行為。
看一個簡單的攔截例子:
var proxy = new Proxy({}, {
get: function(target, property) {
return 666;
}
});
proxy.gender // 666
proxy.name // 666
proxy.age // 666
當然y一個攔截器函數可以有多個攔截操作
let target = {name:'張三',age:18}
var handler = {
get: function(target, name) {
if (name === 'prototype') {
return Object.prototype;
}
return 'Hello, ' + name;
},
apply: function(target, thisBinding, args) {
console.log('執行了apply',thisBinding)
return args[0];
},
construct: function(target, args) {
return {value: args[1]};
}
};
target = function(x, y) {
return x + y;
}
var fproxy = new Proxy(target, handler);
fproxy(1, 2) // 1
new fproxy(1, 2) // {value: 2}
fproxy.prototype === Object.prototype // true
fproxy.foo === "Hello, foo" // true
下面是所有的攔截操作,一共13項:
get(target, propKey, receiver):攔截對象屬性的讀取,比如proxy.foo和proxy['foo']。
set(target, propKey, value, receiver):攔截對象屬性的設置,比如proxy.foo = v或proxy['foo'] = v,返回一個布爾值。
has(target, propKey):攔截propKey in proxy的操作,返回一個布爾值。
deleteProperty(target, propKey):攔截delete proxy[propKey]的操作,返回一個布爾值。
ownKeys(target):攔截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循環,返回一個數組。該方法返回目標對象所有自身的屬性的屬性名,而Object.keys()的返回結果僅包括目標對象自身的可遍歷屬性。
getOwnPropertyDescriptor(target, propKey):攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述對象。
defineProperty(target, propKey, propDesc):攔截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一個布爾值。
preventExtensions(target):攔截Object.preventExtensions(proxy),返回一個布爾值。
getPrototypeOf(target):攔截Object.getPrototypeOf(proxy),返回一個對象。
isExtensible(target):攔截Object.isExtensible(proxy),返回一個布爾值。
setPrototypeOf(target, proto):攔截Object.setPrototypeOf(proxy, proto),返回一個布爾值。如果目標對象是函數,那么還有兩種額外操作可以攔截。
apply(target, object, args):攔截 Proxy 實例作為函數調用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
construct(target, args):攔截 Proxy 實例作為構造函數調用的操作,比如new proxy(...args)。
上面的基本操作我們可以暫時有一個印象,我們基本上代理的都是對象的get,set,deleteProperty,construct方法等
下面我們看一些適用的場景:
- Form表單驗證做成插件;
function createValidator(target, validator) {
return new Proxy(target, {
_validator: validator,
set(target, key, value, proxy) {
if (target.hasOwnProperty(key)) {
let validator = this._validator[key];
if (!!validator(value)) {
return Reflect.set(target, key, value, proxy);
} else {
throw Error(`Cannot set ${key} to ${value}. Invalid.`);
}
} else {
throw Error(`${key} is not a valid property`)
}
}
});
}
const personValidators = {
name(val) {
return typeof val === 'string';
},
age(val) {
return typeof age === 'number' && age > 18;
}
}
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
return createValidator(this, personValidators);
}
}
const bill = new Person('Bill', 25);
// 以下操作都會報錯,我們在賦值時可以快速的用try...catch 捕捉錯誤做出響應
bill.name = 0;
bill.age = 'Bill';
bill.age = 15;
- 私有屬性
JavaScript 或其他語言中,大家會約定俗成地在變量名之前添加下劃線 _ 來表明這是一個私有屬性(并不是真正的私有),但我們無法保證真的沒人會去訪問或修改它。
let api = {
_apiCase1: 'http://182.190.5.88', // 實例1
_apiCase2: 'http://182.190.5.89', // 實例2
/* 測試數據時使用 this._apiCase1 或者 this._apiCase2 */
getUsers: function(){},
getUser: function(userId){},
setUser: function(userId, config){}
};
// 我們定義私有變量的初衷是為了不修改,但是下面依然可以執行
api._apiCase1 = 'http://182.190.5.90';
很顯然,約定俗成是沒有束縛力的,使用 ES6 Proxy 我們就可以實現真實的私有變量了
const privateKes = ['_apiCase1','_apiCase2'];
api = new Proxy(api, {
get(target, key, proxy) {
if(privateKes.indexOf(key) > -1) {
throw Error(`${key} 不存在.`);
}
return Reflect.get(target, key, proxy);
},
set(target, key, value, proxy) {
if(privateKes.indexOf(key) > -1) {
throw Error(`無法為 ${key} 賦值,因為它是私有變量`);
}
return Reflect.get(target, key, value, proxy);
}
});
// 以下操作都會拋出錯誤
console.log(api._apiCase1);
api._apiCase2 = '987654321';
- 預警過時方法和攔截刪除操作
let dataStore = {
noDelete: 1235,
oldMethod: function() {/*...*/ },
doNotChange: "tried and true"
};
const NODELETE = ['noDelete'];
const NOCHANGE = ['doNotChange'];
const DEPRECATED = ['oldMethod'];
dataStore = new Proxy(dataStore, {
set(target, key, value, proxy) {
if (NOCHANGE.includes(key)) {
throw Error(`Error! ${key} 是只讀屬性.`);
}
return Reflect.set(target, key, value, proxy);
},
deleteProperty(target, key) {
if (NODELETE.includes(key)) {
throw Error(`Error! ${key} 是不可刪除屬性`);
}
return Reflect.deleteProperty(target, key);
},
get(target, key, proxy) {
if (DEPRECATED.includes(key)) {
console.warn(`Warning! ${key} 已過時,請謹慎使用.`);
}
var val = target[key];
return typeof val === 'function' ?
function(...args) {
Reflect.apply(target[key], target, args);
} :
val;
}
});
// 這三個都會報錯
dataStore.doNotChange = "foo";
delete dataStore.noDelete;
dataStore.oldMethod();
- Proxy.revocable()
Proxy.revocable 返回一個可取消的Proxy實例
let target2 = {};
let handler2 = {};
let {proxy:newProxy, revoke} = Proxy.revocable(target2, handler2);
newProxy.foo = 123;
newProxy.foo // 123
revoke();
newProxy.foo // Cannot perform 'get' on a proxy that has been revoked
Proxy.revocable的一個使用場景是,目標對象不允許直接訪問,必須通過代理訪問,一旦訪問結束,就收回代理權,不允許再次訪問。
實際開發中有這樣的需求:
比如 登錄時候需要判斷輸入密碼次數,如果輸入錯誤密碼超過5次,密碼輸入框變為不可用
- Reflect
了操作對象而提供的新 API*
Reflect對象其實就是為了取代Object對象。取代原因有一下幾點:
Object對象的一些內部方法放在了Reflect上面,比如:Object.defineProperty。主要是優化了語言內部的方法。
修改Object方法的返回,例如:Object.definePropery(obj,name,desc)無法定義屬性時報錯,而Reflect.definedProperty(obj,name,desc)則會返回false。
讓Object變成函數的行為,以前的:name in obj和delete obj[name],可以讓Reflect.has(name)和Reflect.deleteProperty(obj,name)替代。
Reflect方法和Proxy方法一一對應。主要就是為了實現本體和代理的接口一致性,方便用戶通過代理操作本體。
下面我們寫一個比較復雜的例子,用Proxy和Reflect實現觀察者模式
// 定義一個訂閱者集合
const queuedObservers = new Set();
// 添加訂閱者
const observe = fn => queuedObservers.add(fn);
// 給對象添加代理對象,代理的set方法中進行遍歷訂閱者列表
const observable = obj => new Proxy(obj,{set});
function set(target,key,value,receiver){
const result = Reflect.set(target,key,value,receiver);
//遍歷訂閱者集合,依次觸發訂閱者方法
queuedObservers.forEach(fn => fn());
//返回訂閱者
return result;
}
//使用Proxy實現觀察者模式[發布-訂閱者模式]
const person = observable({name:'張三',age:30});
function print(){
console.log(`${person.name},${person.age}`);
}
function anotherPrint(){
console.log(`你想的很對`)
}
//訂閱者集合里面加入print訂閱者
observe(print);
observe(anotherPrint)
person.name = 'miya'
Reflect對象一共有 13 個靜態方法 與 Proxy 的方法一一對應
Reflect.apply(target, thisArg, args)
Reflect.construct(target, args)
Reflect.get(target, name, receiver)
Reflect.set(target, name, value, receiver)
Reflect.defineProperty(target, name, desc)
Reflect.deleteProperty(target, name)
Reflect.has(target, name)
Reflect.ownKeys(target)
Reflect.isExtensible(target)
Reflect.preventExtensions(target)
Reflect.getOwnPropertyDescriptor(target, name)
Reflect.getPrototypeOf(target)
Reflect.setPrototypeOf(target, prototype)