ES6 ⭐⭐⭐⭐
变量
let
let特性:
1,变量不能重复声明
2.块级作用域 (只在代码块里面有效,全局无法获取)
3.不存在变量提升
4.不影响作用域链
5.暂时性死区:如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
const
//声明常量
1.一定要赋初始值,必须大写
2.声明时必须赋值
3.常量的值不能修改
4.块级作用域
5.对于数组和对象的元素修改,不算做对常量的修改,不会报错
6.冻结对象Object.freeze(),无法更改对象的属性
eg:const TEAM=['UZI',"MXLG",'Ming',"Letme"];
TEAM =100;
//报错
eg:
const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
const foo = Object.freeze({});
// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;
globalThis对象
JavaScript 语言存在一个顶层对象,它提供全局环境(即全局作用域),所有代码都是在这个环境中运行。 但是,顶层对象在各种实现里面是不统一的。
-浏览器里面,顶层对象是,但 Node 和 Web Worker 没有。 window.window
-浏览器和 Web Worker 里面,也指向顶层对象,但是 Node 没有。selfself
-Node 里面,顶层对象是,但其他环境都不支持。global
同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用关键字,但是有局限性。this
-全局环境中,会返回顶层对象。 但是,Node.js 模块中返回的是当前模块,ES6 模块中返回的是。this this this undefined
-函数里面的,如果函数不是作为对象的方法运行,而是单纯作为函数运行,会指向顶层对象。 但是,严格模式下,这时会返回。thisthisthisundefined
-不管是严格模式,还是普通模式,总是会返回全局对象。 但是,如果浏览器用了 CSP(Content Security Policy,内容安全策略),那么、这些方法都可能无法使用。new Function('return this')()evalnew Function
综上所述,很难找到一种方法,可以在所有情况下,都取到顶层对象。 下面是两种勉强可以使用的方法。
// 方法二
var getGlobal = function () {
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('unable to locate global object');
};
BigInt
(1)BigInt数据类型
JavaScript 所有数字都保存成 64 位浮点数,这给数值的表示带来了两大限制。一是数值的精度只能到 53 个二进制位(相当于 16 个十进制位),大于这个范围的整数,JavaScript 是无法精确表示的
ES2020引入了一种新的数据类型 BigInt(大整数),来解决这个问题,这是 ECMAScript 的第八种数据类型。BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。为了与 Number类型区别,BigInt 类型的数据必须添加后缀n。
// BigInt 的运算
1n + 2n // 3n
注意:BigInt 可以使用负号(-),但是不能使用正号(+),因为会与 asm.js 冲突
-42n // 正确
+42n // 报错
(2)BigInt对象
JavaScript 原生提供BigInt对象,可以用作构造函数生成 BigInt 类型的数值。转换规则基本与Number()一致,将其他类型的值转为 BigInt。
`注意`:BigInt()构造函数必须有参数,而且参数必须可以正常转为数值
BigInt(123) // 123n
BigInt('123') // 123n
BigInt(false) // 0n
BigInt(true) // 1n
Symbol
es6引入了一个新的原始数据类型Symbol,表示独一无二的值,它是一种类似于字符串的数据类型
特点:(1)Symbol值是唯一的,用来解决命名冲突的问题
(2)Symbol值不能与其他数据进行运算
(3)Symbol定义的对象属性不能用for...in循环遍历,但可以使用Reflect.ownKeys来获取对象的所有属性名
作用:给对象添加属性和方法
创建Symbol
//第一种:let s=Symbol();
//第二种:
let s2=Symbol('残星落影');
let s3=Symbol('残星落影');
console.log(s2===s3);//false
//第三种:
let s4=Symbol.for('残星落影');
let s5=Symbol.for('残星落影'); console.log(s4==s5);//true;
Symbol创建对象属性
let youxi={
name:'狼人杀',
[Symbol('say')]:function(){
console.log('发言')
}
}
console.log(youxi);// name:'...';
// Symbol(say):f()
1.Symbol.description返回 Symbol 的描述。
const sym = Symbol('foo');
sym.description // "foo"
2.作为属性名的 Symbol
Symbol 值作为属性名时,该属性还是公开属性,不是私有属性
3.消除魔术字符串
魔术字符串指的是,在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。常用的消除魔术字符串的方法,就是把它写成一个变量
4.属性名的遍历
getOwnPropertySymbols()方法,该方法返回一个数组,可以获取所有 Symbol 属性名。该属性不会出现在for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。
5.Symbol.for()和Symbol.keyFor()
(1)Symbol.for()接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局
(2)Symbol.for()与Symbol()的区别:
前者会被登记在全局环境中供搜索,后者不会。Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值
(3)Symbol.keyFor()方法返回一个已登记的 Symbol 类型值的key。
let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"
let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined
变量s2属于未登记的 Symbol 值,所以返回undefined
箭头函数
利用箭头=>定义函数
(1)var f = () => 5;
// 等同于
var f = function () { return 5 };
(2)//声明一个函数
let fn=function(a,b){
result a+b;
}
等同于
let fn=(a,b)=>{
result a+b;
}
//调用函数
let result =fn(1,2)
console.log(result); //3
1.this是静态的,this始终指向函数声明时所在作用域下的this的值
function getName(){
console.log(this.name);
}
function getName2=()=>{
console.log(this.name);
}
//设置window对象的name值
window.name='1111';
const school={
name:"222222";
}
//直接调用
getName(); //----1111
getName2(); //----1111
//call方法调用
getName.call(school);//222222
getName2.call(school);//1111
2.不能作为构造函数实例化对象
let Person=(name,age)=>{
this.name=name;
this.age=age
}
let me =new Person('liu','23');
consle.log(me);// 报错
3.不能使用arguments变量
let fn=()=>{
console.log(arguments);//报错
}
fn(1,2,3)
4.箭头函数的简写
(1)省略小括号,当形参只有一个的时候;
let add=n=>{
return n+n;
}
console.log(add(9));//18
(2)省略花括号 当代码体只有一条语句的时候,return必须省略
let pow =n=>n*n;
console.log(pow(2))//4
`注意`:由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
let getTempItem = id => ({ id: id, name: "Temp" });
5.注意事项
(1)箭头函数没有自己的this对象,它里面的this指向定义时上层作用域的this
(2)不可以当作构造函数,也就是说,不可以对箭头函数使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。解构赋值
1.数组的解构赋值
let [xiao,liu,zhao,song]=['小沈阳','刘能','赵四','宋小宝'];
console.log(xiao); //小沈阳
console.log(liu); //刘能
2.对象的解构赋值(属性名与变量名一致)
eg:let {a:x,b:y,z}={c:1,a:3,z:2,b:4};
console.log(a,b,c);// 3,4,1
let {name,age,xiaopin} ={
name:'赵本山',
age:'58',
xiaopin:function(){
console.log("小品")
}
};
console.log(name);//赵本山
console.log(age);//58
3.字符串的结构赋值
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
4.默认值
解构赋值允许指定默认值undefined。
let [foo = true] = [];
foo // true
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
var {x: y = 3} = {x: 5};
y // 5
var {x = 3} = {x: undefined};
x // 3
var {x = 3} = {x: null};
x // null
模板字符串``
特点:1,使用反引号表示字符串
2,直接换行,保留格式
3,可以直接${}嵌入变量或js代码
4,``.trim()方法去掉执行代码前面的换行
1.声明
let str=`字符串`;
2.内容中可以直接出现换行符
let str=`<ul><li></li></ul> `;
3.变量拼接
let lover="666";
let out=`${lover}四十三`;
console.log(666四十三);
扩展运算符
扩展运算符:‘...’ 能将数组或对象和字符串转换为逗号分隔的参数序列,是一种拆包解构方法
运用范围:
(1)数组:将数组元素分隔开,变为用逗号分隔的参数序列
(2)对象:将对象属性展开,变为用逗号分隔开的参数序列
(3)字符串:将字符串用逗号分隔开,变为用逗号分隔开的参数数组
const tfboys=['易','王源','王俊凯'];
function chunwan(){
console.log(arguments);
}
chunwan(tfboys);//只显示一个参数 里面装一个数组
chunwan(...tfboys);//相当于chunwan('易','王源','王俊凯'),显示数组里面的三个参数
![image-20210606141227945]()
![image-20210606141503274]()
扩展运算符的应用
1.数组的合并
const arr1=[1,2,3];
const arr2=[4,5,6];
const newArr=[...arr1,...arr2];//[1,2,3,4,5,6]
2.数组的克隆 //只能实现浅拷贝,引用数据类型无法拷贝
const arr=[1,2,3];
const newArr=[...arr];//[1,2,3]
3.将伪数组(Nodelist.Htmlcollection之类的集合数组)转换为真数组
const divs=document.querySelectorAll('div');
const divArr[...divs];
console.log(divArr);//[div,div,div....];
字符串的扩展
1.includes():includes():返回布尔值,表示是否找到了参数字符串。
2.startsWith():返回布尔值,表示参数字符串是否在原字符串的头部
3.endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
4.repeat(n):返回一个新字符串,表示将原字符串重复n次
5.padStart(length,str)和padEnd(length,str):一共接受两个参数,第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串
'x'.padStart(4, 'ab') // 'abax'
6.trimStart()和trimEnd():trimStart()消除字符串头部的空格,trimEnd()消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串
7.replaceAll():可以一次性替换所有匹配。
数组的扩展
1.扩展运算符...
2.Array.from()方法用于将两类(类似数组的对象和遍历的对象)对象转为真正的数组
(1)let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
(2)let ps = document.querySelectorAll('p');
Array.from(ps).filter(p => {
return p.textContent.length > 100;
});
(3)function foo() {
var args = Array.from(arguments);
// ...
}
3.Array.of()方法总是返回参数值组成的数组。如果没有参数,就返回一个空数组。
4.数组实例的 copyWithin()
写法:Array.prototype.copyWithin(target, start = 0, end = this.length
接受三个参数:
target(必需):从该位置开始替换数据。如果为负值,表示倒数。
start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。
end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。
5.数组实例的find()和findIndex()
(1)数组实例的find方法,用于找出第一个符合条件的数组成员,它的参数是一个回调函数,直到找出第一个返回值为true的成员,然后返回该成员
[1, 5, 10, 15].find(function(value, index, arr) {
return value > 9;
}) // 10
(2)数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1
[1, 5, 10, 15].findIndex(function(value, index, arr) {
return value > 9;
}) // 2
(3)find和findIndex方法都可以接受第二个参数,用来绑定回调函数的this对象
function f(v){
return v > this.age;
}
let person = {name: 'John', age: 20};
[10, 12, 26, 15].find(f, person); // 26
6.数组实例的fill()
fill方法使用给定值,填充一个数组。
['a', 'b', 'c'].fill(7)// [7, 7, 7]
new Array(3).fill(7)// [7, 7, 7]
fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。
['a', 'b', 'c'].fill(7, 1, 2)// ['a', 7, 'c']
7.数组实例的 entries(),keys() 和 values()
keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。
(1)for (let index of ['a', 'b'].keys()) {
console.log(index);
}// 0 1
(2)for (let elem of ['a', 'b'].values()) {
console.log(elem);
}// 'a' 'b'
(3)for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}// 0 "a" 1 "b"
8.数组实例的 includes(elm,start)
表示某个数组是否包含给定的值,第二个参数表示搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度,则会重置为从0开始。
[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false
[1, 2, NaN].includes(NaN) // true
9.数组实例的 flat()和flatMap()
(1)flat()用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响
[1, 2, [3, 4]].flat()// [1, 2, 3, 4];
flat()默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组,可以将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1。
[1, 2, [3, [4, 5]]].flat()// [1, 2, 3, [4, 5]]
[1, 2, [3, [4, 5]]].flat(2)// [1, 2, 3, 4, 5]
如果不管有`多少层嵌套`,都要转成一维数组,可以用Infinity关键字作为参数。
[1, [2, [3]]].flat(Infinity)// [1, 2, 3]
(2)flatMap()对原数组的每个成员执行一个函数,然后对返回值组成的数组执行flat()方法。该方法返回一个新数组
// 相当于 [[[2]], [[4]], [[6]], [[8]]].flat()
[1, 2, 3, 4].flatMap(x => [[x * 2]])
// [[2], [4], [6], [8]]
数值的扩展
1.二进制和八进制表示法
分别用前缀0b(或0B)和0o(或0O)表示。
0b111110111 === 503 // true
0o767 === 503 // true
2.Number.isFinite()和Number.isNaN()
Number.isFinite()用来检查一个数值是否为有限的(finite),即不是Infinity
Number.isNaN()用来检查一个值是否为NaN
3.Number.parseInt()和Number.parseFloat()
目的:是逐步减少全局性方法,使得语言逐步模块化。
Number.parseInt === parseInt // true
Number.parseFloat === parseFloat // true
4.Number.isInteger()用来判断一个数值是否为整数,不能超过53的二进制位
5.安全整数和 Number.isSafeInteger()
能够准确表示的整数范围在-2^53到2^53之间(不含两个端点),超过这个范围,无法精确表示这个值。
6.Math对象的扩展
(1)Math.trunc()方法用于去除一个数的小数部分,返回整数部分
(2)Math.sign()方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。会返回五种值:
参数为正数,返回+1;
参数为负数,返回-1;
参数为 0,返回0;
参数为-0,返回-0;
其他值,返回NaN。
(3)Math.cbrt()方法用于计算一个数的立方根。
Math.cbrt(2) // 1.2599210498948732
(4)Math.expm1()返回 ex - 1,即Math.exp(x) - 1。
Math.expm1(1) // 1.718281828459045
(5)Math.log1p()方法返回1 + x的自然对数,即Math.log(1 + x)。如果x小于-1,返回NaN。
(6)Math.sinh(x) 返回x的双曲正弦
(7)Math.cosh(x) 返回x的双曲余弦
(8)Math.tanh(x) 返回x的双曲正切
(9)Math.asinh(x) 返回x的反双曲正弦
7.指数运算符**
2 ** 2 // 2的2次方 4
2 ** 3 // 2的3次方 8
2 ** 3 ** 2//2的3的2次方 512
正则的扩展
1.RegExp 构造函数
RegExp构造函数的参数两种情况
Es5:(1)参数是字符串
var regex = new RegExp('xyz', 'i');
// 等价于
var regex = /xyz/i;
(2)参数是一个正则表示式
var regex = new RegExp(/xyz/i);
// 等价于
var regex = /xyz/i;
ES5 不允许此时使用第二个参数添加修饰符,否则会报错
ES6:如果RegExp构造函数第一个参数是一个正则对象,那么可以使用第二个参数指定修饰符。
new RegExp(/abc/ig, 'i').flags// "i"
2. y修饰符
叫做“粘连”(sticky)修饰符。y修饰符的作用与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。
函数的扩展
1.函数参数的默认值
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面
function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
2.函数的 length 属性
指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,`指定了默认值后,length属性将失真,默认值后面的参数不计入length中`。
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5,d,e,f) {}).length // 2
3.rest 参数
ES6 引入 rest 参数(形式为...变量名)跟扩展运算符类似,用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中
4.name属性
函数的name属性,返回该函数的函数名
对象的扩展
1.属性的简洁写法
let name="刘宏涛";
let change=function(){
console.log('6666');
}
(1)简化对象
const school ={
name, // -----name:name,
change, //-----change:change,
improve:function(){ //-----improve(){...}
console.log(sss);
}
}
(2)简化方法
const o = {
method() {
return "Hello!";
}
};
// 等同于
const o = {
method: function() {
return "Hello!";
}
};
2.属性名表达式
JavaScript 定义对象的属性,有两种方法
// 方法一
obj.foo = true;
// 方法二
obj['a' + 'bc'] = 123;
ES6 允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在方括号内
let propKey = 'foo';
let obj = {
[propKey]: true,
['a' + 'bc']: 123
};
log.obj
//{foo:true;abc:123}
表达式还可以用于定义方法名。
let obj = {
['h' + 'ello']() {
return 'hi';
}
};
obj.hello() // hi
3.方法的 name 属性
函数的name属性,返回函数名。对象方法也是函数,因此也有name属性
4.属性的可枚举性和遍历
(1)可枚举性:对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象
eg: let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writable: true,
// enumerable: true,
// configurable: true
// }
有四个操作会忽略enumerable为false的属性。
for...in循环:只遍历对象自身的和继承的可枚举的属性。
Object.keys():返回对象自身的所有可枚举的属性的键名。
JSON.stringify():只串行化对象自身的可枚举的属性。
Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。
(2)遍历
ES6 一共有 5 种方法可以遍历对象的属性。
/1.for...in:for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
/2.Object.keys(obj)返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
/3.Object.getOwnPropertyNames(obj),返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
/4.Object.getOwnPropertySymbols(obj)返回一个数组,包含对象自身的所有 Symbol 属性的键名。
/5.Reflect.ownKeys(obj)返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
5.super关键字
this关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象。
const proto = {
foo: 'hello'
};
const obj = {
foo: 'world',
find() {//如果使用function表达式则报错
return super.foo;
}
};
Object.setPrototypeOf(obj, proto);
obj.find() // "hello"
6.链判断运算符`?.`
读取对象内部的某个属性,往往需要判断一下该对象是否存在
链判断运算符有三种用法:
obj?.prop // 对象属性
obj?.[expr] // 表达式
func?.(...args) // 函数或对象方法的调用
eg:(1)a?.b// 等同于a == null ? undefined : a.b
(2)a?.[x]// 等同于a == null ? undefined:a[x]
(3)a?.b()// 等同于a == null ? undefined :a.b()
(4)a?.()// 等同于a == null ? undefined : a()
7.Null判断运算符??
新的 Null 判断运算符??。它的行为类似||,但是只有运算符左侧的值为null或undefined时,才会返回右侧的值。这个运算符的一个目的,就是跟链判断运算符?.配合使用,为null或undefined的值设置默认值
const animationDuration = response.settings?.animationDuration ?? 300;
对象的新增方法
1.Object.is()
Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。
Object.is('foo', 'foo')
// true
Object.is({}, {})
// false
不同之处只有两个:一是+0不等于-0,二是NaN等于自身。
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
2.Object.assign(target,source1)
基本用法:方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
Object.assign()方法的第一个参数是目标对象,后面的参数都是源对象。
3.__proto__属性和Object.setPrototypeOf()和Object.getPrototypeOf()
(1)__proto__:标准明确规定,只有浏览器必须部署这个属性,其他运行环境不一定需要部署,而且新的代码最好认为这个属性是不存在的,无论从语义的角度,还是从兼容性的角度,都不要使用这个属性.而是使用下面的 Object.setPrototypeOf()(写操作)
Object.getPrototypeOf()(读操作)
Object.create()(生成操作)代替
(2)Object.setPrototypeOf():
作用与__proto__相同,用来设置一个对象的原型对象(prototype),返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法
function setPrototypeOf(obj, proto) {
obj.__proto__ = proto;
// Object.setPrototypeOf(obj, proto);
return obj;
}
(3)Object.getPrototypeOf():
该方法与Object.setPrototypeOf方法配套,用于读取一个对象的原型对象。如果参数是undefined或null,它们无法转为对象,所以会报错。
Object.getPrototypeOf(null)
// TypeError: Cannot convert undefined or null to object
4.Object.keys(),Object.values()和Object.entries()
(1)Object.keys():返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的`键名`
var obj = { foo: 'bar', baz: 42 };
Object.keys(obj)
// ["foo", "baz"] 键名
(2)Object.values():方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的`键值`。
const obj = { foo: 'bar', baz: 42 };
Object.values(obj)
// ["bar", 42]
(3)Object.entries()方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的`键值对数组`
用途:
一:Object.entries的基本用途是遍历对象的属性,
const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]
二:将对象转为真正的Map结构。
const obj = { foo: 'bar', baz: 42 };
const map = new Map(Object.entries(obj));
map // Map { foo: "bar", baz: 42 }
5.Object.fromEntries()
Object.fromEntries()方法是Object.entries()的逆操作,用于将一个键值对数组转为对象
Object.fromEntries([
['foo', 'bar'],
['baz', 42]
])
// { foo: "bar", baz: 42 }
可以配合URLSearchParams对象,将查询字符串转为对象Object.fromEntries(new URLSearchParams('foo=bar&baz=qux'))
// { foo: "bar", baz: "qux" }
迭代器iteratorl
迭代器(iterator)是一种接口,为各种不同的数据结构提供统一的`访问机制`。
迭代器的遍历是由for...of专门实现的,原生具有迭代器iterator结构的数据有:Array,Arguments,Set,Map,String, TypeArray,NodeList等
`注:Object原生对象不具备有迭代器,无法遍历,但可以通过在对象里面设置[symbol.iterator]方法设置一个遍历器`
let obj={
x:1,y:2,z:3,
[Symbol.iterator](){
return{
next(){
return{value:属性值,done:true}
}
}
}
}
工作原理:
(1)创建一个指针对象,指向当前数据结构的起始位置
(2)第一次调用对象的next方法,指针自动指向数据结构的第一个成员
(3)接下来不断调用next方法,指针一直往后移动,直到指向最后一个成员
(4)每调用next方法返回一个包含value和done属性的对象
作用:需要`自定义遍历数据`的时候 用到迭代器
对于遍历器对象来说,done: false和value: undefined属性都是可以省略的
eg:
const xiyou=['唐','孙','猪','沙'];
let iterator=xiyou[Symbol.iterator]();
//调用对象方法
console.log(iterator.next());//value:'唐',done:false
console.log(iterator.next());//value:'孙',done:false
console.log(iterator.next());//value:'猪',done:false
console.log(iterator.next());//value:'沙',done:false
console.log(iterator.next());//undefined,done:true;
1.默认 Iterator 接口
为所有数据结构,提供了一种统一的访问机制,即for...of循环,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有`Symbol.iterator属性`,就可以认为是“可遍历的”(iterable)
2.调用 Iterator 接口的场合
(1)解构赋值
对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator方法。
(2)扩展运算符
扩展运算符(...)也会调用默认的 Iterator 接口
(3)yield*
yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
let generator = function* () {
yield 1;
yield* [2,3,4];
yield 5;
};
var iterator = generator();
iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
(4)
迭代器自定义遍历数据
const banji={
name:'终极一班',
stus:['小明','小绿','小花','小杜'],
[Symbol.iterator](){
//索引
let index=0;
let _this=this;
return:{
next:function(){
if(index<_this.stus.length){
const result={ value:_this.stus[index],done:false
}
//索引自加
idnex++;
//返回结构
return result;
}else{
return{
value:undefined,done:true
};
}
}
}
}
}
//遍历这个对象
for(let v of banji){
console.log(v);
} //'小明','小绿','小花','小杜'
for...of遍历
一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for...of循环遍历它的成员。也就是说,for...of循环内部调用的是数据结构的Symbol.iterator方法。
for...of循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments对象、DOM NodeList 对象)、后文的 Generator 对象,以及字符串。
生成器generator
简介
Generator 函数是 ES6 提供的一种异步编程解决方案,执行 Generator 函数会返回一个遍历器对象,生成器函数不是立即执行函数,必须经过调用才会执行。
生成器特征:
一是,function关键字与函数名之间有一个星号;
二是,函数体内部使用yield表达式,定义不同的内部状态
eg:function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象
yield表达式
Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,yield表达式就是暂停标志
yield运行逻辑流程:
(1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。
(3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
(4)如果该函数没有return语句,则返回的对象的value属性值为undefined。
与 Iterator 接口的关系
由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。
eg: var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1, 2, 3]
next 方法的参数
yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。
async函数
eS2017 标准引入了 async 函数,使得异步操作变得更加方便,async 函数是 Generator 函数的语法糖。
promise
promise是异步编程的解决方案,Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。
特点:
(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中),fulfilled(已成功),rejected(已失败)
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果
缺点:
(1)无法取消Promise,一旦新建它就会立即执行,无法中途取消
(2)如果不设置回调函数,Promise内部抛出的错误,不会反应到外部
(3)当处于pending状态时,无法得知目前进展到哪一个阶段
实例化promise
const p=new Promise(function(resolve,reject){
setTimeout(function(){
let data="数据";
//成功
resolve(data);
//失败
reject(data);
},1000)
});
//调用promise对象then方法
p.then(function(value){
//成功一般用value
},function(reason){
//失败一般用reason
})
Promise封装Ajax
const p=new Promise((resolve,reject)=>{
//1.创建对象
const xhr=new XMLHttpRequset();
//2.初始化
xhr.open("GET","服务器路径");
//3.发送
xhr.sends();
//4.绑定事件,处理响应结果
xhr.onreadystatechange=function(){
if(xhr.readyState===4){
if(xhr.staus >=200 &&xhr.status<300){
//发送成功
console.log(xhr.response);
resolve(xhr.response)
}else{
console.error(xhr.status);
}
}
}
});
//指定回调
p.then(function(value){
console.log(value);
},function(reason){
console.error(reason);
});
Set数据结构
es6提供的新的数据结构Set集合,类似于数组,但成员的值都是唯一的,集合实现了Iterator接口,可以使用扩展运算符和for...of...进行遍历
1. 集合属性和方法:
(1)size 返回集合的元素的个数
(2)add 增加一个新元素,返回当前集合
(3)delete 删除元素,返回布尔值
(4)has 检测集合中是否包含某个元素怒,返回布尔值
(5)clear 清空成员
let s=new Set(['小明','小红','小蓝','小花','小明']);
//元素个数
s.size; 结果为4,自动除去重复元素返回Set实例的成员总数
//添加元素
s.add('小绿');添加某个值,返回 Set 结构本身
//删除元素
s.delete('小红');删除某个值,返回一个布尔值,表示删除是否成功。
//检测has
s.has('小明');//true 返回一个布尔值,表示该值是否为Set的成员。
Set集合实践
let arr=[1,2,3,4,5,3,2,1];
//1.数组去重
let result=[...new Set(arr)];//[1,2,3,4,5]
//2.去除字符串里面的重复字符
[...new Set('ababbc')].join('')//'abc'
2.遍历操作
Set 结构的实例有四个遍历方法,可以用于遍历成员。
Set.keys():返回键名的遍历器
Set.values():返回键值的遍历器
Set.entries():返回键值对的遍历器
Set.forEach():使用回调函数遍历每个成员
3.WeakSet结构
WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别
(1)WeakSet 的成员只能是对象,而不能是其他类型的值。
(2)WeakSet 不可遍历
WeakSet 是一个构造函数,可以使用new命令,创建 WeakSet 数据结构。作为构造函数,WeakSet 可以接受一个数组或类似数组的对象作为参数。该数组的所有成员,都会自动成为 WeakSet 实例对象的成员。
WeakSet 结构有以下三个方法。
WeakSet.add(value):向 WeakSet 实例添加一个新成员。
WeakSet.delete(value):清除 WeakSet 实例的指定成员。
WeakSet.has(value):返回一个布尔值,表示某个值是否在 WeakSet 实例之中。
const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
// WeakSet {[1, 2], [3, 4]}
const b = [3, 4];
const ws = new WeakSet(b);
//报错,数组b的成员不是对象,加入 WeakSet 就会报错
作用:使用 WeakSet 的好处是对实例的引用不会被计入内存回收机制,删除实例的时候,也不会出现内存泄漏。
Map数据结构
es6提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应
const map = new Map([
['name', '张三'],
['title', 'Author']
]);
map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"
Map构造函数接受数组作为参数,实际上执行的是下面的算法,仅仅是数组,任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构,都可以当作Map构造函数的参数。这就是说,Set和Map都可以用来生成新的 Map。
const set = new Set([
['foo', 1],
['bar', 2]
]);
const m1 = new Map(set);
m1.get('foo') // 1
const m2 = new Map([['baz', 3]]);
const m3 = new Map(m2);
m3.get('baz') // 3
1.Map实例的属性和操作方法
(1)size属性:返回 Map 结构的成员总数。
const map = new Map();
map.set('foo', true);
map.set('bar', false);
map.size // 2
(2)Map.set(key, value):设置键名key对应的键值为value,然后返回整个 Map 结构,如果key已经有值,则键值会被更新,否则就新生成该键。
const m = new Map();
m.text="text";//这种方式添加的值无法绑定到m上,size没有变化
m.set('edition', 6) // 键是字符串
m.set(262, 'standard') // 键是数值
m.set(undefined, 'nah') // 键是 undefined
`·set方法返回的是当前的Map对象,因此可以采用链式写法`
let map = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');
(3)Map.get(key):读取key对应的键值,如果找不到key,返回undefined。
const m = new Map();
const hello = function() {console.log('hello');};
m.set(hello, 'Hello ES6!') // 键是函数
m.get(hello) // Hello ES6!
(4)Map.has(key):has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。
(5)Map.delete(key):删除某个键,返回true。如果删除失败,返回false
(6)Map.clear():clear方法清除所有成员,没有返回值
2.Map的遍历
const map = new Map([
['F', 'no'],
['T', 'yes'],
]);
/map.keys方法
for (let key of map.keys()) {
console.log(key);
}
// "F"
// "T"
/map.values方法:
for (let value of map.values()) {
console.log(value);
}
// "no"
// "yes"
/map.entries方法
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);
}
// "F" "no"
// "T" "yes"
// 等同于使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
Map的数据类型转换
(1)Map 转为数组,Map 转为数组最方便的方法,就是使用扩展运算符(...)
const myMap = new Map()
.set(true, 7)
.set({foo: 3}, ['abc']);
[...myMap]
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
(2)数组转为Map:将数组传入 Map 构造函数,就可以转为 Map。
new Map([
[true, 7],
[{foo: 3}, ['abc']]
])
// Map {
// true => 7,
// Object {foo: 3} => ['abc']
// }
(3)Map转为对象(循环)
eg: function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
const myMap = new Map()
.set('yes', true)
.set('no', false);
strMapToObj(myMap)
// { yes: true, no: false }
(4)对象转为Map:对象转为 Map 可以通过Object.entries()。
eg: let obj = {"a":1, "b":2};
let map = new Map(Object.entries(obj));
(5)Map 转为 JSON
Map 转为 JSON 分两种情况。
/1.Map 的键名都是字符串,可以选择转为对象JSON。
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap));
}
let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap)
// '{"yes":true,"no":false}'
/2.Map 的键名有非字符串,可以选择转为数组 JSON。
function mapToArrayJson(map) {
return JSON.stringify([...map]);
}
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap)
// '[[true,7],[{"foo":3},["abc"]]]'
(6)JSON 转为 Map
eg: function jsonToStrMap(jsonStr) {
return objToStrMap(JSON.parse(jsonStr));
}
jsonToStrMap('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}
WeakMap
1.含义:WeakMap结构与Map结构类似,也是用于生成键值对的集合,WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失。WeakMap结构有助于防止内存泄漏
2.WeakMap与Map的区别有两点。
(1)WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
eg: const map = new WeakMap();
map.set(1, 2)
// TypeError: 1 is not an object!
WeakMap的键名所指向的对象,不计入垃圾回收机制。
(2)WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。
3.WeakMap 的语法
WeakMap 与 Map 在 API 上的区别主要是两个,一是没有遍历操作(即没有keys()、values()和entries()方法),也没有size属性。
const wm = new WeakMap();
// size、forEach、clear 方法都不存在
wm.size // undefined
wm.forEach // undefined
wm.clear // undefined
ES6的面向对象
class类
定义
通过class关键字,可以定义类,基本上es6的class可以看作只是一个语法糖,新的class写法只是让对象原型的写法更加清晰,更像面向对象编程的语言
`注意`:
1.class类不需要加上function这个关键字
2.方法与方法之间不需要逗号分隔,加了会报错
3.类必须要用new来调用
4.class中protoType上得方法不可以遍历
作用
(1)class声明类
(2)constructor定义构造函数初始化
(3)extends继承父类
(4)super调用父级构造方法
(5)static定义静态方法和属性
(6)父类方法可以重写
class Animal{
_height='50cm';//实例属性
static _weight='2.5kg';//静态属性---提案
#age=1;//私有属性
//构造函数,每个类都必须有,如果不写,js引擎默认添加一个空的构造函数,当new一个对象时,会自动调用constructor
constructor([args]){
this.arg1=arg1;
}
//方法-不能有function关键字方法与方法之间不能用逗号隔开,类的所有方法都是绑定在prototype上,并且不能被遍历(es5中可以被遍历)
getkind(){//code}
//静态方法---不属于实例
static staticMethod(){}
}
//类只能被new调用
let cat =new Animal([args...]);//实例化
eg:
function Phone(brand,price){
this.brand=brand;
this.price=price;
}
//添加方法
Phone.protoType.call=function(){
console.log('我可以打电话')
}
//实例化对象
let HuaWei=new Phone("华为",5999);
console.log(HuaWei);
----class写法
class Phone{
//构造方法,名字不能修改
constructor(brand,price){
this.brand=brand;
this.price=price;
}
//方法必须用改方法,不能使用es5的对象完整形式
call(){
console.log('我可以打电话')
}
}
let onePlus=new shouji("1+",1999)
set和get函数拦截
class Animal{
constructor(kind,color){
this.kind=kind;
this.color=color;
}
get Voice(){//拦截获取voice属性
console.log("获取voice值");
return 'vioce...'
}
set voice(v){//设置获取voice属性
console.log("设置voice值"+v)
}
}
let cat=new Animal('猫咪','yellow');
cat.voice='喵喵~';
console.log(cat.voice);//"获取voice值" 'vioce...'
class表达式
const Myclass=class Me{
getName(){
return Me.name;
}
}
//类得名字为Me,但Me只在class得内部使用,指代当前类,在class外部只能使用Myclass引用
let inst=new Myclass();
inst.getName();//Me
Me.name;//报错
class严格模式
类和模块的内部默认为严格模式,不需要使用'use strict'指定运行模式
(1)不存在变量提升(主要)
(2)name属性
(3)generator方法
在class类里面,在某个方法之前加上*号,就表示方法是一个遍历器
eg:class Foo{
constructor(...args){
this.args=args;
}
*[Symbol.iterator](){
for(let arg of this.args){
yield arg;
}
}
}
for(let x of new Foo('hello','world')){
console.log(x)
}
//hello
//world
(4)this指向
类的方法内部如果含有this,它默认指向类的实例。一旦单独使用该方法,很可能报错。
解决方案:
/1.在构造方法中绑定this
/2.使用箭头函数
/3.使用Proxy,获取方法的时候,自动绑定this
class静态方法
(1)如果静态方法包含this关键字,这个this指的是类,而不是实例
(2)父类的静态方法,可以被子类继承
lass Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
}
Bar.classMethod() // 'hello'
(3)实例属性的新写法
实例属性除了定义在constructor()方法里面的this上面,也可以定义在类的最顶层。
class Counter {
_count = 0;//实例属性
increment() {
this._count++;
}
}
(4)静态属性
ES6 明确规定,Class 内部只有静态方法,没有静态属性.
有一个提案提供了类的静态属性,写法是在实例属性的前面,加上static关键字。
class MyClass {
static myStaticProp = 42;
constructor() {
console.log(MyClass.myStaticProp); // 42
}
}
eg: function Phone(){
}
Phone.name='手机';
Phone.Change=function(){
log("改变世界")
}
Phone.protoType.size='5.5inch';
let nokia=new Phone();
log(nokia.name);//undefined
log(nokia.size);//5.5inch
//实例化对象无法继承获取到构造函数的静态成员
----class写法
class Phone{
static name='手机';
static chang=function(){
log('改变世界')
}
}
let nokia=new Phone();
log(nokia.name)//undefined
log(Phone.name)//手机
class私有方法和私有属性
![image-20210613160726831]()
私有方法和私有属性,是只能在类的内部访问的方法和属性,外部不能访问
私有方法做法:
1.在命名上加以区别
在类的外部,还是可以调用到这个方法
class Widget {
// 公有方法
foo (baz) {
this._bar(baz);
}
// 私有方法
_bar(baz) {
return this.snaf = baz;
}
// ...
}
2.私有方法移出类
class Widget {
foo (baz) {
bar.call(this, baz);
}
// ...
}
//私有方法移出类
function bar(baz) {
return this.snaf = baz;
}
私有属性做法:
为class加了私有属性。方法是在属性名之前,使用#表示。
class IncreasingCounter {
#count = 0;
get value() {
console.log('Getting the current value!');
return this.#count;
}
increment() {
this.#count++;
}
}
//只能在类的内部使用(this.#count)。如果在类的外部使用,就会报错。
const counter = new IncreasingCounter();
counter.#count // 报错
counter.#count = 42 // 报错
判断私有属性
(1)try...catch 可读性差
(2)in关键字
class A {
#foo = 0;
m() {
console.log(#foo in this); // true
console.log(#bar in this); // false
}
}
class实现继承
1.继承
Class 可以通过extends关键字实现继承,子类通过extends关键字,继承了Father类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个Point类
class Father {}
class Son extends Father {}
`注意`:父级的构造函数construator无法直接被继承,需要通过子级的构造函数去用super关键字调用,否则新建实例时会报错
before:class Point { /* ... */ }
class ColorPoint extends Point {
constructor() {}
}
let cp = new ColorPoint(); // 报错
after:class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
2.Object.getPrototypeOf()
方法可以用来从子类上获取父类。
Object.getPrototypeOf(ColorPoint) === Point
// true 可以使用这个方法判断,一个类是否继承了另一个类
3.super关键字
super这个关键字,既可以当作函数使用,也可以当作对象使用
(1)第一种情况,super作为函数调用时,代表父类的构造函数,ES6 要求,子类的构造函数必须执行一次super函数。即super()函数调用
class A {}
class B extends A {
constructor() {
super();
}
}
//new.target指向当前正在执行的函数
class A {
constructor() {
console.log(new.target.name);
}
}
class B extends A {
constructor() {
super();
}
}
new A() // A
new B() // B
(2)第二种情况,super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。即super.函数名()
class A {
A.prototype.x = 2;
constructor() {
this.p = 2;
}
}
class B extends A {
get m() {
return super.p;
}
constructor(){
super();
console.log(super.x) // 2
}
}
let b = new B();
b.m // undefined super指向父级的原型对象protoType,所以无法获取。但可以把父级的属性方法写在prototype上就可以通过super关键字获取
4.类的 prototype 属性和__proto__属性
Class 作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。
(1)子类的__proto__属性,表示构造函数的继承,总是指向父类。
(2)子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
class A {
}
class B extends A {
}
B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true
5.实例的 __proto__ 属性
子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性,子类的原型的原型,是父类的原型
var p1 = new Father(2, 3);
var p2 = new Son(2, 3, 'red');
p2.__proto__ === p1.__proto__ // false
p2.__proto__.__proto__ === p1.__proto__ // true
![image-20210613173620309]()
es5原型继承
function Phone(brand,price){
this.brand=brand;
this.price=price;
}
Phone.protoType.call=function(){
log('打电话')
}
//子集
function SmartPhone(brand,prcie,color,size){
Phone.call(this,brand,price);
this.color;color;
this.size=size;
}
//设置子级构造函数的原型
SmartPhone.protoType=new Phone;
SmartPhone.ProtoType.constructor=SmartPhone;
//声明子类的方法
SmartPhone.protoType.photo=function(){
log('拍照')
}
SmartPhone.protoType.PlayGame=function(){
log('玩游戏')
}
const chuizi=new SmartPhone('锤子',2499,'黑色','5.5inch')
log(chuizi);
----class继承
class Phone{
//构造方法
constructor(brand,price){
this.brand=brand;
this.price=price;
}
//父类的成员属性
call(){
log('打电话')
}
}
class SmartPhone extends Phone{
//构造方法
constructor(brand,price,color,size){
super(brand,price);
//Phone.call(this,brand,price)
this.color=color;
this.size=size;
}
Phone(){
log('拍照')
}
PlayGame(){
log('玩游戏')
}
}
const xiaomi=new SmartPhone('小米',799,'黑色','4.7inch')
log(xiaomi)
new.target属性
new命令引入了一个new.target属性,该属性一般用在构造函数之中,`返回new命令作用于的那个构造函数`。如果构造函数不是通过new命令或Reflect.construct()调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。
Class 内部调用new.target,返回当前 Class。
注意:子类继承父类时,new.target会返回子类
Proxy代理
1.概述:Proxy 用于修改某些操作的默认行为,Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
(1)声明
var proxy = new Proxy(target, handler);
target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。如果handler没有设置任何拦截,那就等同于直接通向原对象。
var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"
proxy拦截行为
1.get(目标对象,操作的属性名,[,proxy实例本身]):拦截对象属性的获取操作
var person = {
name: "张三"
};
var proxy = new Proxy(person, {
get(target, propKey){
if (propKey in target) {
return target[propKey];
} else {
throw new ReferenceError("Prop name \"" + propKey + "\" does not exist.");
}
}
});
proxy.name // "张三"
proxy.age // 抛出一个错误
2.set(目标对象,属性名,属性值,[Proxy实例本身]):拦截对象属性的设置操作
3.apply(目标对象, 目标对象的上下文对象(this),目标对象的参数数组。):apply方法拦截函数的调用、call和apply操作。
eg:
var target=function(){
return 'I am the target';
};
var handler = {
apply: function () {
return '拦截成功';
}
};
var p = new Proxy(target, handler);
p()// "拦截成功" 变量p是 Proxy 的实例,当它作为函数调用时(p()),就会被apply方法拦截,返回一个字符串
4.has(目标对象, 需查询的属性名):has()方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,典型的操作就是in运算符,返回一个布尔值。
eg:
var handler = {
has (target, key) {
if (key[0] === '_') {
return false;
}
return key in target;
}
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler);
'_prop' in proxy // false
如果原对象的属性名的第一个字符是下划线,proxy.has()就会返回false,从而不会被in运算符发现。
5.construct(目标对象, 构造函数的参数数组,[new命令作用的构造函数]):拦截 Proxy 实例作为构造函数调用的操作,比如new,`construct()方法返回的必须是一个对象,否则会报错`。
eg: const p = new Proxy({}, {
construct: function(target, argumentsList) {
return {};
}
});
new p() // 报错Uncaught TypeError: p is not a constructor
6.deleteProperty()用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除
eg: var handler = {
deleteProperty (target, key) {
invariant(key, 'delete');
delete target[key];
return true;
}
};
function invariant (key, action) {
if (key[0] === '_') {
throw new Error(`Invalid attempt to ${action} private "${key}" property`);
}
}
var target = { _prop: 'foo' };
var proxy = new Proxy(target, handler);
delete proxy._prop;
// Error: Invalid attempt to delete private "_prop" property
7.defineProperty()方法拦截了Object.defineProperty()操作。
8.getOwnPropertyDescriptor()方法拦截Object.getOwnPropertyDescriptor(),返回一个属性描述对象或者undefined。
9.getPrototypeOf()方法主要用来拦截获取对象原型,具体来说,拦截下面这些操作
(1)Object.__proto__
(2)Object.isPrototypeOf()
(3)Object.getPrototypeOf()
(4)Reflect.getPrototypeOf()
(5)instanceof
10.isExtensible()方法拦截Object.isExtensible()操作。
11.ownKeys()方法用来拦截对象自身属性的读取操作。具体来说,拦截以下操作
(1)Object.getOwnPropertyNames()
(2)Object.getOwnPropertySymbols()
(3)Object.keys()
(4)for...in循环
12.preventExtensions()方法拦截Object.preventExtensions()。该方法必须返回一个布尔值,否则会被自动转为布尔值
13.setPrototypeOf()方法主要用来拦截Object.setPrototypeOf()方法。
Reflect反射
Reflect与ES5的Object有点类似,包含了对象语言内部的方法,Reflect也有13种方法,与proxy中的方法一一对应。Proxy相当于去修改设置对象的属性行为,而Reflect则是获取对象的这些行为。
Module模块化
ES6模块
1.定义
将大程序拆分为相互依赖的小文件,再用简单的方法拼装起来。常用的模块有CommonJS和AMD模块,COmmonJS用于服务器,AMD用于浏览器。es6中添加了一个模块---es6模块 es6模块的设计思想是`尽量的静态化,使得编译时就能确定模块的依赖的关系以及输入和输出的变量`
2.CommonJS和JS模块的区别
CommonJS模块的实质是利用require方法整体加载fs模块(加载fs的所有方法),生成一个对象
es6模块不是对象,它是通过export命令显示指定输出的代码,再通过import命令输入
export和improt命令
1. ES6模块功能主要由两个命令构成:export和import ,export命令用于规定模块的对外接口,import命令用于输出其他模块提供的功能
写法:
//一个js里面---模块js
export var a='123',
export var b='456',
export var c='789',
//或者写成 export {a,b,c}
//另外一个js里面---载入js
import {a,b,c} form '模块js',
2.as关键字
可以更改对外接口传的变量名或函数名
//输出
export{
a as A,
b as B,
c as C
}
//输入
import{
A as AA,
B as BB,
C as CC
}
import命令输入的变量都是只读的,因为它的本质是输入接口,无法对变量进行赋值操作,但import获取的是一个对象就可以修改它的属性(建议不要轻易修改属性)。
模块的整体加载
除了指定加载某个输出值,还可以使用整体加载,即用星号(*)指定一个对象,所有输出值都加载在这个对象上面。但无法加载到export default模块化的变量或方法
// circle.js
export function area(radius) {
return Math.PI * radius * radius;
}
export function circumference(radius) {
return 2 * Math.PI * radius;
}
// main.js
import * as circle from './circle';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
`注意`:对对象进行加载只能是静态分析,不允许运行时改变
export default命令
1.export default命令,为模块指定默认输出。
2.使用export default时,对应的import语句不需要使用大括号
3.一个模块只能有一个默认输出,因此export default命令只能使用一次
4.export default命令本质:是输出一个叫做default的变量或方法,所以它后面不能跟变量声明语句。将后面的值,赋给default变量.
eg: var a = 1;
export default a;
export和import的复合写法
如果在`一个模块之中`,先输入后输出同一个模块,import语句可以与export语句写在一起。export命令输出的接口没有直接导到当前模块,而是将模块对外转发export命令的接口,然后再利用import命令导入当前模块里面
export { foo, bar}from 'my_module';
// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };
import()
由于export和import都是放在模块的顶层,js引擎处理在执行代码时对export和import会优先处理再作编译,这样会导致报错
// 报错
if (x === 2) {
import MyModual from './myModual';
}
es6提供了一个解决方案 import()函数,可以支持动态加载模块,import()函数返回一个promise对象
import('模块js')
.then(module => {
module.loadPageInto(main);
})
.catch(err => {
main.textContent = err.message;
});
Module的加载实现
1.在浏览器和Node.js中加载ES6模块
2.实现加载中实际开发问题:循环加载
浏览器加载
1.传统方法
通过script标签加载模块脚本
(1)页面内嵌脚本
<script>
//module code
</script>
(2)外部脚本
<script src='./xxx.js'>
</script>
注意:默认情况下,浏览器是同步加载 JavaScript 脚本,即渲染引擎遇到<script>标签就会停下来,等到执行完脚本,再继续向下渲染。如果是外部脚本,还必须加入脚本下载的时间。下载和执行的时间就会很长,因此造成浏览器堵。
2.解决浏览器同步方案:
<script src="path/to/myModule.js" defer></script>
<script src="path/to/myModule.js" async></script>
3.defer和async属性区别
defer要等到整个页面在内存中正常渲染结束,才会执行。----渲染完再执行
async是一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染---下载完就执行
加载规则
1.浏览器加载 ES6 模块,也使用<script>标签,但是要加入type="module"属性。
eg:<script type="module" src="./foo.js"></script>
浏览器对于带有type="module"的<script>,都是异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了<script>标签的defer属性。