纯牛奶645
纯牛奶645
  • 发布:2018-02-12 18:10
  • 更新:2018-02-12 18:10
  • 阅读:3764

es6(二)

分类:Native.js

ES6(第三版)
2.let和const命令

1.let命令
2.块级作用域
3.const命令
4.顶层对象的属性
5.global对象

let只在let所在的代码块内有效.
for循环很适合使用let命令.
for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域.
没有变量提升,let所声明的变量一定要在声明后使用,否则报错.
暂时性死区
let不允许在相同作用域内,重复声明同一个变量.
块级作用域的数显,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了.
函数声明语句的行为类似于let,在块级作用域之外不可引用.
考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数.如果确实需要,也应该写成函数表示式,而不是
函数声明语句.
//函数表达式
{
let a = 'secret';
let f = function (){
return a;
};
}
//块级作用域写法
{
let tmp = ...;
...
}

const
const声明一个只读的常量,一旦声明,常量的值就不能改变.
将一个对象声明为常量必须非常小心.
冻结,除了将对象本身冻结,对象的属性也应该冻结.下面是一个将对象彻底冻结的函数
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach((key,i) => {
if (typeof obj[Key] === 'object') {
constantize(obj[Key]);
}
})
};
ES6声明变量的6种方法
1.var 2.function 3.let 4.const 5.import 6.class

4.顶层对象的属性
顶层对象,在浏览器环境指的是window对象,在Node指的是global对象.ES5中,顶层对象的属性与全局变量是等价的.
顶层对象的属性与全局变量挂钩,被认为是 JavaScript 语言最大的设计败笔之一。这样的设计带来了几个很大的
问题,首先是没法在编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创
造的,而属性的创造是动态的);其次,程序员很容易不知不觉地就创建了全局变量(比如打字出错);最后,顶层
对象的属性是到处可以读写的,这非常不利于模块化编程。另一方面,window对象有实体含义,指的是浏览器的窗
口对象,顶层对象是一个有实体含义的对象,也是不合适的。
ES6 为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;
另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,
全局变量将逐步与顶层对象的属性脱钩。

5.global对象
浏览器里面,顶层对象是window,但Node和web worker没有window;
浏览器和web worker里面,self也指向顶层对象,但是node没有self;
node里面,顶层对象是global,但其他环境都不支持;
同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this变量,但是有局限性。

全局环境中,this会返回顶层对象。但是,Node 模块和 ES6 模块中,this返回的是当前模块。
函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined。
不管是严格模式,还是普通模式,new Function('return this')(),总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全政策),那么eval、new Function这些方法都可能无法使用。
综上所述,很难找到一种方法,可以在所有情况下,都取到顶层对象。下面是两种勉强可以使用的方法。

//方法一 (typeof window !== 'undefined' ? window (typeof process === 'object' && typeof require === 'function' &&
typeof global === 'object')? global this);

//方法二
var getGlobal = function () {
if (typeof self !=== 'undefined') {return self;}
if (typeof window !== 'undefined') {return window;}
if (typeof global !== 'undefined') {return global;}
throw new Error('unable to locate global object');
}
现在有一个提案,在语言标准的层面,引入global作为顶层对象。也就是说,在所有环境下,global都是存在的,都可以从它拿到顶层对象。
垫片库system.global模拟了这个提案,可以在所有环境拿到global。

3.变量和解构赋值

1.数组的解构赋值
2.对象的解构赋值
3.字符串的结构赋值
4.数值和布尔值的解构赋值
5.函数参数的解构赋值
6.圆括号的问题
7.用途

1.数组的解构赋值
ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring).
本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。下面是一些使用嵌套数组进行解构的例子。
let [a,b,c] = [1,2,3];
如果解构不成功,变量的值就等于undefined。
另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。
// 报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};
上面的语句就都会报错,因为等号右边的值,要么转为对象以后不具备Iterator接口(前五个表达式),要么本身就不具备 Iterator 接口(最后一个表达式)。
1.概念
第一次看到这个概念是当年学C++的时候,STL库中的迭代器。在es6中,Iterator也差不多是这个意思。
在es6中,能表示“集合”概念的数据类型大致有四种。
 Array,Object,Map,Set
既然是集合,那遍历便是一种基本需求。而Iterator就是为了提供一种统一的接口机制。任何的数据结构,
只要部署了Iterator接口,便可以使用类似的方式完成遍历操作。
https://www.cnblogs.com/toulon/p/6403075.html
对于 Set 结构,也可以使用数组的解构赋值。
let [x, y, z] = new Set(['a', 'b', 'c']);
x // "a"
事实上,只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。
解构赋值允许指定默认值。
注意,ES6 内部使用严格相等运算符(===),判断一个位置是否有值。
所以,只有当一个数组成员严格等于undefined,默认值才会生效。
let [x = 1] = [undefined];
x // 1

let [x = 1] = [null];
x // null
上面代码中,如果一个数组成员是null,默认值就不会生效,因为null不严格等于undefined。
如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。
function f() {
console.log('aaa');
}
let [x = f()] = [1];
上面代码中,因为x能取到值,所以函数f根本不会执行。上面的代码其实等价于下面的代码。
let x;
if ([1][0] === undefined) {
x = f();
} else{
x = [1][0];
}
//[1][0]表示数组[1]中的第0个元素[0]

2.对象的解构赋值
解构不仅可以用于数组,还可以用于对象。
let { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;
而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
let { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"

let { baz } = { foo: "aaa", bar: "bbb" };
baz // undefined
也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
3.字符串的解构赋值
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。
let {length : len} = 'hello';
len // 5
4.数值和布尔值的解构赋值
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
Number.prototype.toString 是什么意思?
let {toString: s} = 123;
s === Number.prototype.toString // true

let {toString: s} = true;
s === Boolean.prototype.toString // true
上面代码中,数值和布尔值的包装对象都有toString属性,因此变量s都能取到值。

解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。
let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError
5.函数参数的解构赋值
函数的参数也可以使用解构赋值。
function add([x,y]){
return x + y;
}
add([1,2]);
函数add的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量x和y.对于函数内部的代码
来说,他们能感受到的参数就是x和y.
[[1,2],[3,4]].map(([a,b]) => a + b);

map函数需要了解一下
map函数需要了解一下?
Number.prototype.toString 是什么意思?
Array.from() ?????
new Proxy()生成实例,new的底层是什么操作,在函数那一章节????
Object.create(proto) ???????create???
try catch ???

6.圆括号问题
如果模式中出现圆括号怎么处理,es6的规则是,只要有可能导致解构的歧义,就不得使用圆括号.

7.用途
变量的解构赋值用途很多。
(1)交换变量的值
let x = 1;
let y = 2;

[x, y] = [y, x];
上面代码交换变量x和y的值,这样的写法不仅简洁,而且易读,语义非常清晰。
(2)从函数返回多个值
函数只能返回一个值,如果要返回多个值,只能将他们放在数组或对象里返回.有了解构赋值,取出这个值就非常方便.
function example() {
return [1,2,3];
}
let [a,b,c] = example();

function example() {
return {
foo: 1,
bar: 2
};
}
let {foo,bar} = example();

6.遍历map结构
const map = new Map();
map.set('first','hello');
map.set('second','world');
for(let [key,value] of map) {
console.log(key + "is" + value);
}

字符串的扩展
1.字符串的Unicode表示法
2.codePointAt()
3.String.fromCodePoint()
4.字符串的遍历器接口
5.at()
6.normalize()
7.includes(),startsWith(),endsWith()
8.repeat();
9.padStart() ,padEnd()
10.matchAll()
11.模板字符串
12.实例:模板编译
13.标签模板
14.String.raw()
15.模板字符串的限制

ES6加强了对Unicode的支持,并且扩展了字符串对象.
\uxxxx中\u代表是unicode的表示法
xxxx表示字符的Unicode码点.

正则的扩展
1.RegExp 构造函数
2.字符串的正则方法
3.u 修饰符
4.y 修饰符
5.sticky 属性
6.flags 属性
7.s 修饰符:dotAll 模式
8.后行断言
9.Unicode 属性类
10.具名组匹配
11.String.prototype.matchAll

var regex = new RegExp('xyz','i');
//等价于
var regex = /xyz/i;
new RegExp(/abc/ig, 'i').flags
// "i"
flags属性为ES6为正则表达式新增了flags属性,会返回正则表达式的修饰符.
/abc/ig.source //返回正则表达式的正文 "abc"
/abc/ig.flags //返回正则表达式的修饰符 "gi"

set和map数据结构
javascript的默认对象表示方式{}可以视为其他语言中的Map和Dictionary的数据结构,即一组键值对.
但是JavaScript的对象有个小问题,就是健必须是字符串.但实际上Number或者其他数据类型作为健也是非常合理的.
为了解决这个问题,最新的ES6规范引入了新的数据类型Map.
var m = new Map();
var s = new Set();
Map 是一组键值对的结构,具有极快的查找速度.
var names = ["a","b","c"];
var sources = [95,75,85];
用Map实现
var m = new Map([['a',95],['b',75],['c',89]]);
m.get('a');//95
var n = new Map();
n.set('admin',89);
n.set('bob',90);
n.has('admin');

Set
set和map类似,也是一组key的集合,但不存储value.由于key不能重复,所以,在set中,没有重复的key
要创建一个set,需要提供一个array作为输入,或者直接创建一个空set
var s1 = new Set();
var s2 = new Set([1,2,3]);
重复元素在set中自动被过滤:
set本身是一个构造函数,用来生成Set数据结构.
const s = new Set();
[2,3,2,4,5,3,7].forEach(x => s.add(x));
for (let i of s) {
console.log(i);
}
通过add方法向Set加入成员.
另外,两个对象总是不相等的.
let set = new Set();
set.add({});
set.size //1
set.add({});
set.size //2
set实例的属性和方法
set结构的实例有以下属性
Set.prototype.constructor 构造函数,默认就是Set函数
Set.prototype.size 返回Set实例的成员总数.
Set实例的方法分为两大类:操作方法(用于操作数据)和便利方法(用于便利成员).
四个操作方法:
add
delete
has
clear 清除所有成员
看看在判断是否包括一个健上面,Object结构和Set结构的写法不同.
//对象的写法
const properties = {
'width': 1,
'height': 1
};
if (properties[someName]) {

}

//set的写法
const properties = new Set();
properties.add('width');
properties.add('height');
if(properties.has(someName)){

}

Array.from()方法可以将Set结构转为数组.
const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);
这就提供了去除数组重复成员的另一种方法.
function dedupe(array) {
return Array.from(new Set(array));
}
dedupe([1,1,2,3]);

8,数组的扩展
function f(x,y,z) {

}
var args = [0,1,2];
f.apply(null,args);

function f(x,y,z) {

}
let args = [0,1,2];
f(...args);

Math.max.apply(null,[14,3,77]);
Math.max(...[14,3,77]);
Math.max(14,3,77);
var arr1 = [0,1,2];
var arr2 = [3,4,5];
Array.prototype.push.apply(arr1,arr2);
arr1.push(...arr2);

new (Date.bind.apply(Date,[null,2015,1,1]))
new Date(...[2015,1,1]);

克隆
const a1 = [1,2];
const a2 = a1.concat(); //a1会返回原数组的克隆
a2[0] = 2;
a1; //[1,2]

const a1 = [1,2];
const a2 = [...a1];
const [...a2] = a1;
上面两种写法a2都是a1的克隆.
(3)与解构赋值结合

Array.from()
Array.from()方法用于将两类对象转为真正的数组:
类似数组的对象(array-like-object) 和 可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length:3
}
var arr1 = [].slice.call(arrayLike);
var arr2 = Array.from(arrayLike);

let ps = document.querySelectorAll('p');
Array.from(ps).filter(p => {
return p.textContent.length > 100;
});

function foo() {
var args = Array.from(arguments);
}
如果参数是一个真正的数组,Array.from会返回一个一模一样的新数组.
扩展运算符(...)也可以将某些数据结构转为数组
const args = [...arguments];
[...document.querySelectorAll('div')];
所谓类似数组的对象,本质特征只有一点,即必须有length属性。因此,
任何有length属性的对象,都可以通过Array.from方法转为数组,而此时扩展运算符就无法转换。
Array.from({ length: 3 });
// [ undefined, undefined, undefined ]
Array.of方法用于将一组值,转换为数组。????与push有区别吗?
Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]
上面代码中,Array方法没有参数、一个参数、三个参数时,返回结果都不一样。只有当参数个数不
少于 2 个时,Array()才会返回由参数组成的新数组。参数个数只有一个时,实际上是指定数组的长度。
Array.of总是返回参数值组成的数组。如果没有参数,就返回一个空数组。
Array.of方法可以用下面的代码模拟实现。
function ArrayOf() {
return [].slice.call(arguments);
}
数组实例的copyWithin()
数组实例的copyWithin方法,在当前数组内部,将指定位置的成员复制到其他位置
(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。
Array.prototype.copyWithin(target, start = 0, end = this.length)
它接受三个参数:
-target(必须):从该位置开始替换数据。如果为负值,表示倒数。
-start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示倒数。
-end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。
[1,2,3,4,5].copyWithin(0,3) //[4,5,3,4,5]
//将3号位复制到0号位
[1,2,3,4,5].copyWithin(0,3,4) //[4,2,3,4,5]
数组的实例 entries(),keys()和values()用于遍历数组,区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。

上面代码表示将从3号位置到数组结束的成员(4和5),复制到从0号位开始的位置,结果覆盖了原来的1和2.
copyWithin用来复制复制复制复制复制

遍历操作
Set结构的实例有四个遍历方法,可以用于遍历成员.
keys 返回键名的遍历器
values 返回健值的遍历器
entries 返回健值对的遍历器
forEach 使用回调函数遍历每个成员
let set = new Set(['red','green','blue']);
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);
}
Set结构的实例默认和遍历,他的默认遍历器生成函数就是它的values方法
Set.prototype[Symbol.iterator] === Set.prototype.values //true
这意味着,可以省略values方法,直接用for...of循环遍历set

let set = new Set(['red', 'green', 'blue']);

for (let x of set) {
console.log(x);
}
// red
// green
// blue
(2)forEach()
遍历的应用
扩展运算符(...)内部使用for...of循环,所以也可以用于 Set 结构。
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)];
// [3, 5, 2]
数组的map和fillter方法也可以间接用于set了
let set = new Set([1,2,3]);
set = new Set([...set].map(x => x * 2));//{2,4,6}
.map方法返回一个新数组,数组中的元素为原始调用函数处理后的值,按照原始数组顺序依次处理元素.map不会对空
数组进行检测.map()不会改变原始数组.
let set = new Set([1,2,3,4,5]);
set = new Set([...set].filter(x => (x % 2) == 0));
因此使用Set可以很容易地实现并集(union),交集(intersect)和差集(Difference).
let ab = new Set([1,2,3]);
let ba = new Set([4,3,2]);
并集
let union = new Set([...a,...b]);
交集
let intersect = new Set([...ab].filter(x => ba.has(x))); //2
差集
let differences = new Set([...ab].filter(x => !ba.has(x)));

3.Map含义和基本用法
js的对象(Object),本质上是键值对的集合(Hash结构),但传统上只能用字符串当做健,这个他的使用带来了很大的限制.
const m = new Map();
const o = {p: 'Hello World'};
m.set(o,'content');
m.get(o);
m.has(o) //true
m.delete(o) //true
m.has(o) //false

const map = new Map([
['name','张三'],
['title','Author']
]);
map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"

Map构造函数接受数组作为参数,实际上执行的是下面的算法。
const items = [
['name','张三'],
['title','Author']
];

const map = new Map();
items.forEach(
([key,value]) => map.set(key,value)
);
事实上,不仅仅是数组,任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构
(详见《Iterator》一章)都可以当作Map构造函数的参数。这就是说,Set和Map都可以用来生成新的 Map。

Proxy()代理
proxy可以理解成,在目标对象之前架设一层'拦截',外界对该对象的访问,都必须先通过这层拦截,因此提供了
一种机制,可以对外界的访问进行过滤和改写 Proxy这个词的愿意是代理,用在这里表示由他来'代理'这些操作,
可以译为"代理器".
var obj = new Proxy({},{
get: function (target,key,receiver) {
console.log(getting ${key}!);
return Reflect.get(target,key,receiver);
},
set: function (target,key,value,receiver) {
console.log(setting ${key}!);
return Reflect.set(target,key,value,receiver);
}
});
上面代码对一个空对象架设了一层拦截,重定义了属性的读取(get)和设置(set)行为.
obj.count = 1
// setting count!
++obj.count
// getting count!
// setting count!
// 2
上面代码说明,Proxy 实际上重载(overload)了点运算符,即用自己的定义覆盖了语言的原始定义。
es6原生proxy构造函数,用来生成Proxy实例.
var proxy = new Proxy(target,handler);
proxy对象的所有用法,都是上面这种形式,不同的只是handler参数的写法.其中,new Proxy()表示生成一个proxy实例,

var proxy = new Proxy({},{
get: function(target,property) {
console.log(target); //{}
console.log(property); //time
return 35;
}
});
proxy.time //35
proxy.name //35
proxy.title //35
上例子中,作为构造函数,Proxy接受两个参数,第一个参数是所要代理的目标对象(上例是一个空对象),
即如果没有Proxy的介入,操作原来要访问的就是这个对象;第二个参数是一个配置对象,对于么一个被代理的操作,
需要提供一个对应的处理函数,该函数将拦截对应的操作.比如,上面的代码中,配置对象有一个get方法,用来拦截对
目标对象属性的 访问 请求.get方法的两个参数分别是目标对象和所要访问的属性.可以看到,由于拦截函数总是
返回35,所以访问任何属性都得到35

var target = {};
var handler = {};
var proxy = new Proxy(target,handler);
proxy.a = 'b';
target.a //"b"
上面代码中,handler是一个空对象,没有任何拦截效果,访问proxy就等同于访问target.
一个技巧是将Proxy对象,设置到object.proxy属性,从而可以在object对象上调用
var object = {proxy: new Proxy(target,handler)};

Proxy()实例的方法
var person = {
name: "张三"
};
var proxy = new Proxy(person, {
get: function (target,property) {
if (property in target) {
return target[property];
}else {
throw new ReferenceError("Property \"" + property + "\" does not exist.");
}
}
});
proxy.name //"张三"
proxy.age //抛出一个错误
上面代码表示,如果访问目标对象不存在的属性,会抛出一个错误。
如果没有这个拦截函数,访问不存在的属性,只会返回undefined。
let proto = new Proxy({},{
get(target,propertyKey,receiver) {
console.log('GET' + propertyKey);
return target[propertyKey];
}
});
let obj = Object.create(proto);
obj.foo //"GET foo"

Reflect
Reflect对象与Proxy对象一样,也是ES6为了操作对象而提供的新的API.Reflect对象的设计目的有这样几个.
(1) 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。
现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,
从Reflect对象上可以拿到语言内部的方法。
(2) 修改某些Object方法的返回结果,让其变得更合理。比如,
Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,
而Reflect.defineProperty(obj, name, desc)则会返回false。
//老写法
try{
Object.defineProperty(target,property,attributes);
}catch(e){
//TODO handle the exception
}
//新写法
if (Reflect.defineProperty(target,property,attributes)) {
//success
} else{
//failure
}
(3)让Object操作都变成函数行为,某些Object
(3) 让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],
而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。
// 老写法
'assign' in Object // true

// 新写法
Reflect.has(Object, 'assign') // true
(4)Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,
就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,
完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。
Proxy(target, {
set: function(target,name,value,receiver) {
//reflect应该类似于reflect.set
var success = Reflect.set(target,name,value,receiver);
if (success) {
log('property' + name + 'on' + target + 'set to' + value);
}
return success;
}
});
Proxy方法拦截target对象的属性赋值行为.它采用Reflect.set方法将值赋值给对象的属性,确保完成原有
的行为,然后再部署额外的功能.
var loggedObj = new Proxy(obj, {
get(target,name) {
console.log('get',target,name);
return Reflect.get(target, name);
},
deleteProperty(target, name) {
console.log('delete' + name);
return Reflect.deleteProperty(target, name);
},
has(target,name){
console.log('has' + name);
return Reflect.has(target, name);
}
});
上面代码中,每一个Proxy对象的拦截操作(get/delete/has),内部都调用对应的Reflect方法,保证原生行为能够
正常执行.添加的工作,就是将每一个操作输出一行日志.

Function.prototype.apply.call(Math.floor,undefined,[1.75]) //1
//新写法
Reflect.apply(Math.floor,undefined,[1.75]) //1

3.实例:使用proxy实现观察者模式
const person = observable({
name: '张三',
age: 20
});

function print() {
console.log(${person.name}, ${person.age})
}

observe(print);
person.name = '李四';
// 输出
// 李四, 20
上面代码中,数据对象person是观察目标,函数print是观察者。一旦数据对象发生变化,print就会自动执行。

下面,使用 Proxy 写一个观察者模式的最简单实现,即实现observable和observe这两个函数。
思路是observable函数返回一个原始对象的 Proxy 代理,拦截赋值操作,触发充当观察者的各个函数。

const queuedObservers = new Set();

const observe = fn => queuedObservers.add(fn);
const observeable = obj => new Proxy(obj,{set});

function set(target,key,value,receiver) {
const result = Reflect.set(target,key,value,receiver);
queuedObservers.forEach(observer => observer());
return result;
}
上面代码中,先定义了一个Set集合,所有观察者函数都放进这个集合。然后,observable函数返回原始对象的代理,
拦截赋值操作。拦截函数set之中,会自动执行所有观察者。

Promise
Promise是一种异步编程的解决方案,比传统的解决方案-----回调函数和事件---更合理和更强大.
所谓Promise,简单说就是一个容器,里面保存着未来才会结果的事件(通常是异步操作)的结果.
Promise对象是一个构造函数
const promise = new Promise(function(resolve,reject) {

if () {  

} else{  

}  

})

let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
Promise实例生成以后,可以用then方法分别指定resolvued和rejected状态的回调函数.
promise.then(function(value) {
console.log('resolved.');
},function(error){

});

console.log('Hi!');

// Promise
// Hi!
// resolved

const getJSON = function(url) {
//new出来的promise实例
const promise = new Promise(function(resolve,reject) {
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET",url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept","application/json");
client.send();
});
return promise;
};
getJSON("/posts.json").then(function(json){
console.log("Contents:" + json);
},function(error){
console.error('出错了', error);
});
调用resolve或reject并不会终结Promise的参数函数的执行.所以最好在他们前面加上return来防止意外.

Promise.prototype.then()
then方法返回的是一个新的Promise实例,因此可以采用链式写法,即then方法后面再调用另一个then方法
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
//...
});
上面的代码使用then方法,一次指定了两个回调函数.第一个回调函数完成以后,会将返回结果作为参数,传入
第二个回调函数.

getJSON("/posts.json").then(function(json) {
return json.post;
}).catch(function(error) {
//处理getJSON和前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});
getJSON方法返回一个Promise对象,如果该对象状态变为resolved,则会调用then方法指定的回调函数;如果
异步操作抛出错误,状态就会变为rejected,就会调用catch方法指定的回调函数,处理这个错误.另外,then方
法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获

Promise.prototype.finally()
finally方法用于指定不管Promise对象最后状态如何,都会执行的操作.是ES2018引入标准的.
promise
.then(result => {...})
.catch(error => {...})
.finally(() => {...});
finally方法的回调函数不接受任何参数,里面的操作与状态无关,不依赖Promise的执行结果.
finally本质上是then方法的特例
promise
.finally(() => {
// 语句
});

// 等同于
promise
.then(
result => {
// 语句
return result;
},
error => {
// 语句
throw error;
}
);
上面代码中,如果不使用finally方法,同样的语句需要为成功和失败两种情况各写一次。有了finally方法,则只需要写一次。

它的实现也很简单。
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
上面代码中,不管前面的 Promise 是fulfilled还是rejected,都会执行回调函数callback。
从上面的实现还可以看到,finally方法总是会返回原来的值。
promise.all用于将多个Pormise实例包装成一个新的Promise实例
有时需要将现有对象转为 Promise 对象,Promise.resolve方法就起到这个作用。
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。

const preloadImage = function (path) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
})
}
二维数组,从里面跳出来,就是 arr[3][2]代表数组第三个元素中的数组的第二个元素

Iterator(遍历器)的概念
JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),
ES6 又添加了Map和Set。这样就有了 四种数据集合 ,用户还可以组合使用它们,定义
自己的数据结构,比如数组的成员是Map,Map的成员是对象。这样就需要一种统一的
接口机制,来处理所有不同的数据结构。
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统
一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处
理该数据结构的所有成员)。
Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二
是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令
for...of循环,Iterator 接口主要供for...of消费。
(1)创建一个指针对象,指向当前数据结构的起始位置.也就是说,遍历器对象本质上,就是一个指针对象
(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员
(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。
Iterator 接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即for...of循环(详见下文)。
当使用for...of循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。
原生具备 Iterator 接口的数据结构如下。
array
map
set
string
typedArray
函数的arguments对象
NodeList对象

//重要 利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出错误。  
function throwIfMissing() {  
    throw new Error('Missing parameter');  
}  
function foo(mustBeProvided = throwIfMissing()) {  
    return mustBeProvided;  
}  
foo();  
//上面代码的foo函数,如果调用的时候没有参数,就会调用默认值throwIfMissing函数,从而抛出一个错误。  
从上面代码还可以看到,参数mustBeProvided的默认值等于throwIfMissing函数的运行结果(注意函数名throwIfMissing之后有一对圆括号),这表明参数的默认值不是在定义时执行,而是在运行时执行。如果参数已经赋值,默认值中的函数就不会运行。  

另外,可以将参数默认值设为undefined,表明这个参数是可以省略的。  
function foo(optional = undefined) { ··· }  

2.rest参数
0 关注 分享

要回复文章请先登录注册