- 学习序
- 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; // 正确定义
}
}
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) => { 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);
}
- 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();
- 打断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);
}
实际是怎么样应用的,考虑以下这样的场景:
打开淘宝登录后,我们看到的了什么信息?
- 我的个人信息
- 我的订单信息
- 我关注的商家自动为我推荐的商品
也就是说我们在登录的时候需要获取到这三种信息,这三种信息被淘宝封装成了三个获取接口,我们以 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 {: "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)