目录
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错
Object.defineProperty()与Proxy的异同
ECMAScript 6 简介
ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。
它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
ECMAScript与JS的关系
要讲清楚这个问题,需要回顾历史。1996 年 11 月,JavaScript 的创造者 Netscape 公司,决定将JavaScript 提交给标准化组织 ECMA,希望这种语言能够成为国际标准。次年,ECMA 发布 262 号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript,这个版本就是 1.0 版。
该标准从一开始就是针对 JavaScript 语言制定的,但是之所以不叫 JavaScript,有两个原因。一是商标,Java 是 Sun 公司的商标,根据授权协议,只有 Netscape 公司可以合法地使用 JavaScript 这个名字,且 JavaScript 本身也已经被 Netscape 公司注册为商标。二是想体现这门语言的制定者是 ECMA,不是 Netscape,这样有利于保证这门语言的开放性和中立性。
因此,ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现(另外的ECMAScript 方言还有 JScript 和 ActionScript)。日常场合,这两个词是可以互换的。
Babel 转码器
Babel是一个广泛使用的 ES6 转码器,可以将 ES6 代码转为 ES5 代码,从而在老版本的浏览器执行。这意味着,你可以用 ES6 的方式编写程序,又不用担心现有环境是否支持。下面是一个例子。
上面的原始代码用了箭头函数,Babel 将其转为普通函数,就能在不支持箭头函数的 JavaScript 环境执行了。
let和const命令
let的定义
ES6 新增了let命令,用来声明变量,它的用法类似于var
let 和var的不同
1、变量提示
let不存在变量提升 ---> 报错
var 存在变量提升 -----> underfined
console.log(a); //a is not defined
let a = 1;
console.log(a); // undefined
var a = 1;
2、同一个作用域内不能重复定义同一个名称
let在同一个作用域内不能重复定义同一个名称
var可以在同一个作用域内重复定义同一个名称,但是后面的会覆盖前面的
let a = 10
let a = 20
console.log(a) // Identifier 'a' has already been declared
var a = 10;
var a = 20;
console.log(a); // 20
// 同一个作用域内不能重复定义同一个名称,作用域有全局作用域,局部作用域,函数作用域,在es6又多了一个块级作用域
let a = 10;
if(true){
let a = 30;
//let a = 40;
};
//let a = 20;
console.log(a); // 10
3、有着严格的作用域
let 块级作用域
var是函数作用域
function fun() {
let a = 10;
if (true) {
let a = 20;
}
console.log(a); //10
}
fun();
function fun() {
var a = 10;
if (true) {
var a = 20;
}
console.log(a); //20
}
fun();
// let a = 10
// a = 20
// console.log(a) // 20
// 坑:
function f(i){
let i = 10;
console.log(i) //Identifier 'i' has already been declared
};
f(100);
// 原因: 相当于传入进去的参数,在函数内部新定义了一个 i = 100,同时里面还有一个let i = 10,重复定义了,如果内部那个let i = 10改为var i = 10就不会报错
4、块级作用域的重要性
for(var i = 0;i<5;i++){}
console.log(i); //var i=0; var i = 1; var i = 2....var i = 5;
// 返回5,var 可以把前面的都给覆盖掉
for(let i = 0;i<5;i++){}
console.log(i); //i is not defined
var arr = [];
for(let i=0; i<5; i++){
arr[i] = function(){
console.log(i)
}
};
arr[4](); //var 结果是5 let 结果是4
补充:
for循环是怎么执行的
第一步: let i=0; 第二步:i<5; 第三步:循环体 第四步:i++ 第五步:i加完了之后继续第二步,再进入循环体,再i++.... 直到结果不满足了跳出来
前++ 和 后++的区别
var a = 5;
b = a++; //先赋值,再加 b = a; a = a+1;
console.log(b);
c = ++a; //先自加,再赋值 a = a+1; c = a;
5、暂时性死区
//坑1
function f(i){
let i = 10;
console.log(i) //Identifier 'i' has already been declared
};
f(100);
//坑2
function fun(){
let a = 10;
if(true){
a = 20;
console.log(a);
let a; //只要在块级作用域下let来声明,在当前作用域下不能声明同样的名称
};
};
fun()
//坑3
let a = b, b = 100;
function f(){
console.log(a,b)
}
f();
// 原因: 函数体内,a = b,b有了吗?
const 的定义
const声明一个只读的常量。一旦声明,常量的值就不能改变。
const也不存在变量提升,同一个作用域内不能重复定义同一个名称,有着严格的作用域;
// 修改常量的值会报错
const x = 12345;
x = 999; //error
const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
const y; //error
只声明不赋值,就会报错
var a = 10;
let b = 20;
const a = 100; //error 原因: 同一个作用域下不能有相同的名字,而且对应的值不能修改
const b = 200; //error 原因: 同一个作用域下不能有相同的名字,而且对应的值不能修改
console.log(a,b)
const 的坑
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
常量obj储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把obj指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。
const obj = {};
obj.name = 'abc';
obj['age'] = 18;
console.log(obj); //{ name: 'abc', age: 18 }
obj = {id:10} ; //obj指向另一个地址,是不可行的
常量arr是一个数组,这个数组本身是可写的,但是如果将另一个数组赋值给arr,就会报错。
const arr = [];
arr.push(1);
arr[1] = 2;
// arr = ['a'] //但是如果将另一个数组赋值给arr,就会报错
解构
什么是解构
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
解构是ES6的新特性, 比ES5代码简洁,清晰,减少代码量
es5中的为变量赋值,只能直接指定值.
var a=1;
var b=2;
var c=3;
数组解构
匹配模式: 左边是变量 =(匹配) 右边对应的值
let [a,b,c] =[1,2,3]; // 这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值
// 相当于:
var {a,b,c} = {a:1,b:2,c:3};
var obj = {
a:1,
b:2,
c:3
};
var a = obj.a;
var b = obj.b;
var c = obj.c;
var a = [1,2,3];
var b = {id:2};
var c = null;
let [a,b,c] = [[1,2,3],{id:2},null];
let [a,b] = [1] //匹配不成功 变量的值就等于undefined
let [a,b] = [1,2,3] //不完全解构
解构赋值允许指定默认值默认值
let [a=1,b=1] = [10,20];
var arr = [10,20];
let [a=1,b=1] = arr;
a = arr[0] || 1;
b = arr[1] || 1;
let [a=1,b=2] = [10]; //a=10,b = 2;
let [a=1,b=2] = [10,null]; //a = 10;b = null
let [a,b=2] = [10]; //a=10,b = 2;
// 例外 成员等于undefined 默认值会生效
let [a,b=2] = [10,undefined]; //a=10,b=2
惰性求值
如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值
function f(){
return '12345'
}
var [x = f()] = [1]; //x=1
var [x = f()] = []; //x='12345'
var [x = f()] = [undefined]; //x='12345'
扩展
let [a] = [];
let [a,b] = [[{id:i},{id:2}],null];
let [a,[b],c] = [1,[2],3];
// var pageNo = 本地存储 ?本地存储 :1;
// var pageNo = 本地存储 || 1;
对象解构
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
对象解构的写法
对象解构的写法:
var {a,b,c} = {a:1,b:2,c:3};
var obj = {
a:1,
b:2,
c:3
};
var a = obj.a;
var b = obj.b;
var c = obj.c;
实际的运用
var res = {
code: 200,
msg: "success",
result: [1, 2, 3, 4],
obj: { id: 1 },
arr: [{ name: 1 }],
};
// es5从对象中值得方式
var code = res.code;
var result = res.result;
var obj = res.obj;
// es6通过解构更简洁,清晰
let { code, obj, result } = res;
result[2]
obj.id
扩展题
// 传入整个对象
function fun(str, res) {
console.log(code);
}
fun(1, res);
// 根据需要解构获取需要的值
function fun(str, { code, obj }) {
console.log(code);
console.log(obj);
}
fun(1, res);
别名
全局引入的变量与组件中的变量重名,那么就需要起别名
// 别名 a:q a是匹配模式 q才是变量
var { a:q, b, c } = { a:1, b:2, c:3 };
var { a:a2, b:b2, c:c2 } = { a:1, b:2, c:3 };
console.log(a2)
console.log(b2)
题目:
let {a,b:y} = {a:1,y:2}
// 变量是谁 a y
// 匹配结果: a = 1,y= undefined
解构赋值允许指定默认值默认值
默认值生效的条件是:对象的属性值严格等于undefined
let {x=3} = {x:10} // x = 10 如果有匹配的结果,那么优先选择匹配的结果,规律和数组一样,只有匹配的结果为undefined的时候,我们才会去让默认值生效,否则默认值根本不可能去用到
let {x, y = 10} = {x:10} // x=20,y=10
let {x=1,y=10}={x:undefined} // x=1,y=10
let {x:n=1,y:a=2} = {x:10,y:20}
// 变量 n a
// 匹配结果 n = 10, a = 20
let {x:n=1,y:a=2} = {x:10,y:undefined} // n=10,a=2
注意:
如果要将一个已经声明的变量用于解构赋值,必须非常小心。
// 错误写法:
let x
{x} = {x:1}
// 报错的原因:因为 JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在 行首,避免 JavaScript 将其解释为代码块,才能解决这个问题
// 正确的写法:
let x;
({x} = {x: 1});
// 将整个解构赋值语句,放在一个圆括号里面,就可以正确执行
字符串解构
let[a,b,c,d,e,f]='nodejs';
console.log(a) // n
console.log(b) // o
函数参数解构
解构在函数中具体运用
数组解构
function add([x, y]){
return x + y;
}
add([1, 2]); // 3
// 匹配方式 [x, y] = [1, 2]
对象解构
function move({x, y}) {
return x +y;
}
move({x: 3, y: 8});
// 匹配方式 {x, y} = {x: 3, y: 8}
参数默认值的定义
// 数组: 当实参为空时,写入默认值
function fun([x=0, y=0]){
console.log(x,y)
return x + y;
}
fun([2, 4]);
// 匹配方式 [x=0, y=0] = [2, 4] // 2,4
fun([2]);
// 匹配方式 [x=0, y=0] = [2] // 2,0
fun();
// 匹配方式 [x=0, y=0] = [x=0, y=0] // 0,0
fun([4]);
// 匹配方式 [x=0, y=0] = [4] // 4,0
fun([,4]);
// 匹配方式 [x=0, y=0] = [,4] // 0,4
// 对象: 当实参为空时,写入默认值
function fun({x=0, y=0}){
return x + y;
}
fun({x:3, y:5});
// 匹配方式 {x=0, y=0} = {x:3, y:5}
扩展题
函数参数的解构也可以使用默认值
function move({x = 0, y = 0} = {}) {
return [x, y];
}
move({x: 3, y: 8});
// 匹配方式 {x = 0, y = 0} = {x: 3, y: 8} // [3,8]
move({x: 3});
// 匹配方式 {x = 0, y = 0} = {x: 3} //[3,0]
move({});
// 匹配方式 {x = 0, y = 0} = {} // [0,0]
move();
// 匹配方式 {x = 0, y = 0} = {} // [0,0] 什么都没传入的时候,{x = 0, y = 0} = {}就生效了
{x, y} = { x: 0, y: 0 }
形参 = 实参
function move({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
move({x: 3, y: 8});
// 匹配方式 {x, y} = {x: 3, y: 8} // [3,8]
move({x: 3});
// 匹配方式 {x, y} = {x: 3} // [3,undefined]
move({});
// 匹配方式 {x, y} = {} // [undefined,undefined]
move();
// 匹配方式 {x, y} = { x: 0, y: 0 } // [0,0]
扩展
字符串的扩展
字符串常用方法
记一个方法:从三个地方记: 1. 记意思,2. 记参数 3.记返回值
charAt(); //返回指定索引位置的字符
var str = "hello world"; // str.charAt(2)
str.charAt(2);
indexOf(); //返回字符串中检索指定字符第一次出现的位置
var str = "hello world";
str.indexOf("w");
lastIndexOf(); //返回字符串中检索指定字符最后一次出现的位置
var str = "hello world";
str.lastIndexOf("w");
slice(); //提取字符串的片断,并在新的字符串中返回被提取的部分
split(); //把字符串分割为子字符串数组
var str = "hello world";
str.split("");
toLowerCase(); //把字符串转换为小写
toUpperCase(); //把字符串转换为大写
substr(); //从起始索引号提取字符串中指定数目的字符
substring(); //提取字符串中两个指定的索引号之间的字符
ES6新增
includes(); // 返回布尔值,表示是否找到了参数字符串。
var str = 'hello world'
str.includes('h') // true
if(str.indexOf('h') != -1)
// includes在模糊查询的时候有用
// 模糊查询
var arr = ['abc','sxg','asf','fdbbf','bfab','ravb'] // 查询的列表数据
var input = 'a' // 输入的条件
var select = arr.filter(function(item){
// return item.includes(input)
return item.indexOf(input) != -1 // ==-1不匹配 !=-1匹配
})
console.log(select)
// 如果存在了分页,模糊查询就不能放在前端,这个时候就必须由后端做,也就是说,后端如果能一次性把数据给我们,我们前端就可以做,否则需要后端做
startsWith(); // 返回布尔值,表示参数字符串是否在原字符串的头部。
var str = 'hello world'
str.startsWith('h') // true
str.startsWith('h',4) // false 第二个参数表示当前搜索的位置
str.startsWith('H') // false 区分大小写
str.startsWith('hello') // true
str.startsWith('world') // false
endsWith(); //返回布尔值,表示参数字符串是否在原字符串的尾部
repeat(); //返回一个新字符串,表示将原字符串重复n次。
var str = 'hello'
str.repeat(2)
padStart()// 用于头部补全
var str = 'ok'
str.padStart(5,'abc') // 5表示补全的长度,'abc'表示补全的内容
var str = 'ok'
str.padStart(4,'abc')
var str = 'ok'
str.padStart(7,'abc')
// 在后端给我们返回过来的数据中,我们需要添加东西的时候,我们可以使用到这个,比如补单位什么的
padEnd(); //用于尾部补全。
函数的扩展
函数默认参数
// ES5
function fun(x,y){
y = y || '10';
console.log(x,y); // 123,'10'
};
fun(123);
// ES6
function fun(x,y ="10"){
console.log(x,y); // 123,'10'
};
fun(123);
rest参数
...变量名
ES6 引入 rest 参数(形式为 ...变量名 ),用于获取函数的多余参数,这样就不需要使用 arguments 对象了。
function fun(...item) {
// console.log(arguments[1])
console.log(item) // [1,2,3,4,5,6]
}
fun(1,2,3,4,5,6)
获取具体参数值可以通过item[2]
注意: rest参数之后不能再有其他参数(即只能是最后一个参数),否则会报错
function fun(a,...b) {
console.log(a) // 1
console.log(b) // [2,3,4]
}
fun(1,2,3,4)
function fun(a,b,...c) {
console.log(a) // 1
console.log(b) // 2
console.log(c) // [3,4]
}
fun(1,2,3,4)
报错
function fun(a,...b,c){}
fun(1,2,3,4)
扩展题
function fun(a,b,...c) {
c.push(a)
console.log(c) // [3,4,1]
}
fun(1,2,3,4)
// for...in...主要是用来循环遍历对象的
// for...of...
function add(...values) {
var sum = 0
for(var v of values) {
sum+=v
}
return sum
}
var n = 10
add(n,1,2,3) // sum = 16
箭头函数
ES6 允许使用“箭头”( => )定义函数。
let f = v => v // 变量名 = 参数 => 函数体
// 等同于
var f = function(v) {
return v
}
参数为空
let f = () => 12345
// 等同于
var f = function(v) {
return 12345
}
参数多个
let f = (n1,n2) => n1+n2
// 等同于
var f = function(n1,n2) {
return n1+n2
}
返回对象
let f = (n1,n2) => ({name:n1,age:n2})
其他写法
var f = function(n1,n2) {
list(n2)
}
// 等同于
var f = (n1,n2) => {list(n2)}
var a = [1,2,3,4].filter(function(item) {
return item>3
})
// 等同于
var a = [1,2,3,4].filter(item=>item>3)
默认值
function fun(x,y ="10"){
console.log(x,y);
};
// 等同于
var fun = (x,y='10') => {console.log(x,y)}
fun(123)
解构中的运用
function move({x=0,y=0}={}){
return [x,y]
}
// 等同于
var move = ({x=0,y=0}={}) => [x,y]
move({x:3,y:8})
rest参数
var fun = function(...item) {
return item
}
// 等同于
var fun = (...item) => item
fun(1,2,3,4,5)
扩展题
如果函数体返回对象,添加()
function fun(a,b){
return {name:a,id:b}
}
var fun = (a,b) => ({name:a,id:b})
fun(1,2)
箭头函数有几个使用注意点
(1) 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象
(2)不可以当做构造函数,也就是说,不可以使用new命令,否则会抛出一个错误
(3)不可以使用arguments对象,该对象在函数体内不存在,如果要用,可以用rest参数代替
1.箭头函数不能当做构造函数,不可以使用new 命令
// 箭头函数中,this指向固定化,本身是没有自己的this,所以不能用作构造函数
var fun = () => {
this.name = '1'
}
var f1 = new fun() // fun is not a constructor
2.箭头函数没有原型 对象
function fun(){}
fun.prototype
var fun2 = () => {}
fun.prototype
3.不可以使用arguments对象,该对象在函数体内不存在 替换rest
var fun = () => {
console.log(arguments[1]) // arguments is not defined
}
fun(1,2,3)
// 替代rest
var fun = (...item)=>{ console.log(item[1]) };
fun(1,2,3);
4. this指向 由于箭头函数不绑定this,它会捕获所在上下文的this的值,作为自己的this值
var str = 'global'
var obj = {
str:'private',
getStr:function() {
console.log(this.str)
}
}
obj.getStr() // 运行者 (调用者) 为obj, this指向obj
var a = obj.getStr
a()
var str = 'global'
var obj = {
str:'private',
getStr:() => {
console.log(this.str) // 绑定的是定义者
// 箭头函数的表现为getStr的value,它被定义在obj对象中,obj的执行上下文就是window
// this.str 实际就是window.str 输出'global'
}
}
obj.getStr()
5、call() apply() bind() 对于this毫无影响
var n = 10
function f(){
return this.n
}
var obj = {
n:8,
f2:f
}
var obj2 = {
n:99
}
console.log(obj.f2.call(obj2)) // 99
// 箭头函数
var n = 10
var f=()=> this.n
var obj = {
n:8,
f2:f
}
var obj2 = {
n:99
}
console.log(obj.f2.call(obj2)) // 10
扩展运算符
扩展运算符(spread)是三个点(...)。
它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
该运算符主要用于函数调用。
主要运用
数组的合并
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
let arr3 = [...arr1, ...arr2];
// ...[1,2,3] = 1,2,3
// 或者直接arr1.push(...arr2);
参数
function add(x, y) {
return x + y;
}
const numbers = [4, 5];
add(...numbers);
扩展运算符与正常的函数参数可以结合使用,非常灵活
rest参数与扩展运算符的结合
function f(...items) {
// ...items rest参数
console.log(items); // [-1,0,1,2,3]
console.log(...items); // -1,0,1,2,3
}
const args = [0, 1];
f(-1, ...args, 2, ...[3]); // f(-1,0,1,2,3)
扩展运算符可以与解构赋值结合起来,用于生成数组
const [first, ...rest] = [1, 2, 3, 4, 5];
first; // 1
rest; // [2, 3, 4, 5]
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错
const [...butLast, last] = [1, 2, 3, 4, 5];// 报错
const [first, ...middle, last] = [1, 2, 3, 4, 5];// 报错
在字符串中的应用
扩展运算符还可以将字符串转为真正的数组
// ES5的用法
var s1 = "hello";
console.log(s1.split(""));
// ES6的用法
[..."hello"]; // ['h','e','l','l','o']
深拷贝浅拷贝
let a = [1, 2];
let a2 = [...a];
a2[0] = 100;
console.log(a);
对象中的运用
let o1 = { id: 1 };
let o2 = { name: 2 };
let o3 = { ...o1, ...o2 }; // {id:1,name:2}
let o4 = { ...o1, o2 }; // {id:1,o2:{name:2}}
对象中合并函数
let fun = () => 123;
let obj4 = { ...obj3, fun };
obj4 = {
id: 1,
name: 2,
fun: function () {
return 123;
},
};
// 应用场景:
// setup(prop,{root}){
// var state = '',
// return {
// ...state,
// fun
// }
// }
数值的扩展
parseInt(); //函数可解析一个字符串,并返回一个整数。
parseFloat(); //函数可解析一个字符串,并返回一个浮点数
Number.isInteger(); //用来判断一个数值是否为整数。
Math.ceil(); //返回大于或等于一个给定数字的最小整数
Math.floor(); //返回小于或等于一个给定数字的最大整数
Math.round(); //返回一个数字四舍五入后最接近的整数
Math.trunc(); //用于去除一个数的小数部分,返回整数部分。
Math.sign(); //方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。
// ES5
// parseInt(); // 函数可解析一个字符串,并返回一个整数。
parseInt("11.111"); // 11
// ES6
// Number.parseInt();
Number.parseInt("11.11"); // 11
parseFloat(); //函数可解析一个字符串,并返回一个浮点数
Number.isInteger(); //用来判断一个数值是否为整数。 返回true false
Number.isInteger(11); // true
Number.isInteger(11.0); // true
Number.isInteger(11.11); // false
Math.ceil(); //返回大于或等于一个给定数字的最小整数
Math.ceil(11.11); // 12 上舍入
Math.floor(); //返回小于或等于一个给定数字的最大整数
Math.floor(11.11); // 11 下舍入
Math.round(); //返回一个数字四舍五入后最接近的整数
Math.round(11.6); // 12 四舍五入
Math.trunc(); //用于去除一个数的小数部分,返回整数部分。
Math.trunc(4.1); // 4
Math.trunc(-4.9); // -4
Math.sign(); //方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。
// 五种值
// 正数,返回+1
// 负数,返回-1
// 0 返回0
// -0 返回-0
// 其他值 返回NaN
Math.sign(6); // 1
Math.sign(-6); // -1
Math.sign(0); // 0
Math.sign("abc"); // NaN
数组的扩展
Array.of()
定义:用于将一组值,转换为数组
Array.of(); //[]
Array.of(1); //[1]
Array.of(1,2,3); //[1, 2, 3]
Array.of(4,5).length; //2
copyWithin()
定义:将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组(会修改当前数组)
接受三个参数copyWithin(target, start , end)
target(必需):从该位置开始替换数据。如果为负值,表示倒数。
start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。
end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。
[1,2,3,4,5].copyWithin(0,3) // [4, 5, 3, 4, 5]
[1,2,3,4,5,6,7,8].copyWithin(0,3) // [4, 5, 6, 7, 8, 6, 7, 8]
[4,2,3,4,5].copyWithin(0,3,4) // [4, 2, 3, 4, 5]
[4,2,3,4,5].copyWithin(0,-2,-1) // [4, 2, 3, 4, 5]
find()
它返回的是具体的成员
find()用于找出第一个符合条件的数组成员。它的参数是一个回调函数,,所有数组成员依次执行该回调
函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。
find方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组
var arr = [3,4,7,9];
var a = arr.find(function(item,index,arr){ //用于找出第一个符合条件的数组成员
return item >5
});
console.log(a); // 7
// 另一种写法
[1, 11, 12, 10].find(n => n > 5) //11
findIndex()
它的用法其实和find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1,不同的是它返回的是位置,也就是索引,find返回的是具体的值
var arr = [3,4,7,9];
var a = arr.find(function(item,index,arr){ //返回第一个符合条件的数组成员的位置
return item >5
});
console.log(a); // 2
fill()
定义:用于将一个固定值替换数组的元素。
array.fill(value, start, end)
value(必需):填充的值。
start(可选):开始填充位置。
end(可选):停止填充位置 (默认为 array.length)。
var arr = ["blue", "Orange", "red", "green"];
arr.fill("abc", 2, 4); //["blue", "Orange", "abc", "abc"]
includes()
定义:方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的 includes 方法类似
字符串中的那个之前我们还用到模糊查询,字符串和数组中都有这个方法
[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false
var arr = ["blue", "Orange", "red", "green"];
arr.includes('blue'); //true
该方法的第二个参数表示搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。
[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true
flat()
定义:用于将嵌套的数组“拉平”,变成一维的数组。
[1, 2, [3, 4, 6],[7, 8]].flat(); //[1, 2, 3, 4, 6, 7, 8]
该方法返回一个新数组,对原数据没有影响。
var arr = [1, 2, [3, 4, 6],[7, 8]];
var a = arr.flat();
console.log(arr); //[1, 2, [3, 4, 6],[7, 8]];
console.log(a); // [1, 2, 3, 4, 6, 7, 8]
默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组,可以将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1。
flat()的参数为2,表示要“拉平”两层的嵌套数组
[1, 2, [3, [4, 6]],[7, 8]].flat(2);//[1, 2, 3, 4, 6, 7, 8]
[1, 2, [3, [4, [6]]],[7, 8]].flat(2); //[1, 2, 3, 4, [6], 7, 8]
如果不管有多少层嵌套,都要转成一维数组,可以用Infinity关键字作为参数。
[1, 2, [3, [4, [6]]],[7, 8]].flat(Infinity); //[1, 2, 3, 4, 6, 7, 8]
对象的扩展
属性名表达式
JavaScript 定义对象的属性,有两种方法。
var x = 'name'
let obj = {
[x]:'abc', // 变量
['age']:20,
// age:20, // 字符串
['get'+x]:'xyz' // 拼接+变量
}
console.log(obj) // {name: 'abc', age: 20, getname: 'xyz'}
// 另一种写法:
var obj2 = {}
obj2.id = 1
obj2['x'] = 'x'
obj2['set'+'name'] = 'abc'
console.log(obj2) // {id: 1, x: 'x', setname: 'abc'}
链判断运算符
判断一个对象中的属性是否存在
var obj3 = {
o:{
o1:{
o2:{
o3:'abc'
}
}
}
}
obj3.o.o1.o2.o3
// 安全的写法
var a = (obj3 && obj3.o && obj3.o.o1 && obj3.o.o1.o2 && obj3.o.o1.o2.o3) || 1
if(res && res.data && res.data.length > 0) {}
// 这样的层层判断非常麻烦,引入了“链判断运算符” ?.,简化上面的写法。
var a = (obj3 ?.o ?.o1 ?.o2 ?.o3) || 1
?.运算符常见形式,以及不使用该运算符时的等价形式。
a?.b
// 等同于
a == null ? undefined : a.b
a?.[x]
// 等同于
a == null ? undefined : a[x]
a?.b()
// 等同于
a == null ? undefined : a.b()
a?.()
// 等同于
a == null ? undefined : a()
let obj4 = {
id:1,
name:'abc',
getName:function() {
return this.name
}
}
if(obj4?.getName()){
console.log(1)
}else {
console.log(2)
}
Set数据结构
定义
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
JS数据结构 Array Object Set Map
Set 本身是一个构造函数,用来生成 Set 数据结构。
var arr1 = new Array()
var arr2 = [3,4,5,6,7,8]
arr2.push(1)
arr2[1] = 2
console.log(arr2) // [3, 2, 5, 6, 7, 8, 1]
var obj = new Object()
var obj2 = {}
var s = new Set()
var set = new Set()
set.add(1)
set.add(2)
set.add(3).add(4).add(5).add(5)
// {1, 2, 3, 4, 5}
set.add(3).add(4).add(5).add('5')
// {1, 2, 3, 4, 5,'5'}
// 另一种定义方式
var set2 = new Set([3,4,5,6,7,8])
console.log(set2) // {3,4,5,6,7,8}
Set 实例的属性和方法
1、add(value) 添加某个值,返回 Set 结构本身。
const set = new Set();
set.add(1).add(2).add(3).add(3);
2、delete(value) 删除某个值,返回一个布尔值,表示删除是否成功。
const set = new Set();
set.add(1).add(2).add(3).add(3);
set.delete(2)
3、clear() 清除所有成员,没有返回值。
const set = new Set();
set.add(1).add(2).add(3).add(3);
set.clear()
4、has(value); 返回一个布尔值,表示该值是否为 Set 的成员。
const set = new Set();
set.add(1).add(2).add(3).add(3);
set.has(3);
5、size属性:返回 set结构的成员总数
const set = new Set();
set.add(1).add(2).add(3).add(3);
set.size; //4
类型转换
var arr3 = [1,2,3,4,4,3,2,2,5,6,7,4,3,2,7,5]
// 1. ...扩展运算符
var arr4 = [...new Set(arr3)] // [1,2,3,4,5,6,7]
// 2.Array.from()
var arr5 = Array.from(new Set(arr3)) // [1,2,3,4,5,6,7]
遍历方法
const set = new Set([2,3,52,3,4,43,3]);
for(let v of set){
console.log(v)
}
set.forEach((v,i)=>{
console.log(v)
});
扩展题
求出大于20的数据且去重处理
var arr6 = [10,23,42,23,14,23,42,23,10,55,23,43,42,55,67,42,20,20]
var x = [...new Set(arr6.filter(v=>v>20))]
console.log(x)
解题思路:
// 1.求出大于20
// var max = arr6.filter(function(v){
// return v>20
// })
var max = arr6.filter(v=>v>20) // [23,42,23,23,42,23,55,23,43,42,55,67,42]
// 2.去重
var y = new Set(max) // {23,42,55,43,67}
// 3.类型转换
Array.from(y); // [23,42,55,43,67]
[...y]
// 4.合并代码
var x2 = Array.from(new Set(Arr6.filter(v=>v>20)))
var x3 = [...new Set(arr6.filter(v=>v>20))]
数组去重
var arr3 = [1,2,3,4,4,3,2,2,5,6,7,4,3,2,7,5]
var s = new Set(arr3) // {1,2,3,4,5,6,7}
// es5 数组去重
function f() {
var arr = []
for(var i = 0;i<arr3.length;i++) {
if(arr.indexOf(arr3[i]==-1)) // 没有匹配成功
arr.push(arr3[i])
}
return arr
}
Map数据结构
定义
JavaScript 的对象(Object),本质上是键值对的集合,但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
// map类似于object 键值对
var m = new Map()
m.set('key','value')
m.set('id',10).set('name','jindu').set('a','a')
// {'key' => 'value', 'id' => 10, 'name' => 'jindu', 'a' => 'a'}
var q = new Map()
var o = {id:100}
q.set(o,'abc')
// {Object => "abc"}
另一种添加方式
let m1 = new Map([['a',1],['b',2],['c',3],['id',4]])
{'a' => 1, 'b' => 2, 'c' => 3, 'id' => 4}
Map实例的属性和方法
1、size属性
// 返回 Map 结构的成员总数
const m = new Map();
m.set('a','a').set('b','b');
m.size //2
2、set(key, value)
const m = new Map();
m.set('qq', '2429462491') // 键是字符串
m.set(100, 'jindu') // 键是数值
m.set(undefined, 'value') // 键是 undefined
3、get(key) 读取 key 对应的键值,如果找不到 key ,返回 undefined。
const m = new Map();
var a = {id:2};
m.set(a,'value'); // {Object => "value"}
m.set('name','jindu'); // {{…} => 'value', 'name' => 'jindu'}
m.get(a); //'value' 通过变量a来获取
m.get('name'); //jindu 通过'name'属性获取
4、has(key) 返回一个布尔值,表示某个键是否在当前 Map 对象之中。
const m = new Map();
var a = {id:2};
m.set(a,'value');
m.set('name','jindu');
m.has('name') //true
m.has('a') //false
m.has(a) //true
5、delete(key) 删除某个键,返回 true 。如果删除失败,返回 false。
const m = new Map();
var a = {id:2};
m.set(a,'value');
m.set('name','jindu');
m.delete('name')
m.has('name') //false
6、clear() 清除所有成员,没有返回值。
const m = new Map();
var a = {id:2};
m.set(a,'value');
m.set('name','jindu');
m.clear();
Map遍历方法
keys() values() entries()
let o = new Map([['a',1],['b',2],['c',3],['id',4]])
for(let [k,v] of o) {
console.log(k)
console.log(v)
}
// keys() values() entries()
for(let k of o.keys()) {
console.log(k)
}
for(let v of o.values()) {
console.log(v)
}
map与其它结构类型转换
1.对象转数组
var obj = {'a':1,'b':2,'c':3}
Object.keys(obj) // ['a', 'b', 'c']
Object.values(obj) // [1, 2, 3]
2.map类型转数组
let m1 = new Map([['a',1],['b',2],['c',3],['id',4]]);
[...m1.keys()]; // ['a', 'b', 'c', 'id']
[...m1.values()]; // [1, 2, 3, 4]
[...m1]; // [['a',1],['b',2],['c',3],['id',4]]
3.map转对象object
let m2 = new Map([['a',1],['b',2],['c',3],['id',4]])
var o2 = {}
for(let [k,v] of m2) {
o2[k] = v
}
console.log(o2) // {a: 1, b: 2, c: 3, id: 4}
4.对象object转map
方式一
var obj = {'a':1,'b':2,'c':3}
let m3 = new Map()
for(let k in obj) {
m3.set(k,obj[k])
}
console.log(m3) // {'a' => 1, 'b' => 2, 'c' => 3}
方式二
var obj = {'a':1,'b':2,'c':3}
let m4 = new Map(Object.entries(obj))
console.log(m4) // {'a' => 1, 'b' => 2, 'c' => 3}
for...of循环
ES6 借鉴 C++、Java、C# 和 Python 语言,引入了 for...of 循环,作为遍历所有数据结构的统一的方法。
for...of 循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如 arguments 对象、DOM NodeList 对象)、后文的 Generator 对象,以及字符串。
const arr = ["red", "green", "blue"];
// es6
for (let v of arr) {
console.log(v); // red green blue
}
// 能不能获取arr的索引? 答案是:of course
for(let v of arr.entries()) {
console.log(v) // [0,'red'],[1,'green'],[2,'blue']
}
for(let [i,v] of arr.entries()) {
console.log(v) // red, green, blue
console.log(i) // 0 1 2
}
// es5
for (let v in arr) {
console.log(v); // 0 1 2
console.log(arr[v]); // 转个弯才能拿到具体的值
}
for (var i = 0; i < arr.length; i++) {
console.log(arr[i]);
console.log(i);
}
// for取值比较麻烦,foreach是for的扩展,但是它没有返回值,map循环有返回值,for...in只能拿到当前索引,拿不到具体的值
arr.forEach(function (item, index) {
console.log(item); // red green blue
console.log(index); // 0 1 2
});
set遍历中的运用
const set = new Set([2,3,52,3,4,43,3])
for(let v of set) {
console.log(v)
}
map遍历中的运用
let m1 = new Map([['a',1],['b',2],['c',3],['id',4]]);
for(let [k,v] of m1) {
console.log(k) // a b c id
console.log(v) // 1 2 3 4
}
for(let k of m1) {
console.log(k) // ['a', 1],['b', 2],['c',3],['id',4]
}
对象遍历中的运用
var obj = {'a':1,'b':2,'c':3}
for(let k in obj) {
console.log(k) // a b c
console.log(obj[k]) // 1 2 3
}
for(let k of obj) {
console.log(k) // obj is not iterable
}
// 解决办法:
for(let k of Object.keys(obj)) { // Object.keys(obj) ['a','b','c']
console.log(k)
}
for(let [k,v] of Object.entries(obj)) {
console.log(k) //获取键
console.log(v) //获取值
}
for...of... 支持返回条件
var arr1 = [2,3,4,5,6]
for(let v of arr1) {
if(v == 3) {
break; // 2 终止操作,跳出循环
// continue // 2,4,5,6 // 排除选中项,继续循环
}
console.log(v)
}
把js留言板改成es6留言板
总结
set特色
1.拥有...方法,属性可以获取长度的属性
2.定义的时候,传入的参数可以是一个数组的
3.成员信息是唯一的,而且是通过全等进行匹配(最重要)
map特色
1.map和对象很相似的,其实就是键值对,在键里面我们可以存任何类型,不过暂时用的不多
2.要记得它里面的方法
3.循环遍历
4.map的类型转换(重点记)
for...of...特色
1.其目的就是用来代替原有的foreach,for,for...in...
2.它替代for其目的就是如果我们用到for循环取值挺麻烦的,我们使用for..of直接就可以取到当前对应的值
3.它替代foreach其目的就是,因为foreach里面不能有返回值,for...of可以
4.它替代for...in其目的就是,如果我们在for...in里面循环遍历数组,我们是不太方便取到它对应的值,默认的情况下只能拿到它的索引信息,但是for...of就可以
5.对象是不支持for...of的,想办法让它支持,不用也行,for...in也非常方便
symbol类型
ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值
解决问题:如果添加的属性名出现重复,如何保证属性名的唯一性,解决属性名的冲突
凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突
var obj = {id:1,id:100,name:'abc',name:'xyz'};
console.log(obj)
// 获取结果:{id: 100, name: "xyz"}
声明的方式
let s = Symbol();
独一无二
let s1 = Symbol();
let s2 = Symbol();
console.log(s1==s2) //false
查看类型
let m = Symbol();
typeof m; // "symbol"
Symbol 实例的描述
Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述
主要是为了在控制台显示,或者转为字符串时,比较容易区分
let s3 = Symbol('abc');
let s4 = Symbol('xyz');
注意,Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相 等的。
let s5 = Symbol('abc');
let s6 = Symbol('abc');
s5 === s6 // false
类型转换
// 1、转成布尔值
let v = Symbol();
Boolean(v) //true
// 2、不能转成数值
Number(s) //Cannot convert a Symbol value to a number
获取描述信息
let e = Symbol('wahaha');
e.description //wahaha
实际的运用
注意: Symbol作为对象的属性名,不能用点运算符
var id = Symbol()
var o = {
id:'1',
[id]:'999'
}
o.id // 1
o[id] // 999
o['id'] // 1
// 注意: Symbol作为对象的属性名,不能输出Symbol类型属性名
Object.keys(o)
for(let v in o) {
console.log(v)
}
添加多个id的情况
var o2 = {}
var x = Symbol()
o2[x] = 111
var x = Symbol()
o2[x] = 222
console.log(o2) // {Symbol(): 111, Symbol(): 222} 只会添加,不会覆盖
// 获取o2[Object.getOwnPropertySymbols(o2)[1]]
console.log(o2[Object.getOwnPropertySymbols(o2)[1]]) // 222
Symbol.for()
由于symbol()每次调用都会返回一个不同的值,希望重新使用同一个 Symbol 值, Symbol.for() 方法可以做到这一点.
let s1 = Symbol.for('wahaha');
let s2 = Symbol.for('wahaha');
s1 === s2 // true
s1和s2都是 Symbol 值,但是它们都是由同样参数的 Symbol.for 方法生成的,所以实际上是同一个值。
Symbol.for()与Symbol()的区别:
Symbol.for()与Symbol()这两种写法,都会生成新的 Symbol。区别是,前者会被登记在全局环境中供搜索,后者不会。Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。比如,如果你调用Symbol.for("cat")30 次,每次都会返回同一个 Symbol 值,但是调用Symbol("cat")`30 次,会返回 30 个不同的 Symbol 值。
var o = {};
var a = Symbol('a');
o[a] = 123;
var a = Symbol('a');
o[a] = 456;
console.log(o);
// 返回的结果:{Symbol(a): 123, Symbol(a): 456}
var o = {};
var a = Symbol.for('a');
o[a] = 123;
var a = Symbol.for('a');
o[a] = 456;
console.log(o);
// 返回的结果:{Symbol(a): 456}
Symbol.keyFor()
定义:返回一个已登记的 Symbol 类型值的key
var a = Symbol.for('abc');
Symbol.keyFor(a); //abc
Promise
async-await
经常会看到有了 async-await、promise 还有必要学习吗、async await优于promise的几个特点,接收了这些信息后,就蒙圈了。
async-await是promise和generator的语法糖。只是为了让我们书写代码时更加流畅,当然也增强了代码的可读性。
简单来说:async-await 是建立在 promise机制之上的,并不能取代其地位。
具体理解:
async
async function f() { // async函数返回的是一个promise对象
return 'f123'
}
f(); //为什么没有执行'f123'
async function f() { // async函数返回的是一个promise对象
return 'f123'
}
console.log(f()); //Promise {<resolved>: "f123"}
async 函数返回的是一个promise 对象,如果要获取到promise 返回值,应该用then 方法
async function f() { // async函数返回的是一个promise对象
return 'f123'
}
f(); //Promise {<resolved>: "f123"}
f().then(function(res) { // 默认为成功的状态,将该函数的返回值传给then的参数
console.log(res)
})
await
await操作符用于等待一个Promise对象,它只能在异步函数async function内部使用。
返回值:
返回promise对象的处理结果,如果待等的不是promise对象,则返回该值本身
如果一个promise被传递给一个await操作符,await将等待promise正常处理完成并返回其处理结果
function f2(){
console.log(4)
}
async function f(){
console.log(1)
await f2(); // 阻塞 f()执行,会先执行f2() -> 执行同步任务 -> await后面的代码
console.log(2)
}
f();
console.log(3);
// 打印结果: 1 4 3 2
console.log(2)
async function fn(){
console.log(3)
await 100;
console.log(1)
}
fn()
console.log(4)
// 打印结果: 2 3 4 1
function f2() {
return new Promise((resolve) => {
resolve()
console.log(5)
})
}
async function f() {
console.log(1)
await f2()
console.log(2)
}
f()
console.log(3)
// 打印结果: 1 5 3 2
微任务和宏任务
微任务与宏任务的区别
就像去银行办业务一样,先要取号进行排号。
一般上边都会印着类似:“您的号码为XX,前边还有XX人。”之类的字样。
由于柜员只能处理一个来办理业务的客户,这时每一个来办理业务的人就可以认为是银行柜员的一个宏任务来存在的;
当柜员处理完当前客户的问题以后,选择接待下一位,广播报号,也就是下一个宏任务的开始。
所以多个宏任务合在一起就可以认为有一个任务队列在这,里边是当前银行中所有排号的客户。
如果叫到你的时候你不在,那么你当前的号牌就作废了,柜员会选择直接跳过进行下一个客户的业务处理,等你回来以后还需要重新取号
而且一个宏任务在执行的过程中,是可以添加一些微任务的,就像在柜台办理业务,你前边的一位老大爷可能在存款,结果在存款这个业务办理完以后,柜员会问老大爷还有没有其他需要办理的业务,这时老大爷想了一下:“想选择稳一些的理财”,这时候柜员肯定不能告诉老大爷说:“您再上后边取个号去,重新排队”。
所以本来快轮到你来办理业务,会因为老大爷临时添加的“理财业务”而往后推。
也许老大爷在办完理财以后还想 再办一个信用卡?或者 再买点儿纪念币?
无论是什么需求,只要是柜员能够帮她办理的,都会在处理你的业务之前来做这些事情,这些都可以认为是微任务。
在当前的微任务没有执行完成时,是不会执行下一个宏任务的。
promise是同步还是异步?
Promise是ES6提出的解决异步编程导致陷入回调地狱问题的,那么Promise是同步的还是异步的?可以确定的是,Promise本身是同步的
console.log(1)
let a = new Promise((res,rej) => {
console.log(2)
})
console.log(3)
let a2 = new Promise((res,rej) => {
console.log(4)
})
console.log(5)
// 打印结果: 1 2 3 4 5 promsie是同步的
Promise本身是同步的,他的then方法和catch方法是异步的
let a = new Promise((res,rej) => { //同步
console.log(1)
res(3)
})
a.then((res) => { //微任务
console.log(res)
})
console.log(2) //同步
// 打印结果: 1 2 3
扩展实例
console.log(1)
let a = new Promise((res,rej) => {
res();
console.log(2);
});
a.then(() => {
console.log(3)
})
console.log(4);
let b = new Promise((res,rej) => {
res();
console.log(5);
});
b.then(() => {
console.log(6)
})
console.log(7);
// 打印结果: 1 2 4 5 7 3 6
setTimeout(() => { // 宏任务
console.log(1)
},0)
new Promise((res,rej) => {
res()
console.log(2) // 同步
}).then(() => {
console.log(3) // 微任务
})
console.log(4) // 同步
// 打印结果: 2 4 3 1
async function f() { // async函数返回的是一个promise对象
console.log(5) // 同步
return '1234'
}
f().then(function(res) { // 异步微任务
console.log(res)
})
console.log(1) // 同步
// 打印结果: 5 1 1234
proxy
proxy 在目标对象的外层搭建了一层拦截,外界对目标对象的某些操作,必须通过这层拦截
var p = new Proxy(target,handler)
// 生成P实例 target表示所要拦截的目标对象,handler用来定制拦截行为
var target = {} // target表示所要拦截的目标对象
var handler = {} // handler用来定制拦截行为
注意: handler 是一个空对象,没有任何拦截行为,访问P就等同于访问target
// handler 是一个空对象,没有任何拦截行为,访问P就等同于访问target
var target = { // target表示所要拦截的目标对象
name:'华哥'
}
var handler = {}
var p = new Proxy(target,handler)
p.name // '华哥'
基本用法
new Proxy() 表示生成一个 Proxy 实例, target 参数表示所要拦截的目标对象, handler 参数也是一个对象,用来定制拦截行为
var target = {
name: '123'
};
var handler = {
get: function(target, key) {
console.log(`${key} 被读取`);
return target[key];
},
set: function(target, key, value) {
console.log(`${key} 被设置为 ${value}`);
target[key] = value;
}
}
var a = new Proxy(target, handler);
a.name; // 控制台输出:name 被读取
a.name = 'abc'; // 控制台输出:name 被设置为 abc
console.log(target.name); // 控制台输出: abc
a读取属性的值时,实际上执行的是handler.get:在控制台输出信息,并且读取被代理对象target的属性。
在a设置属性值时,实际上执行的是 handler.set:在控制台输出信息,并且设置被代理对象target的属性的值
Proxy的作用
对于代理模式Proxy的作用主要体现在三个方面
拦截和监视外部对对象的访问
降低函数或类的复杂度
在复杂操作前对操作进行校验或对所需资源进行管理
Proxy所能代理的范围--handler
实际上handler本身就是ES6所新设计的一个对象,它的作用就是用来自定义代理对象的各种可代理操作.它本身一共有13中方法,每种方法都可以代理一种操作.其13种方法如下:
get(target, propKey, receiver):
拦截对象属性的读取,比如proxy.foo和proxy['foo']。
set(target, propKey, value, receiver):
拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。
has(target, propKey):
拦截propKey in proxy的操作,返回一个布尔值。
deleteProperty(target, propKey):
拦截delete proxy[propKey]的操作,返回一个布尔值。
ownKeys(target):
拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、 Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而 Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
getOwnPropertyDescriptor(target, propKey):
拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
defineProperty(target, propKey, propDesc):
拦截Object.defineProperty(proxy, propKey, propDesc)、 Object.defineProperties(proxy, propDescs),返回一个布尔值。
preventExtensions(target):
拦截Object.preventExtensions(proxy),返回一个布尔值。
getPrototypeOf(target):
拦截Object.getPrototypeOf(proxy),返回一个对象。
isExtensible(target):
拦截Object.isExtensible(proxy),返回一个布尔值。
setPrototypeOf(target, proto):
拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种 额外操作可以拦截。
apply(target, object, args):
拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、 proxy.apply(...)。
construct(target, args): 拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)。
Proxy场景
1)实现私有变量,想获取angelababy,由于添加了拦截获取的只能是18
var target = {
name:'angelababy',
_age:30
};
var handler = {
get:(target,key) => { // get方法三个参数,目标对象,属性名,proxy实例本身(非必填)
if(key.startsWith('_')) {
console.log('age不能访问')
return 18
};
return target[key]
},
set:(target,key,value) => { // set方法四个参数,目标对象,属性名,属性值,proxy实例本身(非必填)
if(key.startsWith('_')) {
console.log('age不能修改')
return 18
};
target[key] = value
}
}
var a2 = new Proxy(target,handler)
a2._age //18 私有变量age不能被访问
a2._age = 100 //私有变量age不能被修改
2)抽离校验模块
var num = {
count: 0,
id: 1234,
total: 14
};
var handler = {
set(target, key, value, proxy) {
if (typeof value !== 'number') {
throw Error("只能输入数字");
}
return Reflect.set(target, key, value, proxy);
}
};
var p = new Proxy(num, handler);
// 抛出错误,因为 "123" 不是数值型
p.count = "123";
// 赋值成功
p.count = 333;
Object.defineProperty()与Proxy的异同
Object.defineProperty()
优点:可以更好的拦截
缺点:无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应(虽说对常用的方法进行了处理,但然存在局限性);只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。
proxy
优点:可以劫持整个对象,并返回一个新对象;有13种劫持操作
缺点:兼容性不好
var Obj = {}
Object.defineProperty(Obj, 'a', {
get: function () {
console.log('get');
return v
},
set: function (val) {
console.log('set');
v = val
}
});
Obj.a = [] // set
Obj.a.push('1') // get
Obj.a[0] = 1 // get
Obj.a.pop(1) // get
Obj.a = [1, 2, 3] //
var arr = [];
var p = new Proxy(arr, {
get: (target, key) => {
console.log('get')
return key in target ? target[key] : undefined
},
set: (target, key, value) => {
console.log('set')
target[key] = value
return true
}
})
p.push(1);
get // 获取数组arr的push方法
get // 获取数组arr的length属性
set // 设置arr[0] = 1
set // 设置数组arr长度为1
class