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;
}
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]