1.全局变量
//var function声明的变量属于全局变量属性;let,const,class声明的变量不属于全局变量属性
let a=1;
var b=2;
console.log(window.a,window.b)undefind,2
2.数组的解构赋值
//var a=1,b=2,c=3;
var [a, b, c] = [1, 2, 3];
let [foo, [[bar], baz]] = [1, [[2], 3]];//foo 1,bar 2,baz 3
let [head, ...tail] = [1, 2, 3, 4]; //head 1 tail // [2, 3, 4]
let [, , third] = ["foo", "bar", "baz"]; //third "baz"
let [x, y, ...z] = ['a']; //x "a",y undefined,z []
console.log(y);
function Dosth() {
let [x, y] = [1, 2, 3];// x 1,y 2
let [a, [b], d] = [1, [2, 3], 4];//a 1,b 2,d 4
}
3.对象的解构赋值
//对象的解构和数组的解构是不同的,数组的元素是按次序排列的,变量的值是由他的位置决定的,
//对象的属性没有次序,变量名必须与属性名相同
let { foo, bar } = { foo: "aaa", bar: "bbb" };
//对象的解构值相当于下面的简写
var { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };
//解构值的内部机制,先找到同名属性,然后再赋值给对应的变量,真正赋值的是后者
var { foo: baz } = { foo: "aaa", bar: "bbb" }; //baz "aaa" ,foo error: foo is not defined
//如果一个已声明的变量用于解构值,一定要在其外面加上括号,否则会报错
var x;
({ x } = { x: 1 });
let obj = { first: 'hello', last: 'world' };
//f:'hello',l:'world'
let { first: f, last: l } = obj;
4.字符串的解构赋值
const [a, b, c, d, e]="hello";
console.log(a);//h
5.数值和布尔类型的解构赋值
//如果右边是布尔值或者数值,会转换为对象
let { toString: s } = 123;//转换为key代表的方法 funciton toStrign(){}
console.log(Number.prototype.toString === s);
let { toString: a } = true;
console.log(Boolean.prototype.toString === a);
// console.log(Number,Boolean);
//解构赋值的规则是,只要等号右边的值不是对象,就先将其转为对象。
// 由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错
6.函数的解构赋值
1.//函数参数的解构也可以使用默认值
//注意:结合对象的解构赋值
// {x,y}默认值为{0,0}
function move({ x = 0, y = 0 } = {}) {
return [x, y];
}
move({ x: 3, y: 8 }); // [3, 8]
move({ x: 3 }); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]
2.//{x,y}默认值为undefined
function move({ x, y } = { x:0, y:0 }) {
return [x, y];
}
move({ x: 3, y: 8 }); // [3, 8]
move({ x: 3 }); // [3, undefined]
move({}); // [undefined,undefined]
move(); // [0, 0]
3.
function foo({ x, y = 5 }){
return x + y;
}
foo({}); // undefined, 5
foo(); // TypeError: Cannot read property 'x' of undefined
4.//下面写法不能省略第二个参数,如果结合函数参数的默认值,就可以省略第二个参数
function fetch1(url, { method = 'GET' }) {
console.log(method);
}
fetch1('http://example.com', {});// "GET"
// fetch1('http://example.com');// 报错 Cannot read property 'method' of undefined
// 函数fetch没有第二个参数时,函数参数的默认值就会生效,然后才是解构赋值的默认值生效,变量method才会取到默认值GET。
function fetch(url, { method = 'GET' } = {}) {
console.log(method);
}
fetch('http://example.com')// "GET"
7.使用圆括号的情况
//以下情况不能使用圆括号,全部报错
//1.变量声明语句不能使用圆括号
// var [(a)] = [1];
// var {x: (c)} = {};
// var ({x: c}) = {};
// var {(x: c)} = {};
// var {(x): c} = {};
// var { o: ({ p: p }) } = { o: { p: 2 } };
//2.函数模式参数中不能带有圆括号,因为函数参数也属于声明变量
// function f([(z)]){
// }
//3.赋值语句中,不能将整个模式,或者嵌套模式的其中一层,放在圆括号中
//3.1下面将整个模式放在圆括号中
// ({p: a }) = { p: 42 };
// ([a]) = [5];
//3.2不能嵌套在其中一层
// [({ p: a }), { x: c }] = [{}, {}];
//可以使用圆括号的情况
//赋值语句的非模式部分可以使用圆括号
//它们的圆括号都不属于模式的一部分。
//模式是取数组的第一个成员,跟括号无关
[(b)] = [2];
//模式是P,而不是d
({ p: (d) } = {});
//模式是取数组的第一个成员,跟括号无关
[(parseInt.prop)] = [3];
8.应用
//1.交换值
var a = 1, b = 2;
[a, b] = [b, a];//a=2,b=1
//2.返回多个值
function example() {
return [1, 2, 3];
}
var [a, b, c] = example();
console.log(a,b,c);
//4.提取json对象中的数据
var jsonData={
id:42,
staus:"OK",
data:[1,2]
}
let {id,data,staus}=jsonData;
console.log(id,data,staus)
//6.遍历map
var map=new Map();
map.set('first','hello');
map.set('second','world');
for(let [key,values] of map){
console.log(key+" is "+values);
}
9.字符串的拓展
// includes() :返回布尔值,表示是否找到了参数字符串。
// startsWith() :返回布尔值,表示参数字符串是否在源字符串的头部。
// endsWith() :返回布尔值,表示参数字符串是否在源字符串的尾部。
//这三个方法都支持第二个参数,表示搜索位置,endswith截取的是前面字符串
var s = "hello,world";
s.includes("ll");//true
s.startsWith("e");//true
s.endsWith("d");//true
// console.log()
//repeat()返回一个字符串,表示重复字符串n次,如果是小数,则会向下取整,如果是负数或者infinity,就会报错
'a'.repeat(2);//'aa'
//padStart(),padEnd()
//ES7推出了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart用于头部补全,padEnd用于尾部补全。
'a'.padStart(5, 'ab');
'b'.padEnd(5, 'ab');
10.数值的拓展
//ES6提供了二进制和八进制的表示方法,分别是0b(0B),0o(0O)
let num1 = 0b111;
let num2 = 0O77;
console.log(num1, num2);
//Number.isInteger(),判断是不是整数 在JavaScript中,浮点和整数是同样的储存方法,所以2和2.0被视为同一个值
Number.isInteger(16);//true
Number.isInteger(16.0);//true
Number.isInteger(16.1);//false
Number.isInteger("17");//false
Number.isInteger("true");//false
//Number.isSafeInteger()则是用来判断一个整数是否落在这个范围之内
Number.isSafeInteger(Number.MAX_SAFE_INTEGER);//true
Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1);//flase
Number.isSafeInteger(Number.MIN_SAFE_INTEGER);//true
11.指数运算符
//指数运算符**
//数运算符可以与等号结合,形成一个新的赋值运算符(**=)。
console.log(2 ** 4);
12.数组的拓展
let obj = {
0: "a",
1: "b",
2: "c",
length: 3
};
//伪数组转数组 常用于DOM操作返回的NodeList集合,以及函数内部的arguments对象。
//ES5写法
var arr1 = [].slice.call(obj);
//ES6写法
var arr2 = Array.from(obj);
//Array.of方法用于将一组值,转换为数组
Array.of(3, 8, 250);
//find()找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined
//findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
[1, 4, -5, 10].find((n) => n < 0);//-5
[1, 4, -5, 10].findIndex((n) => n < 0);//2
//fill方法用于空数组的初始化非常方便。数组中已有的元素,会被全部抹去。
['a', 'b', 'c'].fill(7);// [7, 7, 7]
new Array(3).fill(7)// [7, 7, 7]
13.rest参数
function add1(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add1(2, 5, 3) // 10
//about
let arr=[1,2,3]
...arr//1 2 3
14.字符串转数组
// 2.字符串
res = [...'hello'];//将字符串转为真正的数组。
console.log(res)
15.函数name属性
function foo() {
foo.name;//foo
}
var func1 = function () { };
// ES6
func1.name // "func1"
// 1.个具名函数赋值给一个变量,则ES5和ES6的name属性都返回这个具名函数原本的名字
const bar = function baz() { };
// ES5
bar.name // "baz"
// Function构造函数返回的函数实例,name属性的值为“anonymous”。
(new Function).name // "anonymous"
// bind返回的函数,name属性值会加上“bound ”前缀。
function foo1() { };
foo1.bind().name // "bound foo1"
(function(){}).bind().name // "bound " 匿名函数
16.尾调用的概念
// 尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,
// 就是指某个函数的最后一步是调用另一个函数
function f(x) {
return g(x);
}
// 以下三种情况,都不属于尾调用。
// 情况一
//调用函数g之后,还有赋值操作,所以不属于尾调用,即使语义完全一样
function f(x) {
let y = g(x);
return y;
}
// 情况二
//属于调用后还有操作,即使写在一行内
function f(x) {
return g(x) + 1;
}
// 情况三
function f(x) {
g(x);
}
// 尾调用不一定出现在函数尾部,只要是最后一步操作即可。
function f(x) {
if (x > 0) {
return m(x)
}
return n(x);
}
17.尾递归
//8.7.3尾递归
// 函数调用自身,称为递归。如果尾调用自身,就称为尾递归
//只保留一个调用记录,复杂度 O(1)
//时间复杂度O(1)是常数阶,其基du本操作重复执行的次数是一个固zhi定的常数,执行次数不存在变化;
function factorial(n, total) {
debugger
if (n === 1) {
return total;
}
return factorial(n - 1, n * total);
}
console.log(factorial(5, 1)) // 120
//最多需要保存n个调用记录,复杂度 O(n)
//而时间复杂度O(n)是线性阶,其基本操作重复执行的次数是与模块n成线性相关的,其值会随着模块n的变化而变化,
// 当模块n的规模确定为定值后,其时间复杂度转化为O(1)
function factorial(n) {
if (n === 1) {
return 1;
}
return n * factorial(n - 1);
}
factorial(5); // 120
18.尾递归的改写与柯里化的概念
//柯里化的概念:将多参数的函数转换成单参数的形式
//1.
function tailFactorial(n, total) {
if (n === 1) {
return total;
}
return tailFactorial(n - 1, n * total);
}
function factorial(n) {
return tailFactorial(n, 1);
}
factorial(5) // 120
//2.
function currying(fn, n) {
return function (m) {
return fn.call(this, m, n); //fn.call()直接运行
};
}
function tailFactorial(n, total) {
if (n === 1) {
return total;
}
console.log(1)
return tailFactorial(n - 1, n * total);
}
const factorial = currying(tailFactorial, 1);
let res = factorial(5) // 120
console.log(res);
19.Object.is()
//ES5比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。
//它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。
//JavaScript缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。
Object.is('foo', 'foo')// true
Object.is({}, {})// false
20.Object.assign()
//注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性
//如果只有一个参数,Object.assign会直接返回该参数
//由于undefined和null无法转成对象,所以如果它们作为参数,就会报错。
//如果undefined和null不在首参数,就不会报错
//其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。
//但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果
//Object.assign方法实行的是浅拷贝,而不是深拷贝。
//1.
var target = { a: 1 };
var source1 = { b: 2 };
var source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
//2.
var v1 = 'abc';
var v2 = true;
var v3 = 10;
var obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
21.Object.getOwnPropertyDescriptor()
let obj = { age: 10086 };
let res = Object.getOwnPropertyDescriptor(obj, 'age');
//enumerable 可枚举性,如果该属性为false,就表示某些操作会忽略当前属性
// { value: 10086, writable: true, enumerable: true, configurable: true }
22.属性的遍历
// 9.7 属性的遍历属性的遍历
//ES6一共有5种方法可以遍历对象的属性。
/*
1.for..in
for...in循环遍历对象自身的和继承的可枚举属性(不含Symbol属性)。
2.Object.keys(obj)
Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)
3.Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)
4.Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有Symbol属性
5.Reflect.ownKeys(obj)
Reflect.ownKeys返回一个数组,包含对象自身的所有属性,不管是属性名是Symbol或字符串,也不管是否可枚举。但不包括继承自原型的属性
23.Object.setPrototypeOf()
//Object.setPrototypeOf方法的作用与__proto__相同,用来设置一个对象的prototype对象。
//它是ES6正式推荐的设置原型对象的方法
//设置原型后,obj可以访问proto里面的属性,proto不可以访问obj里面的属性
let proto = {};
let obj = { x: 10 };
Object.setPrototypeOf(obj, proto);
proto.y = 20;
proto.z = 40;
obj.x // 10
obj.y // 20
obj.z // 40
24.Object.getPrototypeOf()
//Object.getPrototypeOf():读取一个对象的prototype对象
function Person() {
}
let per = new Person();
let res = Object.getPrototypeOf(per) === Person.prototype;
console.log(res);//true
//Object.getPrototypeOf():读取一个实例化对象的prototype对象
let proto = {};
let obj = { x: 10 };
Object.setPrototypeOf(obj, proto);
res = Object.getPrototypeOf(proto) === Object.prototype;
console.log(res)//true
//add Object.getPrototypeOf(obj) === proto;//true
25.Object.keys()
//ES5引入了Object.keys方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名
var obj = { namme: "hehe", age: 18 };
let res = Object.keys(obj);
console.log(res);//[name,age]
26.Object.values()
// Object.values()Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值
var obj = { 100: 'a', 2: 'b', 7: 'c' };
Object.values(obj)// ["b", "c", "a"]
//属性名为数值的属性,是按照数值大小,从小到大遍历的
let o = {};
obj = Object.create(o, { p: { value: 42 } });
var res = Object.values(obj) // []
//上面代码中,Object.create方法的第二个参数添加的对象属性(属性p),
//如果不显式声明,默认是不可遍历的。Object.values不会返回这个属性
console.log(obj.p);
//Object.values会过滤属性名为Symbol值的属性
Object.values({ [Symbol()]: 123, foo: 'abc' });// ['abc']
//如果Object.values方法的参数是一个字符串,会返回各个字符组成的一个数组
Object.values('foo')// ['f', 'o', 'o']
//上面代码中,字符串会先转成一个类似数组的对象。字符串的每个字符,就是该对象的一个属性。
//因此,Object.values返回每个属性的键值,就是各个字符组成的一个数组
//如果参数不是对象,Object.values会先将其转为对象。由于数值和布尔值的包装对象,
//都不会为实例添加非继承的属性。所以,Object.values会返回空数组
Object.values(42) // []
Object.values(true) // []
27.Object.Enties()
//Object.entries方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。
var obj = { foo: 'bar', baz: 42 };
Object.entries(obj)// [ ["foo", "bar"], ["baz", 42] ]
//除了返回值不一样,该方法的行为与Object.values基本一致
28.对象的拓展运算符
//2.拓展运算符
//扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中
let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }
// 同于使用Object.assign方法
let aClone = Object.assign({}, z);
//扩展运算符可以用于合并两个对象。
let a = { c: 1 }, b = { d: 2 };
let ab = { ...a, ...b };
// 等同于
ab = Object.assign({}, a, b)
29.Object.getOwnPropertyDescriptors()实现方法
//object.getOwnPropertyDescriptors方法返回一个对象,所有原对象的属性名都是该对象的属性名,
//对应的属性值就是该属性的描述对象。该方法的实现非常容易
//注:该方法的提出目的,主要是为了解决Object.assign()无法正确拷贝get属性和set属性的问题
function getOwnPropertyDescriptors(obj) {
const result = {};
for (let key of Reflect.ownKeys(obj)) { // Reflect.ownKeys()返回所有自有属性key,不管是否可枚举,但不包括继承自原型的属性
result[key] = Object.getOwnPropertyDescriptor(obj, key);
}
return result;
}
29.1Object.getOwnPropertyDescriptors()
//ES7有一个提案,提出了Object.getOwnPropertyDescriptors方法,返回指定对象所有自身属性(非继承属性)的描述对象
obj = {
foo: 123,
get bar() {
return 'abc'
}
};
res = Object.getOwnPropertyDescriptor(obj, 'bar');
console.log(res);
res = Object.getOwnPropertyDescriptors(obj);
console.log(res.bar);
// { foo:
// { value: 123,
// writable: true,
// enumerable: true,
// configurable: true },
// bar:
// { get: [Function: bar],
// set: undefined,
// enumerable: true,
// configurable: true } }
//bject.getOwnPropertyDescriptors方法返回一个对象,所有原对象的属性名都是该对象的属性名,
//对应的属性值就是该属性的描述对象。该方法的实现非常容易
30.使用Object.getOwnPropertyDescriptors()拷贝
const source = {
set foo(value) {
console.log(value);
}
};
const target2 = {};
res = Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source));
res=Object.getOwnPropertyDescriptors(target2,'foo');
31.Symbol基本概念
//Symbol值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的
///Symbol类型。凡是属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突
let a1 = Symbol('foo');
// console.log(a1); //Symbol(foo)
//1.注意,Symbol函数的参数只是表示对当前Symbol值的描述,因此相同参数的Symbol函数的返回值是不相等的
let a=Symbol("t");
let b=Symbol("t");
console.log(a===b);
//2.Symbol值不能与其他类型的值进行运算,会报错 可以使其值转换成字符串后加减
console.log("your Symbol is"+a.toString());
//3.Symbol值也可以转为布尔值,但是不能转为数值。
let c=Symbol();
let flag= Boolean(c);//true
console.log(flag);
// Number(c);
//注:遍历含Symbol属性的对象使用Object.getOwnPropertySymbols()
32.Symbol的基本使用
let mySymbol = Symbol();
var obj = {};
obj[mySymbol] = 1;
console.log(obj[mySymbol]);
var obj = {
[mySymbol]: 2
}
console.log(obj[mySymbol]);
var obj = {};
Object.defineProperty(obj, mySymbol, {
value: 3
});
console.log(obj[mySymbol]);
//注意,Symbol值作为对象属性名时,不能用点运算符
var a = {};
a.mySymbol = 'Hello!'; //注:这里是基本对象,不是 Symbol()的mySymbol
a[mySymbol] // undefined 有Symbol属性名的对象输出
a['mySymbol'] // "Hello!" 普通对象输出
33.Symbol.for(),Symbol.keyfor()
//Symbol.for()重新使用同一个Symbol值
var s1 = Symbol.for('foo');
var s2 = Symbol.for('foo');
s1 === s2;//true
//Symbol.for()与Symbol()这两种写法,都会生成新的Symbol。它们的区别是,前者会被登记在全局环境中供搜索,
// 后者不会。Symbol.for()不会每次调用就返回一个新的Symbol类型的值,而是会先检查给定的key是否已经存在,
// 如果不存在才会新建一个值比如,如果你调用Symbol.for("cat")30次,每次都会返回同一个Symbol值,
//但是调用Symbol("cat")30次,会返回30个不同的Symbol值。
//Symbol.keyFor方法返回一个已登记的Symbol类型值的key
var res = Symbol.keyFor(s1);
// console.log(res);//foo
s2 = Symbol('foo');
res = Symbol.keyFor(s2);
console.log(res);
34.Symbol.hasInstance
//对象的Symbol.hasInstance属性,指向一个内部方法。当其他对象使用instanceof运算符,判断是否为该对象的实例时,
//会调用这个方法。比如,foo instanceof Foo在语言内部,实际调用的是Foo[Symbol.hasInstance](foo)
class MyClass {
[Symbol.hasInstance](foo) {
return foo instanceof Array;
}
}
[1, 2, 3] instanceof new MyClass() // true
console.log(new MyClass());
class Even {
static [Symbol.hasInstance](obj) {
return Number(obj) % 2 === 0;
}
}
console.log(1 instanceof Even);
35.Proxy概念
//Proxy可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,
//因此提供了一种机制,可以对外界的访问进行过滤和改写。
//let proxy=new Proxy(target,handler);
//Proxy对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。
//其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为
//拦截读取属性行为的例子
var proxy = new Proxy({}, {
get: function (target, property) { //{},time
console.log("getting");
return 35;
}
});
proxy.time;//35
proxy.age;//35
proxy.title;//35
36.Proxy中target和handler的关系
//如果handler没有设置任何拦截,那就等同于直接通向原对象。
var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"
proxy.doth = function () {
console.log("h");
}
console.log(target,proxy);
//如果未设置拦截(handler为空),那么实例化对象的属性会访问target里面的属性
//如果未设置拦截(handler为空),设置实例化对象的属性相当于设置target里面的属性
37.Proxy拦截多个操作
var handler = {
get: function (target, name) {
if (name === 'prototype') {
return Object.prototype;
}
return 'Hello, ' + name;
},
apply: function (target, thisBinding, args) {
return args[0];
},
construct: function (target, args) {
return { value: args[1] };
}
};
let fproxy = new Proxy(function (x, y) {
return x + y;
}, handler);
let res = fproxy(2, 3);//2
console.log(res);
res = new fproxy(1, 2);// {value: 2}
console.log(res);
fproxy.prototype === Object.prototype // true
fproxy.foo // "Hello, foo"
38.Proxy支持的拦截操作
// 对于可以设置、但没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。
///(1)get(target, propKey, receiver)
// 拦截对象属性的读取,比如proxy.foo和proxy['foo']。
// 最后一个参数receiver是一个对象,可选,参见下面Reflect.get的部分。
//(2)set(target, propKey, value, receiver)
// 拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。
//(3)has(target, propKey)
//拦截propKey in proxy的操作,以及对象的hasOwnProperty方法,返回一个布尔值。
//(4)deleteProperty(target, propKey)
//拦截delete proxy[propKey]的操作,返回一个布尔值。
//(5)ownKeys(target)
//拦截Object.getOwnPropertyNames(proxy) 、Object.getOwnPropertySymbols(proxy) 、Object.keys(proxy) ,
//返回一个数组。该方法返回对象所有自身的属性,而Object.keys()仅返回对象可遍历的属性。
//(6)getOwnPropertyDescriptor(target, propKey)
//拦截Object.getOwnPropertyDescriptor(proxy, propKey) ,返回属性的描述对象。
//(7)defineProperty(target, propKey, propDesc)
//拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs) ,
// 返回一个布尔值。
//(8)preventExtensions(target)
//拦截Object.preventExtensions(proxy) ,返回一个布尔值。
//(9)getPrototypeOf(target)
//拦截Object.getPrototypeOf(proxy) ,返回一个对象。
//(10)isExtensible(target)
//拦截Object.isExtensible(proxy) ,返回一个布尔值。//Object.isExtensible()判断是否能拓展
//(11)setPrototypeOf(target, proto)
//拦截Object.setPrototypeOf(proxy, proto) ,返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
//(12)apply(target, object, args)
//拦截Proxy实例作为函数调用的操作,比如proxy(...args) 、proxy.call(object, ...args) 、proxy.apply(...) 。
//(13)construct(target, args)拦截Proxy实例作为构造函数调用的操作,
//比如new proxy(...args) 。对于可以设置、但没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。
38.1 get()
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
38.2 set()
let proxy = new Proxy({}, {
set: function (obj, prop, value) {
if (prop === "age") {
if (!Number.isInteger(value)) {
throw new TypeError("The age not an integer");
}
if (value > 200) {
throw new RangeError("The age seems invalid");
}
}
//对于age以外的属性直接保存
obj[prop] = value;
}
});
proxy.age=12;
console.log(proxy.age);
proxy.age=201; //error
proxy.age="a?"; //error
38.3 apply()
//apply方法拦截函数的调用、call和apply操作。
//apply方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组
var twice = {
apply(target, ctx, args) {
//rest传参会将参数整成数组,而apply是以数组的方式传参,代入方法后参数会转换成数字或字符串
return Reflect.apply(...arguments)*2;
}
};
function sum(left, right) {
return left + right;
};
var proxy = new Proxy(sum, twice);
let res = proxy(1, 2) // 6
console.log(res);
proxy.call(null, 5, 6) // 22
proxy.apply(null, [7, 8]) // 30
//另外,直接调用Reflect.apply方法,也会被拦截
res = Reflect.apply(proxy, null, [1, 3]);//8
console.log(res);
38.4 has()
//has方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in运算符
//下面的例子使用has方法隐藏某些属性,不被in运算符发现。
var target = {
_prop: 'foo',
prop: 'foo'
}
let proxy = new Proxy(target, {
has(target, propKey) {
if (propKey[0] === "_") {
return false;
}
return propKey in target;
}
});
let res = 'prop' in proxy;
console.log(res);
//注: 如果原对象不可配置或者禁止扩展,这时has拦截会报错
var obj = { a: 10 };
Object.preventExtensions(obj);
var p = new Proxy(obj, {
has: function (target, prop) {
return false;
}
});
'a' in p // TypeError is thrown
38.5 construct()
//construct方法用于拦截new命令
//construct方法可以接受两个参数。target: 目标对象,args:构建函数的参数对象
let proxy = new Proxy(function () { }, {//注:第一个参数必须是构造函数,不能是对象!
construct(target, args) {
console.log('called: ' + args.join(', '));
return { value: args[0] * 10 };
}
});
console.log(new proxy(1).value);
//注:construct方法返回的必须是一个对象,否则会报错。
var p = new Proxy(function () {}, {
construct: function (target, argumentsList) {
return 1;
}
});
// new p() // 报错
39.6 deleteProperty()
//deleteProperty方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除
let proxy = new Proxy({ _prop: 'foo' }, {
deleteProperty(target, key) {
if (key[0] === "_") {
throw new Error(`Invalid attempt to ${'delete'} private "${key}" property`);
}
return true;
}
});
delete proxy._prop;
// Error: Invalid attempt to delete private "_prop" property
39.7 defineProperty()
var proxy = new Proxy({ foo: 3 }, {
defineProperty(target, key, descriptor) {
console.log("hh");
console.log(descriptor);
return true;
// return false;
}
});
// proxy.foo = 2;
Object.defineProperty(proxy, "foo", {
configurable: true,
enumerable: true,
value: 2,
writable: true,
})
//上面代码中,defineProperty方法返回false,导致使用Object.defineProerty()添加新属性会抛出错误
39.8 getOwnPropertyDescriptor()
//getOwnPropertyDescriptor方法拦截Object.getOwnPropertyDescriptor,返回一个属性描述对象或者undefined。
var proxy = new Proxy({ _foo: 'bar', baz: 'tar' }, {
getOwnPropertyDescriptor(target, key) {
if (key[0] === "_") {
return;
}
return Object.getOwnPropertyDescriptor(target, key);
}
});
let res = Object.getOwnPropertyDescriptor(proxy, "wait");//undefined
res = Object.getOwnPropertyDescriptor(proxy, "_wait");//undefined
// { value: 'tar', writable: true, enumerable: true, configurable: true
res = Object.getOwnPropertyDescriptor(proxy, 'baz');
console.log(res);
39.9 getPropertyOf()
//getPrototypeOf方法主要用来拦截Object.getPrototypeOf()运算符,以及其他一些操作
//Object.__proto__
//Object.isPrototypeOf()
//Object.getPrototypeOf()
//Reflect.getPrototypeOf()
//instanceof运算符
var proto = {};
var p = new Proxy({}, {
getPrototypeOf(target) {
return proto;
}
});
let res= Object.getPrototypeOf(p) === proto // true
console.log(res);
//上面代码中,getPrototypeOf方法拦截Object.getPrototypeOf(),返回proto对象
39.10 isExtensible()
//isExtensible方法拦截Object.isExtensible操作
var p = new Proxy({}, {
isExtensible: function (target) {
console.log("called");
return true;
}
});
Object.isExtensible(p);
// "called"
39.11 ownkeys()
// ownKeys方法用来拦截Object.keys()操作
let proxy = new Proxy({ "a": 1 }, {
ownKeys(target) {
console.log("!!!");
return ['a'];
}
});
console.log(Object.keys(proxy));
39.12 preventExtensible()
//preventExtensions方法拦截Object.preventExtensions()。该方法必须返回一个布尔值。
//这个方法有一个限制,只有当Object.isExtensible(proxy)为false(即不可扩展)时,
// proxy.preventExtensions才能返回true,否则会报错
var proxy = new Proxy({}, {
preventExtensions: function (target) {
console.log('called');
Object.preventExtensions(target);//将target变成false
return true;
}
})
console.log(Object.preventExtensions(proxy));
39.13 setPropertyOf()
//setPrototypeOf方法主要用来拦截Object.setPrototypeOf方法
let proto = {};
var proxy = new Proxy(function () { }, {
setPrototypeOf(target, proto) {
throw new Error('Changing the prototype is forbidden');
}
});
Object.setPrototypeOf(proxy, proto);
//// Error: Changing the prototype is forbidden
//上面代码中,只要修改target的原型对象,就会报错。
39.Proxy.revocable()
let {proxy,revoke}= Proxy.revocable({},{});
proxy.x=12;
console.log(proxy.x);
revoke();
console.log(proxy.x);
//Proxy.revocable方法返回一个对象,该对象的proxy属性是Proxy实例,
//revoke属性是一个函数,可以取消Proxy实例。
//上面代码中,当执行revoke函数之后,再访问Proxy实例,就会抛出一个错误。
40.set的概念
//1.Set是伪数组 Set可以用来去除重复值
let arr = new Set([1, 2, 3, 4, 4]);
console.log(arr);//Set(4)[1,2,3,4]
console.log(...arr);//1,2,3,4
//向Set加入值的时候,不会发生类型转换,所以5和"5"是两个不同的值。
//主要的区别是NaN等于自身,而精确相等运算符认为NaN不等于自身
//2.两个对象总是不相等的。
let Numset = new Set();
Numset.add({});
let numres = Numset.size;
console.log(numres);//1
Numset.add({});
numres = Numset.size;
console.log(numres);//2
41.Set遍历
//key方法、value方法、entries方法返回的都是遍历器对象(详见《Iterator对象》一章)。
//由于Set结构没有键名,只有键值(或者说键名和键值是同一个值),所以key方法和value方法的行为完全一致
let set = new Set(['red', 'green', 'blue']);
for (let item of set.keys()) {
console.log(item);
}
// red// green// blue
for (let item of set.values()) {
console.log(item);
}
// red// green// blue
for (let item of set.entries()) {
console.log(item);
}
// ["red", "red"]// ["green", "green"]// ["blue", "blue"]
//上面代码中,entries方法返回的遍历器,同时包括键名和键值,所以每次输出一个数组,它的两个成员完全相等
//Set结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法
Set.prototype[Symbol.iterator] === Set.prototype.values//true
//这意味着,可以省略values方法,直接用for...of循环遍历Set。
for (let x of set) {
console.log(x);
}
// red// green// blue
//Set结构的实例的forEach方法,用于对每个成员执行某种操作,没有返回值
let numset = new Set([1, 2, 3]);
numset.forEach((value, key) => console.log(value * 2));
42.Set应用
//数组的map和filter方法也可以用于Set了。
var set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x * 2));
// 返回Set结构:{2, 4, 6}
set = new Set([1, 2, 3, 4, 5]);
set = new Set([...set].filter(x => (x % 2) == 0));
// 返回Set结构:{2, 4}
//使用Set可以很容易地实现并集(Union)、交集(Intersect)和差集(Difference)
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}
// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}
//如果想在遍历操作中,同步改变原来的Set结构,目前没有直接的方法,但有两种变通方法。
//一种是利用原Set结构映射出一个新的结构,然后赋值给原来的Set结构;
//另一种是利用Array.from方法。
// 方法一
set = new Set([1, 2, 3]);
set = new Set([...set].map(val => val * 2));
// set的值是2, 4, 6
// 方法二
set = new Set(Array.from(set, val => val * 2));// set的值是2, 4, 6
43.WeakSet
//WeakSet结构与Set类似,也是不重复的值的集合。但是,它与Set有两个区别
//1.WeakSet的成员只能是对象,而不能是其他类型的值。
//2.WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用,
//也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,
//不考虑该对象还存在于WeakSet之中。这个特点意味着,无法引用WeakSet的成员,因此WeakSet是不可遍历的。
//作为构造函数,WeakSet可以接受一个数组或类似数组的对象作为参数。(实际上,任何具有iterable接口的对象,都可以作为WeakSet的参数。)
//该数组的所有成员,都会自动成为WeakSet实例对象的成员。
var a = [[1, 2], [3, 4]];
var ws = new WeakSet(a);
//上面代码中,a是一个数组,它有两个成员,也都是数组。
//将a作为WeakSet构造函数的参数,a的成员会自动成为WeakSet的成员。
//注意,是a数组的成员成为WeakSet的成员,而不是a数组本身。这意味着,数组的成员只能是对象
// var b = [3, 4];var ws = new WeakSet(b);
// Uncaught TypeError: Invalid value used in weak set(...)
//上面代码中,数组b的成员不是对象,加入WeaKSet就会报错。
44.Map的概念
//Map数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键
var m = new Map();
let o = "";
m.set(o, 'content');
let res = m.get(o);
console.log(res);
console.log(m);
//下面的例子中,字符串true和布尔值true是两个不同的键
m = new Map([[true, 'foo'], ['true', 'bar']]);
m.get(true) // 'foo'
m.get('true') // 'bar'
console.log(m);
//如果对同一个键多次赋值,后面的值将覆盖前面的值
m.clear();
m.set(1, 'aaa').set(1, 'bbb');
m.get(1) // "bbb"
//如果读取一个未知的键,则返回undefined。
new Map().get('asfddfsasadf');//undefined
//注意,只有对同一个对象的引用,Map结构才将其视为同一个键。这一点要非常小心
var map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined
//上面代码的set和get方法,表面是针对同一个键,但实际上这是两个值,内存地址是不一样的,
//因此get方法无法读取该键,返回undefined。
//同理,同样的值的两个实例,在Map结构中被视为两个键
map = new Map();
var k1 = ['a'];
var k2 = ['a'];
map.set(k1, 111).set(k2, 222);
map.get(k1) // 111
map.get(k2) // 222
//由上可知,Map的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。
//这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,
//如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。
//如果Map的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,
//Map将其视为一个键,包括0和-0。另外,虽然NaN不严格相等于自身,但Map将其视为同一个键
map = new Map();
map.set(NaN, 123);
map.get(NaN) // 123
map.set(-0, 123);
map.get(+0) // 123
45.Map遍历
//Map原生提供三个遍历器生成函数和一个遍历方法。
//keys():返回键名的遍历器。
//values():返回键值的遍历器。
//entries():返回所有成员的遍历器。
//forEach():遍历Map的所有成员。
let map = new Map([['F', 'no'], ['T', 'yes'],]);
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// 等同于使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
//上面代码最后的那个例子,表示Map结构的默认遍历器接口(Symbol.iterator属性),就是entries方法。
map[Symbol.iterator] === map.entries;//true
46.Map拓展运算符
//Map结构转为数组结构,比较快速的方法是结合使用扩展运算符(...)。
let 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的遍历和过滤(Map本身没有map和filter方法)。
let map0 = new Map().set(1, 'a').set(2, 'b').set(3, 'c');
let map1 = new Map([...map0].filter(([k, v]) => k < 3));
// 产生Map结构 {1 => 'a', 2 => 'b'}
let map2 = new Map([...map0].map(([k, v]) => [k * 2, '_' + v]));
// 产生Map结构 {2 => '_a', 4 => '_b', 6 => '_c'}
map.forEach(function (value, key, map) {
console.log("Key: %s, Value: %s", key, value);
});
//forEach方法还可以接受第二个参数,用来绑定this。
var reporter = {
report: function (key, value) {
console.log("Key: %s, Value: %s", key, value);
}
};
map.forEach(function (value, key, map) {
this.report(key, value);
}, reporter);
//上面代码中,forEach方法的回调函数的this,就指向reporter。
47.weakMap
//1.WeakMap结构与Map结构基本类似,唯一的区别是它只接受对象作为键名(null除外),
//2.不接受其他类型的值作为键名,而且键名所指向的对象,不计入垃圾回收机制
//WeakMap的设计目的在于,键名是对象的弱引用(垃圾回收机制不将该引用考虑在内),
//所以其所对应的对象可能会被自动回收。当对象被回收后,WeakMap自动移除对应的键值对。
//典型应用是,一个对应DOM元素的WeakMap结构,当某个DOM元素被清除,其所对应的WeakMap记录就会自动被移除。
//基本上,WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失。WeakMap结构有助于防止内存泄漏
//下面是WeakMap结构的一个例子,可以看到用法上与Map几乎一样。
var wm = new WeakMap();
var element = document.querySelector(".element");
wm.set(element, "Original");
wm.get(element); // "Original"
element.parentNode.removeChild(element);
element = null;
wm.get(element) // undefined
//上面代码中,变量wm是一个WeakMap实例,我们将一个DOM节点element作为键名,然后销毁这个节点,
//element对应的键就自动消失了,再引用这个键名就返回undefined。
//WeakMap与Map在API上的区别主要是两个,一是没有遍历操作(即没有key()、values()和entries()方法),
//也没有size属性;二是无法清空,即不支持clear方法。这与WeakMap的键不被计入引用、被垃圾回收机制忽略有关。
//因此,WeakMap只有四个方法可用:get()、set()、has()、delete()。
48.Iterator概念
//遍历器(Iterator)一种统一的接口机制,来处理所有不同的数据结构.
//Iterator的作用有三个:
//一是为各种数据结构,提供一个统一的、简便的访问接口;
//二是使得数据结构的成员能够按某种次序排列;
//三是ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费。
//Iterator的遍历过程是这样的。
//1.创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
//2.第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
//3.第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
//4.不断调用指针对象的next方法,直到它指向数据结构的结束位置。
//每一次调用next方法,都会返回数据结构的当前成员的信息。
//具体来说,就是返回一个包含value和done两个属性的对象。
//其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。
var it = makeIterator(['a', 'b']);
it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }
function makeIterator(array) {
var nextIndex = 0;
return {
next() {
return nextIndex < array.length ?
{ value: array[nextIndex++], done: false } :
{ value: undefined, done: true };
}
};
}
49.for..of与Iterator接口
//Iterator接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即for...of循环
//使用for...of循环遍历某种数据结构时,该循环会自动去寻找Iterator接口
//在ES6中,有三类数据结构原生具备Iterator接口:数组、某些类似数组的对象、Set和Map结构。
//一个对象如果要有可被for...of循环调用的Iterator接口,
//就必须在Symbol.iterator的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。
class RangeIterator {
constructor(start, stop) {
this.value = start;
this.stop = stop;
}
[Symbol.iterator]() {
return this;
}
next() {
var value = this.value;
if (value < this.stop) {
this.value++;
return { done: false, value: value };
}
else {
//done属性是一个布尔值,表示遍历是否结束。如果结束,则值也不会输出
return { done: true, value: undefined };
}
}
}
for (var value of new RangeIterator(0, 3)) {
console.log(value);
//0 1 2
}
50.为对象添加Iterator接口
//1.Nodelist.prototype[Symbol.iterator]=Array.prototype[Symbol.iterator]
//2.Nodelist.prototype[Symbol.iterator]=[][Symbol.iterator]
//3.在对象内部直接设置 [Symbol.iterator]:Array.prototype[Symbol.iterator]
51.使用Generator实现Iterator接口
//Symbol.iterator方法的最简单实现,还是使用下一章要介绍的Generator函数。
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1, 2, 3]
// 或者采用下面的简洁写法
let obj = {
*[Symbol.iterator]() {
yield 'hello';
yield 'world';
}
};
for (let x of obj) {
console.log(x);
}
// hello
// world
52.for..of循环
//for...of循环可以使用的范围包括数组、Set和Map结构、某些类似数组的对象
//(比如arguments对象、DOM NodeList对象、后文的Generator对象,以及字符串)
//1. 数组
const arr = ['red', 'green', 'blue'];
console.log(arr);
let iterator = arr[Symbol.iterator]();
for (let v of arr) {
console.log(v);
// red green blue
}
for (let v of iterator) {
console.log(v);
// red green blue
}
//上面代码的for...of循环的两种写法是等价的。
53.Generator函数
//Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同
//从语法上,首先可以把它理解成,Generator函数是一个状态机,封装了多个内部状态。
//执行Generator函数会返回一个遍历器对象,返回的遍历器对象,可以依次遍历Generator函数内部的每一个状态
function* helloGenerator() {
yield 'hello';
yield 'Generator';
return "huohuo"; //如果没有return,返回对象的value值为undefined
}
var res = helloGenerator();
// console.log(res.next());
// console.log(res.next());
// console.log(res.next());
for (const iterator of res) {
console.log(iterator);
}
function* gen() {
// some code
}
var g = gen();
g[Symbol.iterator]() === g;// true
//上面代码中,gen是一个Generator函数,调用它会生成一个遍历器对象g。
//它的Symbol.iterator属性,也是一个遍历器对象生成函数,执行后返回它自己
54.Generator next()
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
//如果向next方法提供参数,返回结果就完全不一样了。上面代码第一次调用b的next方法时,返回x+1的值6;
//第二次调用next方法,将上一次yield语句的值设为12,因此y等于24,返回y / 3的值8;
//第三次调用next方法,将上一次yield语句的值设为13,因此z等于13,这时x等于5,y等于24,
//所以return语句的值等于42。
//注意,next方法的参数表示上一个yield语句的返回值,第一次使用next方法时,不能带有参数
//从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。
55.第一次调用
function wrapper(generatorFunction) {
return function () {
let generatorObject = generatorFunction();
generatorObject.next();
return generatorObject;
};
}
const wrapped = wrapper(function* () {
console.log('First input: ${yield}');
});
wrapped().next('hello!');// First input: hello!
//上面代码中,Generator函数如果不用wrapper先包一层,是无法第一次调用next方法,就输入参数的
//通过next方法的参数,向Generator函数内部输入值的例子
function* dataConsumer() {
console.log('Started');
console.log('1. ${yield}');
console.log('2. ${yield}');
return 'result';
}
let genObj = dataConsumer();
genObj.next();// Started {value:undefind,done:false}
genObj.next('a')// 1. a {value:undefind,done:false}
genObj.next('b')// 2. b {value:result,done:true}
//每次通过next方法向Generator函数输入值,然后打印出来
56.使用Generator函数实现斐波那契数列
function* fibonacci() {
let [prev, curr] = [0, 1];
for (; ;) { //死循环
[prev, curr] = [curr, prev + curr];
yield curr;
}
}
for(let i of fibonacci()){
if (i>30) {
break;
}
console.log(i);
}
57.使用generator函数为对象添加遍历接口
function* objctEntries(obj) {
let keyarr = Reflect.ownKeys(obj);//Reflect.ownKey()获取对象的key,返回一个数组
for (let propkey of keyarr) {
yield [propkey, obj[propkey]];
}
}
let obj={
name:"yang",
age:18,
sex:"nv"
}
for(let [key,value] of objctEntries(obj)){
console.log(`${key}: ${value}`);
}
//上面代码中,对象jane原生不具备Iterator接口,无法用for...of遍历。这时,
// 我们通过Generator函数objectEntries为它加上遍历器接口,就可以用for...of遍历了
58.generater throw
//Generator函数返回的遍历器对象,都有一个throw方法,可以在函数体外抛出错误,然后在Generator函数体内捕获
var g = function* () {
try {
yield;
}
catch (e) {
console.log('内部捕获', e);
}
};
var i = g();
i.next();
try {
i.throw('a');
i.throw('b');
}
catch (e) {
console.log('外部捕获', e);
}
// 内部捕获 a
// 外部捕获 b
//上面代码中,遍历器对象i连续抛出两个错误。第一个错误被Generator函数体内的catch语句捕获。
//i第二次抛出错误,由于Generator函数内部的catch语句已经执行过了,不会再捕捉到这个错误了,
//所以这个错误就被抛出了Generator函数体,被函数体外的catch语句捕获。
//注意:如果Generator函数内部和外部,都没有部署try...catch代码块,那么程序将报错,直接中断执行
59.throw
//throw方法被捕获以后,会附带执行下一条yield语句。也就是说,会附带执行一次next方法。
var gen = function* gen() {
try {
yield console.log('a');
}
catch (e) {
// ...
}
yield console.log('b');
yield console.log('c');
}
var g = gen();
g.next(); // a
g.throw() // b
g.next() // c
//上面代码中,g.throw方法被捕获以后,自动执行了一次next方法,所以会打印b。另外,也可以看到,
//只要Generator函数内部部署了try...catch代码块,那么遍历器的throw方法抛出的错误,不影响下一次遍历
60.return
//Generator函数返回的遍历器对象,还有一个return方法,可以返回给定的值,并且终结遍历Generator函数。后面的yield将不会再执行
// 如果Generator函数内部有try...finally代码块,那么return方法会推迟到finally代码块执行完再执行。
function* numbers() {
yield 1;
try {
yield 2;
yield 3;
}
finally {
yield 4;
yield 5;
}
yield 6;
}
var g = numbers();
g.next();// { done: false, value: 1 }
g.next(); // { done: false, value: 2 }
g.return(7); // { done: false, value: 4 }
g.next(); // { done: false, value: 5 }
g.next(); // { done: true, value: 7 }
//上面代码中,调用return方法后,就开始执行finally代码块,然后等到finally代码块执行完,再执行return方法。
61.yield语句
// yield*语句 在一个Generator函数里面执行另一个Generator函数。
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
yield 'a';
yield 'b';
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
for (let v of foo()) {
yield v;
}
yield 'y';
}
for (let v of bar()) {
console.log(v);
}
// "x"// "a"// "b"// "y"
62.yield*语句
function* inner() {
yield 'hello!';
}
function* outer1() {
yield 'open';
yield inner();
yield 'close';
}
var gen = outer1();
gen.next().value; // "open"
gen.next().value;// 返回一个遍历器对象 可以通过gen.next().value.next().value输出值
gen.next().value; // "close"
function* outer2() {
yield 'open';
yield* inner();
yield 'close';
}
// 从语法角度看,如果yield命令后面跟的是一个遍历器对象,需要在yield命令后面加上星号,
//表明它返回的是一个遍历器对象。这被称为yield*语句。
63.yield遍历
//如果yield*后面跟着一个数组,由于数组原生支持遍历器,因此就会遍历数组成员
function* gen() {
yield* ["a", "b", "c"];
}
gen().next(); // { value:"a", done:false }
//上面代码中,yield命令后面如果不加星号,返回的是整个数组,加了星号就表示返回的是数组的遍历器对象
//实际上,任何数据结构只要有Iterator接口,就可以被yield*遍历
let read = (function* () {
yield 'hello';
yield* 'hello';
})();
read.next().value // "hello"
read.next().value // "h"
//上面代码中,yield语句返回整个字符串,yield*语句返回单个字符。因为字符串具有Iterator接口,所以被yield*遍历
64.Promise
//基本用法
//then方法 的第一个参数是Resolved状态的回调函数,第二个参数(可选)是Rejected状态的回调函数
let promise = new Promise(function (resolve, reject) {
console.log('Promise');
resolve();
// reject("reject");
});
promise.then(function () {
console.log('Resolved.');
}, function (str) {
console.log(str);
}).then(function () {
console.log("one");
}, function () {
console.log("two");
});
console.log('Hi!');
//Promise Hi Resolved one
//简写:
Promise.resolve("hello");
Promise.reject("hi")
65.catch()
// Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获
let promise = new Promise(function (resolve, reject) {
resolve();
});
promise.then(
function () {
y+2;
return;
}
).then(function () {
x++;
return;
}).catch(function (error) {
console.log(error);
});
//一般来说,不要在then方法里面定义Reject状态的回调函数(即then的第二个参数),总是使用catch方法
66.catch()
//1.Promise.prototype.catch()就是.then(null,reject)的别名
//2.第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。
var p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000)
});
// p2.then因为参数是p1返回的是p1
p2.then(result => console.log("res")).catch(error => console.log(error));// Error: fail
67.Promise.all()
//Pomise.all的使用
//Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,
//成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
let p1 = new Promise((resolve, reject) => {
resolve('成功了')
})
let p2 = new Promise((resolve, reject) => {
resolve('success')
})
let p3 = Promise.reject('失败')
Promise.all([p1, p2]).then((result) => {
console.log(result) //['成功了', 'success']
}).catch((error) => {
console.log(error)
})
Promise.all([p1, p3, p2]).then((result) => {
console.log(result)
}).catch((error) => {
console.log(error) // 失败
})
68.Promise异步操作
//1.回调函数
fs.readFile('/etc/passwd', function (err, data) {
if (err) throw err;
console.log(data);
});
//上面代码中,readFile函数的第二个参数,就是回调函数,也就是任务的第二段。
//等到操作系统返回了/etc/passwd这个文件以后,回调函数才会执行
//2.Promise
//回调函数本身并没有问题,它的问题出现在多个回调函数嵌套。假定读取A文件之后,再读取B文件,代码如下。
fs.readFile(fileA, function (err, data) {
fs.readFile(fileB, function (err, data) {
// ...
});
});
//Promise就是为了解决这个问题而提出的。它不是新的语法功能,而是一种新的写法,允许将回调函数的嵌套,改成链式调用。
//采用Promise,连续读取多个文件,写法如下。
var readFile = require('fs-readfile-promise');
readFile(fileA).then(function (data) {
console.log(data.toString());
}).then(function () {
return readFile(fileB);
}).then(function (data) {
console.log(data.toString());
}).catch(function (err) {
console.log(err);
});
69.async和Promise
//async和Promise对比
function takeLongTime(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n)
});
}
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(n) {
console.log(`step2 with ${n}`);
return takeLongTime(n);
}
function step3(n) {
console.log(`step3 with ${n}`);
return takeLongTime(n);
}
//Promise方式
function doIt() {
console.time("doit");
const time1 = 300;
step1(time1)
.then(time2 => step2(time2))
.then(time3 => step3(time3))
.then(result => {
console.log(`result is ${result}`);
console.timeEnd("doit");
})
}
doIt();
//async方式
async function doSth() {
console.time("doSth");
const time1 = 300;
var time2 = await step1(time1);
var time3 = await step2(time2);
var result = await step3(time3);
console.log(`result is ${result }`);
console.timeEnd("doSth")
}
doSth();
70.语法
//1.正常情况下,await命令后面是一个Promise对象。如果不是,会被转成一个立即resolve的Promise对象。
async function f() {
return await 123;
}
f().then(v => console.log(v));// 123
//上面代码中,await命令的参数是数值123,它被转成Promise对象,并立即resolve。
//2.await命令后面的Promise对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到
async function f1() {
await Promise.reject('出错了');
}
f1().then(v => console.log(v)).catch(e => console.log(e));
//注意,上面代码中,await语句前面没有return,但是reject方法的参数依然传入了catch方法的回调函数。
//这里如果在await前面加上return,效果是一样的。
//3.只要一个await语句后面的Promise变为reject,那么整个async函数都会中断执行。
async function f2() {
await Promise.reject('出错了');
await Promise.resolve('hello'); // 不会执行
}
//上面代码中,第二个await语句是不会执行的,因为第一个await语句状态变成了reject。
//4.为了避免这个问题,可以将第一个await放在try...catch结构里面,这样第二个await就会执行
async function f3() {
try {
await Promise.reject('出错了');
}
catch (e) {
}
return await Promise.resolve('hello world');
}
f3().then(v => console.log(v));
//5.另一种方法是await后面的Promise对象再跟一个catch方面,处理前面可能出现的错误。
async function f5() {
await Promise.reject('error').catch(e => console.log(e));
return await Promise.resolve('hello.5');
}
f5().then(v => console.log(v));
//6.如果有多个await命令,可以统一放在try...catch结构中。
71.Class基本语法
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.foo = function () {
return (this.x + this.y);
}
let pt1 = new Point(1, 2);
console.log(pt1.foo());
class Dosth {
constructor(x, y) {
this.x = x;
this.y = y;
}
foo() {
return this.x + this.y;
}
}
let dt1 = new Dosth(12, 2);
console.log(dt1.foo());
//由于类的方法都定义在prototype对象上面,所以类的新方法可以添加在prototype对象上面。
//Object.assign方法可以很方便地一次向类添加多个方法。
Object.assign(Dosth.prototype, {
tostring(){},
tovalue(){}
});
//注:ES6中类的内部所有定义的方法,都是不可枚举的(non-enumerable)
72.Constructor
//constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。
//一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。例如, constructor() {}
//constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。
class Foo {
constructor() {
return Object.create(null);
}
}
new Foo() instanceof Foo;//false 输出对象为空
//注: 类的构造函数,不使用new是没法调用的,会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。
// Foo();
//注:Class不存在变量提升(hoist),这一点与ES5完全不同。
// new Foo1(); // ReferenceError
// class Foo1 {}
73.私有方法
//私有方法:类的外部无法看到这个方法,包括不同命名空间的、同一命名空间不同的类和这个类的子类,都不能看到这个方法。私有方法是常见需求,但ES6不提供,只能通过变通方法模拟实现
//1.一种方法就是索性将私有方法移出模块,因为模块内部的所有方法都是对外可见的。
class Widget {
foo(baz) {
bar.call(this, baz);
}
// ...
}
function bra(baz) {
return this.snaf = baz;
}
//2.利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值
const bar = Symbol('bar');
const snaf = Symbol('snaf');
class Dosth {
// 公有方法
foo(baz) {
return this[bar]('baz');
}
//私有方法
[bar](baz) {
this[snaf] = baz;
return this[snaf]
}
}
let _dosth = new Dosth();
console.log(_dosth.foo());
console.log(_dosth[bar]('a'));
// 上面代码中,bar和snaf都是Symbol值,导致第三方无法获取到它们,因此达到了私有方法和私有属性的效果。
74.this指向
//类的方法内部如果含有this,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。
class Logger {
printName(name = 'there') {
this.print(`Hello ${name}`);
}
print(text) { //print()window下面的方法,打印内容
console.log(text);
}
}
const logger = new Logger();
const { printName } = logger;
printName();
//上面代码中,printName方法中的this,默认指向Logger类的实例。但是,如果将这个方法提取出来单独使用,
//this会指向该方法运行时所在的环境,因为找不到print方法而导致报错。
//1.一个比较简单的解决方法是,在构造方法中绑定this,这样就不会找不到print方法了。
class Logger {
constructor() {
this.printName = this.printName.bind(this);
}
// ...
}
//2.使用箭头函数
class Logger {
constructor() {
this.printName = (name = 'there') => {
this.print(`Hello ${name}`);
};
}
// ...
}
//3.使用Proxy,获取方法的时候,自动绑定this
function selfish(target) {
const cache = new WeakMap();
const handler = {
get(target, key) {
const value = Reflect.get(target, key);
if (typeof value !== 'function') {
return value;
}
if (!cache.has(value)) {
cache.set(value, value.bind(target));
}
return cache.get(value);
}
};
const proxy = new Proxy(target, handler);
return proxy;
}
const logger = selfish(new Logger());
75.继承
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
// this.color = color; // ReferenceError
super(x, y);// 调用父类的constructor(x, y) 注意:是方法,会直接运行父类构造函数
this.color = color; // 正确
}
}
let cp = new ColorPoint(25, 8, 'green');
cp instanceof ColorPoint // true
cp instanceof Point // true
console.log(cp.__proto__,cp.__proto__.__proto__);//分别指向ColorPoint,Point
76.类的prototype和proto
//1.子类的__proto__属性,表示构造函数的继承,总是指向父类
//2.子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
class A {
}
class B extends A {
}
B.__proto__ === A;//true //实例化对象也是指向类A
B.prototype.__proto__ === A.prototype;//true
//上面代码中,子类B的__proto__属性指向父类A,子类B的prototype属性的__proto__属性指向父类A的prototype属性
//这样的结果是因为,类的继承是按照下面的模式实现的。
class A {
}
class B {
}
// B的实例继承A的实例
Object.setPrototypeOf(B.prototype, A.prototype);
// B继承A的静态属性
Object.setPrototypeOf(B, A);
77.类的静态方法
//类相当于实例的原型,所有在类中定义的方法,都会被实例继承。
//静态方法
//1.如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
class Foo {
static classMethod() {
return 'hello';
}
}
//静态方法可以直接调用,而不是在Foo类的实例上调用
Foo.classMethod(); //'hello'
let foo = new Foo();
// foo.classMethod(); // TypeError: foo.classMethod is not a function
//2.父类的静态方法,可以被子类继承,子类实例化对象可以直接调用。
class Bar extends Foo {
static Dosth(){
return super.classMethod()+" static";
}
}
Bar.classMethod(); // 'hello'
Bar.Dosth();//"hello,static"
78.静态属性和实例属性
//1.静态属性
//静态属性指的是Class本身的属性,即Class.propname,而不是定义在实例对象(this)上的属性。
class Student {
static age = 18;//ES7提出
}
//注:如果同时定义了相同属性,会输出外边的属性
Student.sex = "girl";
console.log(Student.age);
//2.实例属性
//类的实例属性可以用等式,写入类的定义之中
class Person {
myProp = 42;
constructor() {
console.log(this.myProp); // 42
}
}
79.new target属性
//ES6为new命令引入了一个new.target属性,(在构造函数中)返回new命令作用于的那个构造函数
//1.定义:如果构造函数不是通过new命令调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的
function Person(name) {
if (new.target !== undefined) {
this.name = name;
}
else {
throw new Error('必须使用new生成实例');
}
}
// 另一种写法
function Person(name) {
if (new.target === Person) {
this.name = name;
}
else {
throw new Error('必须使用new生成实例');
}
}
var person = new Person('张三'); // 正确
// var notAPerson = Person.call(person, '张三'); // 报错
//2.特点:Class内部调用new.target,返回当前Class
class Rectangle {
constructor(length, width) {
console.log(new.target === Rectangle);
this.length = length;
this.width = width;
}
}
var obj = new Rectangle(3, 4); // 输出 true
//需要注意:子类继承父类时,new.target会返回子类
class Square extends Rectangle {
constructor(length) {
super(length, length);
}
}
var obj = new Square(3); // 输出 false
//3.应用:利用这个特点,可以写出不能独立使用、必须继承后才能使用的类
class Shape {
constructor() {
if (new.target === Shape) {
throw new Error('本类不能实例化');
}
}
}
class Rect extends Shape {
constructor(length, width) {
super();
// ...
}
}
// var x = new Shape(); // 报错
var y = new Rect(3, 4); // 正确
//上面代码中,Shape类不能被实例化,只能用于继承。
//注意,在函数外部,使用new.target会报错。
80.export
//模块功能主要由两个命令构成:export和import。
//export命令用于规划模块的对外接口
//import命令用于输出其他模块提供的功能
//1.export有三种写法(as写法)
//第一种
export var firstName = "yang";
export var lastName = "wen";
//第二种
var firstAge = 18;
var lastAge = 22;
export { firstAge, lastAge }
//2.export命令除了输出变量,还可以输出函数或类(class)。
export function foo(x, y) {
return x + y;
}
//3.通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名。
function v1() {
// ...
}
function v2() {
// ...
}
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
//上面代码使用as关键字,重命名了函数v1和v2的对外接口。重命名后,v2可以用不同的名字输出两次
//4.export命令可以出现在模块的任何位置,只要处于模块顶层就可以。
//如果处于块级作用域内,就会报错,下一节的import命令也是如此
81.import
// 1.js
var a = 1;
var b = function () {
console.log(a);
}
var c = 3;
var d = a + c;
var obj = { a, b, c }
export { a, b };
export { c, d };
export default obj;
//注:变量定义的值必须与模块中的值一致
import { a, b } from "./199.exports.js"; // 导入export
import m1 from "./199.exports.js"; // 不加{}即导入export default
import { c } from "./199.exports.js"; // 导入export 按需导入
import * as m from "./199.exports.js"
console.log(a); // 1
console.log(b); // ƒ (){ console.log(a); }
console.log(m1); // {a: 1, c: 3, b: ƒ}
console.log(c); // 3
//export default命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此export deault命令只能使用一次。
82.语法
2.1 转码结果输出到标准输出(控制台输出)
$ babel example.js
2.2 转码结果写入一个文件
--out-file or -o 参数指定输出文件
$ babel example.js --out-file compiled.js
$ babel example.js -o compiled.js
2.3整个目录转码
--out-dir or -d 参数指定输出目录
$babel src --out-dir lib
$babel src -d lib