ES6学习笔记

caowanjing_code

ES6

参考阮一峰的博客https://es6.ruanyifeng.com/#R...

Babel 转码器

将ES6转为ES5.

let命令

let声明变量只在它所在的代码块有效。

for循环就非常适合let的使用。

但是:

for (let i = 0; i < 10; i++) {
}
console.log(i);
// ReferenceError: i is not defined

同一个作用域不能使用let重复声明同一个变量。

不存在变量提升

let放在它被调用之前。

暂时性死区

只要块级作用域内存在let命令,它所声明的变量绑定“bind”这个区域就不再受外部影响。

var tmp = 123;
if (true) {
 tmp = 'abc'; // ReferenceError
 let tmp;
}

全局变量TMP但是块级作用域内let有声明了一个局部变量tmp,导致后绑定了这个作用域,在let声明变量之前,tmp会报错。

不容易发现的死区

function bar(x = y, y = 2) {
 return [x, y];
}
bar();

x=y并没有声明,就错了。

var x = x;
// 报错
let x = x;
// ReferenceError: x is not defined

不允许重复声明

let不允许在同一个作用域内,重复声明一个变量。

// 报错
function func() {
 let a = 10;
 var a = 1;
}
// 报错
function func() {
 let a = 10;
 let a = 1;
}

ES6 Symbol

ES6 引入了一种新的原始数据类型 Symbol ,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。

ES6 数据类型除了 Number 、 String 、 Boolean 、 Object、 null 和 undefined ,还新增了 Symbol 。

Symbol 函数栈不能用 new 命令,因为 Symbol 是原始数据类型,不是对象。

由于每一个 Symbol 的值都是不相等的,所以 Symbol 作为对象的属性名,可以保证属性不重名。

绑定key与value值不同。

Symbol 值作为属性名时,该属性是公有属性不是私有属性,可以在类的外部访问。但是不会出现在 for...in 、 for...of 的循环中,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 返回。如果要读取到一个对象的 Symbol 属性,可以通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 取到。

扩展运算符

两种运算符

1.扩展运算符

扩展运算符用3个点表示(...)用于将一个数组或者类数组对象转化为逗号分隔符的值序列。

拆解数组和字符串。

const array = [1,2,3,4];
console.log(...array);
const str = 'string';
console.log(...str);

拓展运算符代替apply()函数

获取数组最大值时,使用apply()函数

let arr = [1,4,6,8,2];
console.log(Math.max.apply(null,arr))

拓展运算符

let arr = [1,4,6,8,2];
console.log(Math.max(...arr));

拓展运算符代替contact()函数合并数组

console.log([...arr1,...arr2]);

2.rest运算符

rest运算符同样使用...表示,用于将逗号分隔的值序列转化成数组。

结构组合的使用

let arr = ['one','two','three','four'];let[arg1,...arg2] = arr;console.log(arg1);//oneconsole.log(arg2)//[two,three,four]

rest运算符代替arguments处理函数参数

 function foo(...args){      for(let arg of args){        console.log(arg);      }    }    foo('one','two','three','four');

模板字符串

$('#result').append(` There are <b>${basket.count}</b> items in your basket, <em>${basket.onSale}</em> are on sale!`);
let x = 1;let y = 2;`${x} + ${y} = ${x + y}`// "1 + 2 = 3"`${x} + ${y * 2} = ${x + y * 2}`// "1 + 4 = 5"let obj = {x: 1, y: 2};`${obj.x + obj.y}`// "3"

includes(), startsWith(), endsWith()

三个函数都是返回布尔值,表示是否找到了参数字符串

padStart(), padEnd()

padStart()用于1头部补全,padEnd()用于尾部补全。

 console.log('x'.padStart(5,'ax'));//axaxx console.log('x'.padEnd(5,'ba'));//xbaba

字符提示格

console.log('12'.padStart(10, 'YYYY-MM-DD')); // "YYYY-MM-12"console.log('09-12'.padStart(10, 'YYYY-MM-DD'));

trimStart(),trimEnd()

消除头部或者尾部的空格

replaceAll()

字符串代替所有的指定数值

console.log('aabbcc'.replaceAll('b', '_'));// 'aa__cc'

数值新增的方法

Number.isInteger()

判断一个数值是否为整数,boolean值。

25.0和25都是整数,返回true。

Math.trunc()

用于去除一个数的小数部分,返回整数。

对于非数值,它会将非数值转化成数值。

对于空值和无法取证的数值,就返回NaN.

Math.sign()

它来判断一个数到底是正数还是负数还是零。对于非数值,会将其先转换为数值。

正数返回+1;

负数返回-1;

参数为0返回0;

参数-0,返回-0;

其他值返回NaN;

函数参数的默认值

ES6允许函数的参数设置为默认值,直接写在参数定义的后面。

function Point(x=0,y=0){  this.x=x;  this.y=y;}const p=new Point();console.log(p);

解构赋值默认值结合使用

function foo({x, y = 5}) { console.log(x, y);}foo({}) // undefined 5foo({x: 1}) // 1 5foo({x: 1, y: 2}) // 1 2foo() // TypeError: Cannot read property 'x' of undefined

函数的length属性

指定了默认值以后,函数的length属性,将返回没有默认指定默认值的参数个数,指定了默认值以后,length属性将失真。

(function (a) {}).length // 1(function (a = 5) {}).length // 0(function (a, b, c = 5) {}).length // 2

(function (a, b, c = 5) {}).length // 2

本来应该是3个参数,长度为3,但是有一个定义了默认值,所以要减去一个。

作用域

一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域context,等初始化结束,这个作用域就会消失,没有设置参数默认值时就不会出现。

var x=1;function f(x,y=x){  console.log(y);}f(2);

参数 y 的默认值等于变量 x 。调用函数 f 时,参数形成一个单独的作用域。在这个作用域里面,默认值变量 x 指向第一个参数 x ,而不是全局变量 x ,所以输出是 2 。

let x = 1;function f(y = x) { let x = 2; console.log(y);}f() // 1

箭头函数

ES6可以让箭头=>定义函数

var f = () => 5;// 等同于var f = function () {    return 5 };var sum = (num1, num2) => num1 + num2;// 等同于var sum = function(num1, num2) { return num1 + num2;}

如果箭头函数代码部分多于一条语句,就要使用{}把他们括起来。

并且使用return语句返回。

var sum = (num1, num2) => { return num1 + num2; }

因为大括号被解释为代码块,如果箭头函数直接返回一个对象,必修在对象外面加上括号,否则会报错。

// 报错let getTempItem = id => { id: id, name: "Temp" };// 不报错let getTempItem = id => ({ id: id, name: "Temp" });

箭头函数可以与变量解构结合使用。

const full = ({ first, last }) => first + ' ' + last;// 等同于function full(person) { return person.first + ' ' + person.last;}

箭头函数的使用注意点

1.箭头函数没有自己的this对象

2.不可以构造函数,也就是不能使用函数使用new()

3.不可以使用arguments对象,该对象在函数体内不存在,如果必须要用可以用rest参数代替。

4.不可以使用yield命令,箭头函数不能用作Generator函数。

不使用场合

箭头函数从动态变成静态。

globalThis.s = 21;const obj = { s: 42, m: () => console.log(this.s)};obj.m() // 21

需要动态this的时候,也不应该使用箭头函数。

Array.from()

Array.from 方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。

let arrayLike={  '0':'a',  '1':'b',  '2':'c',  length:3}let arr2=Array.from(arrayLike);console.log(arr2)

Array.of()

用于一组值转化为数组

用于弥补Array()的不足,因为参数个数的不同,会导致Array(有

差异。只有参数个数不少于2个时,Array()才会返回由参数组成的新数组。

Array.of()总是返回参数值组成的数组,如果没有参数,就返回一个空数组。

function ArrayOf(){ return [].slice.call(arguments);}

数组实例的fill()

fill方法使用给定值,填充一个数组

['a', 'b', 'c'].fill(7)// [7, 7, 7]

数组实例的flat()

使用 Array.prototype.flat() 用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。

当使用一次flat时只能拉平一个2维数组,如果要拉平3维数组时,就到再使用一次了。

对象的拓展

const name = 'cao teacher';const age = 18;const obj={name,age};//等同于const obj = {  name:'caoteacher',  age:18

定义obj对象时,变量名name作为了对象的属性名,它的值作为了属性值,一次只需要写一个name就可以表示{name:name}的含义。

除了属性可以简写,函数也可以简写,即省略关键字function。

属性遍历

一共有5种方法实现对象属性的变量。

1.for...in

2.Object.getOwnPropertyNames(obj)

3.Object.getOwnPropertySymbols(obj)

4.Object.keys(obj)

5.Reflect.ownKeys(obj)

Object.is()

和ES5很像(==)和(===)

和严格比较运算符(===)行为基本一致

Object.assign()

用于对象的合并,将源对象的所有可枚举属性,复制到目标对象(target)

浅拷贝

Object.assign() 方法实行的是浅拷贝

Symbol

保证每个属性名的名字都是独一无二。

s 就是一个独一无二的值。 typeof 运算符的结果,表明变量 s 是 Symbol 数据类型,而不是字符串之类的其他类型。

这个symbol函数不能使用new命令。

如果symbol参数是一个对象,那么就会调用tostring方法,将其转化为字符串,然后才生成一个symbol值。

Symbol 函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的 Symbol 函数的返回值是不相等的。

作为属性名的symbol

因为每个symbol值都是不相同的,对于对象的属性名,就能够保证不会出现同名的属性,在多个模板够成的情况下,能够防止某一个键被不小心改写或者覆盖。

let mySymbol=Symbol();let a={};a[mySymbol]='hello';console.log(a);
let a={    [mySymbol]:'hello'}console.log(a);
let a={};Object.defineProperty(a,mySymbol,{value:'hello'});console.log(a) ;

代码通过方括号结构和 Object.defineProperty ,将对象的属性名指定为一个 Symbol 值。注意,Symbol 值作为对象属性名时,不能用点运算符。

使用symbol值定义属性时,symbol值必须放在[]方括号里面。

使用Symbol最大的好处是,在switch语句时,也可以保证他的工作,Symbol作为属性名时,该属性还是公开属性,不是私有属性。

const COLOR_RED=Symbol();const COLOR_GREEN=Symbol();function getComplement(color){    switch (color){        case COLOR_GREEN:            return COLOR_GREEN;            case COLOR_RED:                return COLOR_RED;    }}

实例:消除魔术字符串

在代码中多次出现,与代码形成强耦合的某一个具体字符串或者数值,为了代码的健壮性,尽量消除魔术字符串,改为清晰的变量。

function getArea(shape,options){    let area=0;    switch(shape){        case 'tra'://魔术字符串        area=.5*options.width*options.height;        break;    }    return area;}getArea('tra',{switch:100,height:100})//魔术字符串

tra就是一个魔术字符串,他多次出现,与代码形成了强耦合,不利于代码的维护,我们可以把他写成为一个变量。

function getArea(shape, options) { let area = 0; switch (shape) {  case shapeType.triangle:   area = .5 * options.width * options.height;   break;} return area;}getArea(shapeType.triangle, { width: 100, height: 100 })

shapeType.triangle等于哪个值并不重要,只需要确保他不会和其他shapeType重名就好,所以特别适合symbol值。

属性的遍历

Symbool作为属性名时,该属性不会出现在for...in for...of循环中,也不会被object.keys(),object.getOwnPropertyNames()

JSON.stringify()返回

但是他也不是私有属性,他有一个Object.getOwnPropertySymbols()方法,可以获取指定对象的所有Symbol属性名,该方法返回一个数组,成员是当前对象所有作用域属性名的symbol值。

const obj={};let a=Symbol('a');let b=Symbol('b');obj[a]='hello';obj[b]='world';const objectSymbols=Object.getOwnPropertySymbols(obj);console.log(objectSymbols);
const obj = {};const foo = Symbol('foo');obj[foo] = 'bar';for (let i in obj) { console.log(i); // 无输出}console.log(Object.getOwnPropertyNames(obj) ) // []console.log(Object.getOwnPropertySymbols(obj)) ;//[Symbol(foo)]

可以看到,使用for...in循环和Object.getOwnPropertyNames并没有获取到symbol的键名,需要使用Object.getOwnPropertySymbols方法。

另一个是 Reflect.ownKeys() 方法
可以返回所有类型的键名,包括常规键名和 Symbol 键名。

let obj = {    [Symbol('my_key')]: 1,     enum: 2,     nonEnum: 3    };   console.log( Reflect.ownKeys(obj));//["enum", "nonEnum", Symbol(my_key)]

用于symbol值作为键名,不会被常规方法遍历得到,我们可以利用这个特性,为对象定义一些非私有,但又只用于内部的方法

let size = Symbol('size');class Collection { constructor() {  this[size] = 0;} add(item) {  this[this[size]] = item;  this[size]++;} static sizeOf(instance) {  return instance[size];}}let x = new Collection();Collection.sizeOf(x) // 0x.add('foo');Collection.sizeOf(x) // 1Object.keys(x) // ['0']Object.getOwnPropertyNames(x) // ['0']Object.getOwnPropertySymbols(x) // [Symbol(size)

set和Map的数据结构

set()的用法

ES6提供了数据结构set,他类似于数组,但是成员是唯一的,没有重复的值。类数组

const s=new Set();[1,2,2,3,3,4,4,5,4,4,6].forEach(x=>s.add(x));for(let in  s){    console.log(i);}console.log(s)

通过add()方法向set结构中加入成员,结果表明set结果不会添加重复的值,set函数可以接受一个数组,作为参数,用来初始化。

const set = new Set([1, 2, 3, 4, 4]);[...set]// [1, 2, 3, 4]// 例二const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);items.size // 5// 例三const set = new Set(document.querySelectorAll('div'));set.size // 56//类似属const set = new Set();document.querySelectorAll('div').forEach(div => set.add(div));set.size // 56

set()实例的属性和方法

set有以下属性

Set.prototype.constructor :构造函数,默认就是 Set 函数。
Set.prototype.size :返回 Set 实例的成员总数。

四个操作方法

Set.prototype.add(value) :添加某个值,返回 Set 结构本身。
Set.prototype.delete(value) :删除某个值,返回一个布尔值,表示删除是否成功。
Set.prototype.has(value) :返回一个布尔值,表示该值是否为 Set 的成员。
Set.prototype.clear() :清除所有成员,没有返回值

Array.from()方法可以将set结构转化为数组

const items=new Set([7,2,3,4,4,5,4,4,5]);const array=Array.from(items);console.log(array);

与set()提结合供数组去重方法。

set()的常用方法

单一数组的去重,用于set成员具有唯一性,因此可以使用Set来进行数组的去重。

let arr = [1,2,2,3,3,3,4,4,5,5,6,6];console.log(new Set(arr));

多个数组的合并去重

Set可以用于单一数组的合并去重,也可以用做多个数组的合并去重。

let arr1=[1,2,3,3,4,4,5];let arr2=[1,1,2,3,3,4,4,5,5,8];let set1=new Set([...arr1,...arr2]);console.log(set1);

Set与数组的转化

Set()与数组都有便利的数据处理函数,但是两者的相互转换也很简单,我们可以选择对两者进行转化,并调用对应的函数。

Array.from 方法可以将 Set 结构转为数组

const items = new Set([1, 2, 3, 4, 5]);const array = Array.from(items);

set的遍历

forEach()函数的第一个参数表示的是Set中的每个元素,第二个参数表示的元素的索引,第三个就是他的全值

  1. keys():返回键名的遍历器。
  2. values():返回键值的遍历器。
  3. entries():返回键值对的遍历器

    上述函数或者对象都是遍历对象iterator,通过for 循环可以获得每一项都值。

let set = new Set(['red','blue','yellow']);for(let item of set.keys()){  console.log(item);}for(let item of set.values()){  console.log(item);}for(let item of set.entries()){  console.log(item);}

Map的用法

ES6新增加了一种数据结构Map,与传统的对象字面量类似,他的本质还是一种键值对的组合,对于非字符串的值会强制性转化为字符串,而Map的键却可以由各种类型的值组成。

Map 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者原始值) 都可以作为一个键或一个值。

一个Map对象在迭代时会根据对象中元素的插入顺序来进行 — 一个 for...of 循环在每次迭代后会返回一个形式为[key,value]的数组

const data = {};const element = document.getElementById('myDiv');data[element] = 'metadata';data['[object HTMLDivElement]'] // "metadata"

以上代码只是将DOM节点作为对象data的值,但是由于对象只接受字符串作为键名,所以有以element被自动转化为字符串。

const m=new Map();const o={p:'hello world'};m.set(o,'content');console.log(m.get(o)) ;console.log(m.has(o)) ;console.log(m.delete(o)) ;console.log(m.has(o)) ;

展示了如何向Map添加成员,作为构造函数,map也可以接受一个数组作为参数。

Map构造函数接受数组作为参数,实际上是执行以下的算法。

一个Map对象在迭代时会根据对象中元素的插入顺序来进行 — 一个 for...of 循环在每次迭代后会返回一个形式为[key,value]的数组。

const items = [['name', '张三'],['title', 'Author']];const map = new Map();items.forEach(([key, value]) => map.set(key, value));

不仅仅是数组,任何具有lterator的接口,且每一个成员都是一个双元素数组的数据结构都可以当作Map的构造函数的参数。

const set = new Set([['foo', 1],['bar', 2]]);const m1 = new Map(set);m1.get('foo') // 1const m2 = new Map([['baz', 3]]);const m3 = new Map(m2);m3.get('baz') // 3

对一个键进行多次赋值,后者就要会覆盖前者的值。

const map = new Map();map.set(1, 'aaa').set(1, 'bbb');map.get(1) //bbb

如果读取一个未知的键,则返回undefined。

new Map().get('asfddfsasadf')// undefine

set和get()方法一样,表面是针对同一个键,但是实际上是两个不同的数组实例。内存地址也一样。

const map = new Map();const k1 = ['a'];const k2 = ['a'];map.set(k1, 111).set(k2, 222);map.get(k1) // 111map.get(k2) /
map.size

size 属性返回 Map 结构的成员总数

Map.prototype.set(key, value)

set 方法设置键名 key 对应的键值为 value ,然后返回整个 Map 结构。如果 key 已经有值,则键值会被更新,否则就新生成该键。

Map.prototype.get(key)

get 方法读取 key 对应的键值,如果找不到 key ,返回 undefined 。

Map.prototype.has(key)
Map.prototype.delete(key)
Map.prototype.clear()

遍历方法

提供三个遍历器生成函数和一个遍历方法。

Map的遍历顺序就是插入顺序。

const map = new Map([['F', 'no'],['T',  'yes'],]);for (let key of map.keys()) { console.log(key);}for (let value of map.values()) { console.log(value);}// "no"// "yes"for (let [key, value] of map.entries()) { console.log(key, value);}for (let [key, value] of map) { console.log(key, value);}// "F" "no"// "T" "yes"
map[Symbol.iterator] === map.entries

Map转化为数组结构的,快速方法是使用扩展运算符(...)

const map = new Map([[1, 'one'],[2, 'two'],[3, 'three'],]);[...map.keys()]// [1, 2, 3][...map.values()]// ['one', 'two', 'three'][...map.entries()]// [[1,'one'], [2, 'two'], [3, 'three']][...map]// [[1,'one'], [2, 'two'], [3, 'three']]

结合数组的map方法,filter方法,可以实现Map的遍历和过滤

const map0 = new Map().set(1, 'a').set(2, 'b').set(3, 'c');const map1 = new Map([...map0].filter(([k, v]) => k < 3));// 产生 Map 结构 {1 => 'a', 2 => 'b'}const map2 = new Map([...map0].map(([k, v]) => [k * 2, '_' + v]) );// 产生 Map 结构 {2 => '_a', 4 => '_b', 6 => '_c'}

forEach方法还可以接受第二个参数,用来绑定this

Map转化为数组

前面我们试过用...转化

数组转化为Map

将数组传入Map构造函数,就可以转化为Map

new Map([[true, 7],[{foo: 3}, ['abc']]])

Map转化为对象

如果所有Map的键值是字符串,那么他可以完好无损的转化为对象

function strMapToObject(strMap){    let obj=Object.create(null);    for(let [k,v]of strMap){        obj[k]=v;    }    return obj;}const myMap=new Map().set('yes',true).set('no',false);console.log(strMapToObject(myMap)) ;

如果有非字符穿的键名,那么这个键值会被转化成字符串。

对象转化为Map

对象转化为Map可以通过object.entries()

MAP转化为JSON

JSON.stringify

JSON 转为 Map

JSON.parse

Proxy

代理器

主要用于改变对象的默认访问行为,实际表现是在访问对象之前增加一层拦截,任何对象的访问行为都会通过这层拦截,在拦截中,我们可以增加自定义行为。

const proxy = new Proxy(target,handler);

构造函数,接受2个参数,一个目标对象target,另一个配置对象是handler,用来定义拦截行为。

const person={    name:'cao',    age:18,    sex:'woman'};let handler={    get:function(target,prop,receiver){        console.log('你访问了person属性');        return target[prop];    }}const p=new Proxy(person,handler);console.log(p.name);console.log(p.age);
你访问了person属性es6.js:481 caoes6.js:476 你访问了person属性es6.js:482 18

必须通过代理实例访问

配置对象不能为空对象

Proxy实例函数以及其基本使用

  1. get(target,propKey,receiver)
  2. set(target,propKey,value,receiver)
  3. has(target.propKey)
  4. ........

读取不存在的属性

正常情况下当读取一个对象不存在的属性时,会返回undefined,但是通过Proxy()的get()函数可以设置读取不存在的属性时抛出异常,从而避免对undefined的兼容处理

let person={    name:'cao'}const proxy=new Proxy(person,{    get:function (target,propKey){        if(propKey in target){            return target[propKey];        }else{            throw new ReferenceError(`访问的属性${propKey}不存在`);        }    }});console.log(proxy.name);console.log(proxy.age)

读取读索引的值

通过proxy()增加索引的查找速度

target[target.length+index];

禁止访问私有属性

私有属性会以_开头,因为我们并不想访问到他的属性,同样可以设置Proxy.get()函数;来实现

const person = {      name:'cao teacher',      _pwd:'123456'    }    const proxy = new Proxy(person,{      get:function(target,prop){        if(prop.indexOf('_') ===0){          throw new ReferenceError('不能直接访问私有属性');       }else{          return target[prop];       }     }    });    console.log(proxy.name);    console.log(proxy._pwd)

实现真正的私有

通过proxy处理下滑线来真正实现私有

不能访问,修改,不能遍历私有值,遍历出来的属性中也不会包含私有属性。

get:function(target,prop){    if(prop[0] === '_'){      return undefined;

增加日志记录

针对那些缓慢或者资源密集型的接口,我们可以先使用Proxy进行拦截,通过get()函数拦截调用的函数名,如何用apply()函数进行函数调用。

const apis = {  _apiKey:'12ab34cd56ef',  getAllUsers:function(){    console.log('这是查询全部用户的函数'); },  getUserById:function(userId){    console.log('这是根据用户ID查询用户的函数'); },  saveUser:function(user){    console.log('这是保存用户的函数'); }};//记录日志的方法function recordLog(){  console.log('这是记录日志的函数');}const proxy = new Proxy(apis,{  get:function(target,prop){    const value = target[prop];    return function(...args){      //此处调用记录日志的函数      recordLog();      //调用真实的函数      return value.apply(null,args);   } }});proxy.getAllUsers();

Reflect

Reflect 对象与Proxy对象一样,也是 ES6为了操作对象而提供的新 API。

名为Reftect 的全局对象,上面挂载了对象的某些特殊函数,这些函数可以通过类似于 Reflect.apply()这种形式来调用,所有在Reflecl对象上的函数要么可以在Object原型链中找到,要么可以通过命令式操作符实现,

Reflect静态函数

Reflect.apply(target, thisArgument, argumentsList)

和 Function.prototype.apply()功能类似。

Reflect.construct(target, argumentsList[, newTarget])

..............

Reflect.has(obj, name)

Reflect.has 方法对应 name in obj 里面的 in 运算符。

Reflect.apply(func, thisArg, args)

类似于 Function.prototype.apply.call(func, thisArg, args) ,用于绑定this 对象后执行给定函数。

使用Proxy实现观察者模式

实现自动观察数据对象

一旦有变化函数就会自动执行

const person = observable({ name: '张三', age: 20});function print() { console.log(`${person.name}, ${person.age}`)}observe(print);person.name = '李四'

Promise

处理Ajax请求代码

以前因为耦合太严重,执行多个异步请求,每一个请求又需要依赖上一个请求的结果,按照回调函数。

//第一个请求$.ajax({  url:'url1',  success:function(){    //第二个请求    $.ajax({      url:'url2',      success:function(){        //第三个请求        $.ajax({          url:'url3',          success:function(){            //第四个请求            $.ajax({              url:'url4',              success:function(){                //成功的回调             }           })         }       })     }   }) }})

因为行为产生的异步请求,导致代码嵌套太深,引发“回调地狱”

导致问题:

1.代码臃肿,可读性差

2.耦合度高,可维护性差,难以复用。

3.回调函数都是匿名函数,不方便调试。

为了解决此问题,我们使用promise

promise中有三种状态,即Pending,fulfill和reject

promise执行成功,pending状态改变为fulfilled状态,promise执行失败时,pending变成rejected状态。

promise对象本身就是一个构造函数,可以通过new操作符生成promise实例。

const promise = new Promise((resolve,reject)=>{  //异步处理请求  if(/异步请求标识/){    resolve(); }else{    reject(); }})

promise执行过程中,在接受函数时处理异步请求,然后判断异步请求的结果,如果返回true,则表示异步请求成功,调用resolve()函数,一旦执行,就从pending变成fulling,如果是false,则表示异步请求失败,调用reject()函数,reject函数一旦执行,promise就会从pending变为reject。

let promise=new Promise(function(resolve,reject){  console.log('Promise');  resolve();});promise.then(function(){  console.log('resolve');});console.log('hello');

打印结果如下:

Promisees6.js:610 helloes6.js:608 resolve

先执行hello再执行resolve

如果把resolve()改成reject,你们promise.then会返回undefined,只会输出Promise, hello

也就是所有同步代码执行完毕后,会执行then()函数,输出resolve.

function ajaxGetPromise(url){  const promise = new Promise(function(resolve,reject){    const handler = function(){      if(this.readyState !== 4){        return;     }      //当状态码为200时,表示请求成功,执行resolve()函数      if(this.status === 200){        //将请求的响应体作为参数,传递给resolve()函数        resolve(this.response);     }else{        //当状态码不为200时,表示请求失败,reject()函数        reject(new Error(this.statusText));     }   }    //原生ajax操作    const client = new XMLHttpRequest();    client.open("GET",url);    client.onreadystatechange = handler;    client.responseType="json";    client.setReqestHeader("Accept","application/json");    client.send(); });  return promise;

then()和catch()

处理成功或者失败的异步处理

then()表示在promise实例状态改变时执行的回调函数。

他有2个参数,第一个在promise执行成功后,函数参数通过resolve()函数传递的参数,第二个参数是可选的,表示promise在执行失败后,执行的回调函数。

then()函数返回的是一个新的promise实例,因此可以使用链式调用then()函数,在上一轮then()函数内部return值会作为新一轮的then()函数接受的参数值。

const promise=new Promise((resolve,reject)=>{  resolve(1);});promise.then((result)=>{  console.log(result);  return 2;}).then((result)=>{  console.log(result);  return 3;}).then((result)=>{  console.log(result);  return 4;}).then((result)=>{  console.log(result);

catch()函数

catch()函数与then()函数成对存在,then()函数在promise执行成功后回调,而catch()函数是promise执行失败后回调。

const promise=new Promise((resolve,reject)=>{  try{    throw new Error('err11111');  }catch(err){    reject(err);  }});promise.catch((err)=>{  console.log(err);})

只要promise执行过程中出现了异常,就会自动抛出,并触发reject(err),而不要我们去使用try...catch,在catch()函数中手动调用reject()函数。

也可以直接改写为如下所示:

const promise=new Promise((resolve,reject)=>{ throw new Error('err1111');});promise.catch((err)=>{  console.log(err);})

promise.race()

利用多个Promise实例,包装成为一个新的实例。

const p = Promise.race([p1, p2, p3]);

只要p1,p2,p3中有一个实例率先改变状态,p就会跟着改变,那个率先改变promise实例的返回值,就传递给p的回调函数。

如果没有在指定时间获得结果,就将promise的状态变为reject,否则就是resolve。

const p=Promise.race([  fetch(''),  new Promise(function (resolve,reject){    setTimeout(() => {      reject(new Error('request timeout'))          }, 5000);  })]);p.then(console.log);p.catch(console.error);

如果在指定时间没有获得结果,就会将promise的状态变成reject。

promise.reject()

Promise.reject(reason) 方法也会返回一个新的Promise 实例,该实例的状态为 rejected 。

const p=Promise.reject('出错了');p.then(null,function(s){  console.log(s);});

生成一个promise对象实例,状态为rejected,回调函数会立即执行。

lterator和for循环

集合的概念,主要是数组和对象object,ES6又添加的Map和Set。

他就是一种遍历起器,一种接口,为不同的数据结构提供了统一的访问机制,任何数据只要部署了lterator接口,他就可以完成遍历操作。

作用:

1.为各种数据提供统一,简便的访问接口

2.使得数据结构的成员能够按照某种次序排列

3.ES6创造了一种新的遍历命令,for...of循环。lterator接口主要提供for...of消费。

遍历过程如下:

1.创建一个指针,指向当前数据结构的起始位置。

2.第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。

3.第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。

4.不断调用指针的next方法,直到他指向数据结构的结束位子。

默认的iterator接口

为了所有的数据结构,提供了一种统一的访问机制,即for...of循环,当使用for...of循环遍历某种数据结构时,该循环就会自动去寻找lterator接口。

Symbol.iterator属性他本身就是一个函数,当前数据结构默认的遍历器生成函数,执行这个函数就会返回一个遍历器。类型为Symbol的特殊值,所以要放在方括号里面。

const obj={  [Symbol.iterator]:function(){    return {      next:function(){        return {          value:1,          done:true        }      }    }  }}

原生iterator接口的数据结构有6种

1.Array

2.Map

3.Set

4.String

5.函数的arguments对象

6.nodeList对象

let arr = ['a', 'b', 'c'];let iter = arr[Symbol.iterator]();console.log(iter.next())  // { value: 'a', done: false }console.log(iter.next())  // { value: 'b', done: false }console.log(iter.next())  // { value: 'c', done: false }console.log(iter.next())  // { value: undefined, done: true }

使用[Symbol.iterator]遍历循环['a', 'b', 'c']

for...of循环

只要部署了Symbol.iterator属性,就被视作为iterator接口,就可以用for...of循环他的成员。

数组

原生数组具备了iterator接口,for...of循环本质上就是调用这个接口产生的遍历器。

const arr=['red','green','blue'];const obj={};obj[Symbol.iterator]=arr[Symbol.iterator].bind(arr);for(let v of obj){  console.log(v);}

输出结果:red
es6.js:680 green
es6.js:680 blue

Symbol.iterator 属性,结果 obj 的 for...of 循环,产生了与 arr 完全一样的结果。

set和map结构

他们也具有iterator接口,可以直接使用for...of循环

let map=new Map().set('a',1).set('b',2);for (let pair of map){  console.log(pair);}for(let [key,value]of map){  console.log(key +':'+value);}

类似数组的对象

包括DOM NodeList对象,arguments对象。

let str="hello";for(let s of str){  console.log(s);}function printArgs(){  for (let x of arguments){    console.log(x);  }}printArgs('a','b');

Generator函数

它是ES6提供的一种异步编程解决的方案,语法与传统函数不同。

可以把他当成一个状态机。里面封装了多个内部函数状态。

执行Genertaor函数会返回一个遍历对象,除了状态机,他还是一个遍历对象生成函数,返回遍历器对象,可以依次遍历Generator函数中的每一个状态。

function关键字与函数名之间有一个*号,函数内部使用yield表达式。

function* helloGenerator(){  yield 'hello';  yield 'world';  return 'ending';}var hw=helloGenerator();console.log(hw);

Generator函数调用后,该函数并不执行,返回的也不是函数运行的结果,而是一个指向内部状态的指针对象。

下一步调用遍历器对象中的next()方法,使得指针移向下一个状态,每次调用next方法,内部指针就从函数头部或者上一次停下来的地方开始执行,一直遇到下一个yield表达式(或者为return)Generator函数是分段执行的,yield表达式是暂停执行的标记。next方法可以恢复执行。

yield

yield表达式后面的表达式,只有调用next方法,指针指向该语句时才会执行。

如果Generator函数没有使用yield表达式,这时候就变成了一个单纯的暂缓执行函数。

function*f(){  console.log('xxxxx')}var generator=f();setTimeout(() => {  generator.next()},  5000);

没有yield返回时,变成了一个暂缓执行函数。

异步操作同步化表达

异步操作放在yield表达式下面,要等到next方法再执行,Generator函数的一个实际意义就是来处理异步操作,改写回调函数

先把JSON反序列化。再请求。

function*main(){  var result=yield request("http:");  var resp=JSON.parse(result);  console.log(resp.value);}function request(url){  makeAjaxCall(url,function(response){    it.next(response);  });}var it =main();it.next();

通过 Generator 函数部署 Ajax 操作,可以用同步的方式表达。

class

引入class类作为对象的模板,通过class关键字,可以定义类。

class Point { constructor(x, y) {  this.x = x;  this.y = y;} toString() {  return '(' + this.x + ', ' + this.y + ')';}}

里面的constructor()是构造方法,而this关键字就是代表实例对象。

constructor()方法是类的默认方法,通过new生成对象实例的时,自动调用该方法,一个类中必须有constructor()方法。如果没有,那就会被默认添加。

必须要使用new。

如果忘记加上new就会报错。

实例中的非显示调用在其本身,否则都是定义在class上。

阅读 450
1 声望
0 粉丝
0 条评论
1 声望
0 粉丝
文章目录
宣传栏