ES6快速浏览
let和const关键字
let
- 局部变量
- 作用于块级作用域
,而{}
是函数级作用域var
- 必须先定义后才可使用,而
定义的变量会先提升成var
undefined
- 不可重复声明变量,
可var
const
- 常量,不可修改
- 块级作用域
- 对象常量,只不可修改对象整体,对象内部变量可修改,因此需要冻结对象或深度冻结
全局变量
-
与var a
window.a
-
与let
不可用const
window
变量的解构
数组的解构赋值
let [a,b,c] = [4,5,6];//定义时
[a, b] = [20, 30];//后期
let [age, [salary, [name]]] = [20,[3000,["Bill"]]];//嵌套
let [m, n] = ['a'];//a,undefined
let [v1, v2] = [1,2,3,4,5];//1,2
let [value1, value2, ...others] = [0,1,2,3,4,5,6,7,8,9];//others-->2,3,4,5,6,7,8,9
let [t1] = 20;//报错
let [x1, x2 = 20] = [100];//100,20 默认值
var [f1, f2 = f] = [1];//1,函数f 默认值也可以为函数
Generator函数的解构赋值
// 只要某种数据结构具有Iterator接口,都可以采用数据形式的解构赋值
// 每执行一次,都会指向yield语句
// Generator函数,天生具有Iterator接口
// Generator每调用一次,会暂停
function* gen(x)
{
while(true)
{
yield x;//执行一次,返回此时x
x = x + 2;
}
}
let [g1, g2, g3, g4] = gen(10);
console.log("g1 = " + g1);//10
console.log("g2 = " + g2);//12
console.log("g3 = " + g3);//14
console.log("g4 = " + g4);//16
对象的解构赋值
var {name, age} = {age:30, name:"Bill"};
({x,y} = {x:20, y:30});//后期赋值
var {value1, value2} = {value1:30};//30,undefined
var {product:a, price:b} = {product:"iPhone7", price:5000};//被赋值变量为a;product只做匹配
var {product:a,price} = {product:"iPhone7 Plus", price:6000};//被赋值变量为a,price
var obj = {p:["hello",{x:100}]};var {p:[s,{x:y}]} = obj;//嵌套赋值
var {x,y=10} = {x:2};//默认值
var {car} = {name:"Bill"};//解构失败,undefined
var obj = {fun1:function(x){return x*x;},fun2:function(x,y){return x+y;}};let {fun1,fun2} = obj;//可赋值函数
字符串的解构赋值
let [a,b,c,d,e] = 'hello';
函数参数的解构赋值
function sub([x,y])
{
return x - y;
}
[[1,2],[4,6]].map(([a,b])=>{let result = a + b; console.log(result);});
应用
变量交换
var m = 20;
var n = 30;
[m,n] = [n,m];
函数返回一个数组,将返回结果赋给多个变量
function multiNames()
{
return ["Bill", "Mike", "John"];
}
var [name1, name2, name3] = multiNames();
参数使用对象形式,从而可以无序传参
function sub({x,y,z})
{
return x - y - z;
}
sub({z:20, y:-15,x:10});
提取json数据
var jsonData =
{
id:18,
name:"Mike",
salary:4000
};
let {id, name, salary} = jsonData;
函数参数使用默认值
function fun(name, {flag = true, num = 100})
{
console.log(name, flag, num);
}
fun("John",{}); // {}不能省略
遍历map结构
任何部署了Interator接口的,都可以用for of遍历
var map = new Map();
map.set('id', 49);
map.set('name', "John");
map.set('age', 20);
for(let [key, value] of map)
{
console.log(key, value);
}
// 只获取key,无法按下面这种方式只获取value
for(let [key] of map)
{
console.log(key);
}
字符串的扩展
字符串变编码
- .charCodeAt()–>Unicode编码,不超过\uFFFF
- .codePointAt()–>Unicode2编码,超过\uFFFF
编码变字符串
- String.fromCharCode()
- String.fromCodePoint()
var ss = "?";
console.log(ss.length);
console.log("?:" + ss.charCodeAt(0).toString(16));//\ud83d
console.log("?:" + ss.charCodeAt(1).toString(16));//\udc12
console.log("\ud83d\udc12"); // utf-16
// 获取unicode2编码
console.log("?:" + ss.codePointAt(0).toString(16));//\u1f412
console.log("?:" + ss.codePointAt(1).toString(16));//\udc12
遍历
for…of
for(let c of 'world')
{
console.log(c);
}
查找子字符串
- indexOf([子字符串],[n开始查询位置])
- includes()–>true:子字符串存在,false:不存在
- startsWith()–>是否以指定子字符串开头
- endsWith()–>是否以指定子字符串结尾
// ES5:indexOf
console.log("abcde".indexOf("cd",0));
// ES6
console.log("hello world".includes("w"));
console.log("hello world".startsWith("hello"));
console.log("hello world".endsWith("world1"));
重复输出
.repeat()–>
- 如果参数是浮点数,会将浮点数转换为整数,向下取整
- 如果参数是负数或Infinity,抛出异常
- 特例:0到-1之间的浮点数,等同于0
- 如果参数是字符串,将字符串转换为数值型
- 如果参数值无法转换为数值,等同于0
console.log("hello".repeat(10));
console.log("h".repeat(0)); // 输出长度为0的字符串
console.log("wow".repeat(2.9)); //wowwow 2.9 => 2
console.log("ha".repeat(-0.5));//0
console.log("ha".repeat(NaN)); //0
console.log("ok".repeat("4"));//okokokok
console.log("ok".repeat('ok'));//0
模板字符串
增强型字符串,反单引号
- 保留字符串的格式
- 可大括号嵌入变量
- 可大括号引用表达式计算
- 可大括号引用函数
- 大括号引用字符串依然是字符串
- 大括号引用未定义变量异常
console.log(`hello
world`
);
var s = `你的名字是${name}`;
console.log(s);
var x = 20;
var y = 30;
console.log(`${x} + ${y} = ${x + y}`);
function fun()
{
return "It's beautiful";
}
console.log(`wow ${fun()}`);
console.log(`Hello ${'world'}`);
// 引用模板字符串本身
// 方法1:
let str1 = 'return ' + '`Hello ${name}`';
let func1 = new Function('name', str1);
console.log(func1("Mike"));
// 方法2:
let str2 = '(name)=>`Hello ${name}`';
let func2 = eval.call(null, str2);
console.log(func2('Mary'));
- 标签模板–>fun``
function func1(s, n1, n2,n3)
{
console.log(s);
console.log(n1);
console.log(n2);
console.log(n3);
}
// 调用格式:函数名+模板字符串
var n1 = 20;
var n2 = 30;
func1`abc${n1} xyz${n2}ok${n2 + n1}ddd`;
- .raw``方法–>直接输出最原始的字符串
console.log(String.raw`abc \n xyz`);
console.log(String.raw`abc \\n xyz`);
数值的扩展
二进制、八进制
从ES5开始,在严格模式中,八进制数值就不再允许使用前缀0表示,ES6进一步明确了这一点,要使用0o。
- 二进制:0b/0B
- 八进制:0o/0O
var n1 = 345;
var n2 = 0o531;
if(n1 == n2)
{
console.log("n1 == n2");
}
Number.infinite、Number.NAN
分别用来检测Infinite(数值且有限)和NaN(非数值,不是数字)两个值
传统方法:
isFinite
和
isNaN
新方法与传统的全局方法的区别:
- 传统方法先调用
将非数值转换为数值,再进行判断。Number()
- 新方法只对数值有效,对于非数值一律返回
。false
console.log(Number.isFinite(20));//true
console.log(Number.isFinite(0.4));//true
console.log(Number.isFinite(NaN));//false
console.log(Number.isFinite());//false
console.log(Number.isFinite(Infinity));//false
console.log(Number.isFinite('hello')); // false
console.log(Number.isFinite(true)); // false
console.log("--------------");
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN(15)); // false
console.log(Number.isNaN(9/NaN)); // true
console.log(Number.isNaN('false'/0)); // true;
// NaN:非数值
console.log(Number.isNaN('1'/'2')) ;// false
console.log(Number.isNaN('1'/'false')) ;// true
Number.parseInt、Number.parseFloat、Number.isInterger
// ES5的写法
console.log(parseInt('44.66')); // 舍去取整
console.log(parseFloat('65.34'));
// ES6的写法
console.log(Number.parseInt('44.66')); // 舍去取整
console.log(Number.parseFloat('65.34'));
console.log(Number.parseInt === parseInt); // true
console.log(Number.parseFloat === parseFloat); // true
// Number.isInteger用来判断一个值是否为整数,20和20.0是同一个值
console.log(Number.isInteger(20)); // true
console.log(Number.isInteger(20.4)); // false
console.log(Number.isInteger(20.0)); // true
console.log(Number.isInteger("14")); // false
console.log(Number.isInteger(true)); // false
判断浮点数相等Number.EPSILON
Number.EPSILON
极小值
if(((0.1 + 0.2) - 0.3) < Number.EPSILON)
{
console.log("等于");
}
else
{
console.log("不等于");
}
Math
-
.trunc:用于去除一个数的小数部分,返回整数部分,正负号保留
对于非数值,Math.trunc内部会使用Number方法将其转换为数值
对于空值和无法取整数的值,返回NaN
-
.sign:用于判断一个数到底是整数、负数、还是零
参数为正数:返回1
参数为负数:返回-1
参数为0:返回0
参数为-0:返回-0
其他值:返回NaN
-
- .cbrt:用于计算一个数的立方根
-
.clz32:JavaScript的整数使用32位二进制形式表示
方法返回一个数的32位无符号整数形式有多少个前导0
对于小数,只考虑整数部分
如果无法转换为数值,返回的是32,而不是NaN
-
.imul:返回两个数以32位带符号整数形式相乘的结果,返回的也是一个32位的带符号整数
如果两个整数的乘积超过了2^53次方,JavaScript无法保证结果的精度
- .fround:返回一个数的单精度浮点数形式
- .hypot:返回所有参数的平方和的平方根
- .expm1:返回e^x-1
- .log1p:返回ln(1+x)
- .log10:返回以10为底的x的对数,如果x小于0,则返回NaN
- .log2:返回以2为底的x的对数,如果x小于0,返回NaN
数组的扩展
.from(对象转换为数组)
两类对象:
- 类似数组的对象,即任何有length属性的对象
- 可遍历的对象(iterable),字符串,集合等
以key作为数组的索引(从0开始),如果key不是按顺序给出,那么数组当前元素值是undefined,需要使用length定义数组的长度,如果长度比数组元素个数大,后面的值都是未定义,如果小,后面的值被忽略,对象中属性顺序可以颠倒
let obj1 = {
'0':'hello',
'2':'中国',
'1':'world',
'3':'ok',
length:4
};
let array1 = Array.from(obj1);
console.log(array1);
Array.from方法还可以接受第二个参数,和map类似
console.log(Array.from([1,2,3,4],x=>x+x));
// 等同于下面的代码
console.log(Array.from([1,2,3,4]).map(x=>x+x));
// 填充随机数数组
console.log(Array.from({length:20},()=>Math.random()));
.of方法(一组值转换为数组)
console.log(Array.of(1,2,3,4,5));
console.log(Array.of(3));
console.log(Array.of(10).length);
.copyWithin(迁移数组元素)
target -->必选,迁移到哪儿
start -->必选,从哪儿开始迁移
end–>可选,哪儿结束迁移,默认到结尾
**[start,end)**迁移的内容,闭开区间
console.log([1,2,3,4,5,6].copyWithin(0, 3));//456456
console.log([1,2,3,4,5,6].copyWithin(1, 3,5));//145456
console.log([1,2,3,4,5,6].copyWithin(1, -3,-1));//145456
.find .findIndex(查找)
用来扫描数组,用来查找第一个满足条件的数组元素
-
返回数组元素find
-
返回数组元素的索引findIndex
var n = [3,5,1,10,6].find((n)=>n>3)
console.log(n);
var m = [1,2,-4,6].find(function(value,index,arr){
return value < 0
});
console.log(m);
var p = [1,2,-4,6].find((value,index,arr) => value < 0)
console.log(p);
var index = [1,2,-4,6].findIndex((value,index,arr) => value < 0)
console.log(index)
find和findIndex,可以接收第二个参数,用来绑定回调函数的this对象
和indexOf的区别:find和findIndex可以用来查找NaN
console.log([NaN].indexOf(NaN));
console.log([NaN].findIndex((n)=>Object.is(NaN,n)));
.fill(用给定的值填充数组)
常用来初始化数组
可选第二个第三个参数
console.log(new Array(10).fill(123));//[123,123,123......]
console.log(['x','y','z'].fill("hello world"));//["hello world","hello world","hello world"]
console.log(['1','2','3','5'].fill("xyz",1,3));//['1','xyz','xyz','5']
.entries、.keys(遍历)
- .keys–>索引
- .entries–>索引+值
for(let index of['a','b','c'].keys())
{
console.log(index)//0 1 2
}
for(let [index,value] of['a','b','c'].entries())
{
console.log(index) //0 1 2
console.log(value) //a b c
}
数组的空位
没有值
var arr1 = Array(3); // [,,,],应该是三个逗号而不是两个
var arr2 = [,,,]
var arr3 = [undefined, undefined, undefined]
console.log(0 in arr2); // false,第0个位置是否有值
console.log(0 in arr3); // true
- ES5:大多数情况下会忽略空位
forEach、filter、every和some都会跳过空位
map会跳过空位,但会保留这个值
join和toString会将空位视为undefined
undefined和null 会被处理成空字符串
- ES6明确将空位转为undefined
- 扩展运算符(…)也会将空位转为undefined
function fun(a,b,c)
{
console.log(a);
console.log(b);
console.log(c);
}
var arr4 = ['x',,'y'];
fun(...arr4);//x undefined y
- for…of也会将空位转为undefined
let arr5 = ['a',,'b']
for(let i of arr5)
{
console.log(i);//a undefined b
}
函数的扩展
函数的默认值
ES5判断的方法
function write(x,y)
{
// 无效的值:undefined、NaN、null、''(空串)
y = y || 'JavaScript';//判断y是否有效,若有效返回第一个值
console.log(x,y);
}
write('Hello', 'World');
write('Hello');//Hello JavaScript
write('Hello', NaN);//Hello JavaScript
write('Hello', null);//Hello JavaScript
write('Hello', '')//Hello JavaScript
ES5其他判断函数参数默认值的方法
function write1(x,y)
{
if(typeof y == 'undefined')
{
y = 'JavaScript'
}
console.log(x,y);
}
// 方法2
function write2(x,y)
{
if(arguments.length == 1)
{
y = 'JavaScript'
}
console.log(x, y)
}
write1('Hello')//Hello JavaScript
write1('Hello', '')//Hello
write2('Hello')//Hello JavaScript
write2('Hello', '')//Hello
ES6默认值
function write3(x,y='JavaScript')
{
console.log(x,y)
}
write3('Test');//Test JavaScript
write2('Test', '');//Test
function Product(name='iPhone', price=6000)
{
this.name = name;
this.price = price;
}
var product = new Product()
console.log(product.name);
console.log(product.price);
参数默认值与解构赋值默认值结合
function fun1({x,y=5})
{
console.log(x,y)
}
//fun1();//报错
fun1({});//undefined 5
fun1({x:10});//10 5
fun1({x:12,y:20});//12 20
解构赋值默认值和函数参数默认值结合
function fun2(n, {value1='', value2=20})
{
console.log(value2);
}
//fun2(123) // error
fun2(123,{});// 20
fun2(123,{value2:100});// 100
两种例子,有什么区别
function f1({x = 0, y = 0} = {})
{
console.log(x,y);
}
function f2({x, y} = {x:0, y:0})
{
console.log(x,y);
}
f1()// 0 0
f2()// 0 0
f1({x:10, y:20})// 10 20
f2({x:10, y:20})// 10 20
f1({x:20}); // 20, 0
f2({x:20}); // 20, undefined
f1({}); // 0 0
f2({}); // undefined undefined
f1({p:10}); // 0 0
f2({p:20}); // undefined undefined
参数默认值的位置与调用方式
不同位置,调用方法不同
// demo1
function fun1(x,y,z=20)
{
console.log(z);
}
fun1(1,2,3);
fun1(1,2);//前两个参数必须有
// demo2
function fun2(x, y=123,z = 200)
{
console.log(y,z);
}
fun2(20);//第一个参数必须有
fun2(20,44);
// demo3
function fun3(x, y = 345, z)
{
console.log(y);
}
fun3(20,123,100);//全部参数必须有
//fun3(20,,100);//报错
length属性(获取无默认参数的个数)
length属性从左向右检测函数参数属性,一旦遇到第一个有默认值的参数,就会停止检测,并返回第一个带有默认值参数前面参数的个数
console.log((function(x){}).length)//1
console.log((function(x = 10){}).length)//0
console.log((function(x,y=100,z,t){}).length) // 1
console.log((function(x,y,z=100){}).length) // 2
console.log((function(x,...args){}).length) // 1
函数参数的作用域
var x = 10;
function fun1(m, y = x)
{
console.log(y);
}
fun1(100);//100
/* error
function fun2(m, y = n)
{
let n = 90;
console.log(y);//n未定义错误
} */
// fun2(100)
//默认值为函数
var value = 'hello';
function fun3(value = 'xyz', func = x=>value){
console.log(func());
}
fun3()//xyz
参数默认值应用
function throwIfMissing()
{
throw new Error('必须指定该参数值');
}
function fun1(x,y = throwIfMissing())
{
console.log(y);
}
fun1(10);//抛出异常-->必须指定该参数值
function fun2(x, y = undefined)
{
console.log(y)//undefined
}
rest参数
放在函数参数最后面,指任意多个参数
function add(...values)
{
let sum = 0;
for(var v of values)
{
sum += v;
}
return sum;
}
console.log(add(1,4,5,7))
function fun1(a,b,...c)
{
console.log(a,b);
for(var v of c)
{
console.log(v)
}
}
fun1(1,2,3,4)
arguments和rest参数的区别:
- arguments不是数组
- rest参数是数组
function sort1()
{
const sortedNumbers = Array.prototype.slice.call(arguments).sort();//不能直接调用sort()
return sortedNumbers;
}
console.log(sort1(3,2,1))
function sort2(...numbers)
{
const sortedNumbers = numbers.sort();//能直接调用sort()
return sortedNumbers;
}
console.log(sort2(5,4,3));
参数长度区别:
- 整个函数的参数是形参,不考虑rest参数和带默认值的参数
- arguments考虑的是值参
function fun2(a,b,c,d,...e)
{
// arguments考虑的是值参
console.log(arguments.length) // 10
}
fun2(1,2,3,4,5,6,7,8,9,10)
console.log((function (a,b,c,d,...e)
{
console.log(arguments.length) // 10
}).length) // 4 考虑的是形参,但不包括rest参数和带默认值的参数
扩展运算符
...
三个点表示,将数组变成单值
var values = [1,2,3,4];
console.log(...values);// 1 2 3 4
console.log(values);//[1,2,3,4]
console.log('abc','xyz',...values,30);
对于rest参数,调用时只能传递多个单值,不能传递数组
function addNumbers(...numbers)
{
var sum = 0;
for(var n of numbers)
{
sum+=n;
}
return sum;
}
var arr = [1,4,7,12,-3];
// console.log(addNumbers(arr)) // error 对于rest参数,只能传递多个单值,不能传递数组
console.log(addNumbers(...arr)) // ok
扩展运算符应用
合并数组
var arr1 = ['a','b']
var arr2 = ['c','d']
var arr3 = ['e','f']
// ES5
var arr4 = arr1.concat(arr2, arr3);
console.log(arr4);
// ES6
var arr5 = [...arr1, ...arr2, ...arr3]
console.log(arr5)
扩展运算符与解构赋值结合
rest必须是数组的最后一个元素
const [a,...b] = [1,2,3,4]; // ...b rest参数
console.log(a);
console.log(...b);
const [first, ...rest] = [];
console.log(first); // undefined
console.log(rest); // []
const [x, ...y] = [10];
console.log(x)//10
console.log(y)//[]
//const [...k, t] = [10,20] // error rest必须是数组的最后一个元素
函数的返回值
function fun1()
{
return [1,2,3,4]
}
function fun2(a,b,c,d)
{
console.log(a,b,c,d)
}
var values = fun1();
fun2(...values) // 可以通过扩展运算符将函数返回的数组解析成多个值
字符串
字符串拆成数组
var str = "hello";
var strArray = [...str]; // 不能是...str
console.log(strArray);
正确识别32位Unicode
console.log('a\uD83D\uDE80b'.length); // 4,无法获得真正的字符串长度
console.log([...'a\uD83D\uDE80b'].length);//3,真正的长度
console.log([...'a\uD83D\uDE80b'][1]);//小火箭图标
function length(str)
{
return [...str].length
}
console.log(length([...'a\uD83D\uDE80b']));//3
var str = 'a\uD83D\uDE80b';//\uD83D\uDE80b-->小火箭
console.log(str);//a小火箭
var str1 = str.split('').reverse().join('');
console.log(str1);//翻转失败,buDE80\uD83D\a'
var str2 = [...str].reverse().join('');
console.log(str2);//小火箭a,正确翻转
可以处理类似数组的对象(Iterator)
let map = new Map(
[
[1, 'one'],
[2, 'two'],
[3, 'three']
]
);
let arrKeys = [...map.keys()];
console.log(...arrKeys);//1 2 3
let arrValues = [...map.values()];
console.log(...arrValues);//one two three
name属性
返回该函数的属性名
function myfun(){}
console.log(myfun.name);
var fun1 = function(){}//匿名函数
// ES5是空串,ES6是变量名
console.log(fun1.name) // fun1
var fun2 = function process(){}
console.log(fun2.name); // process
console.log((new Function).name) // anonymous
function test(){}
console.log(test.bind({}).name) // bound test,前置bound
箭头函数
使用=>定义的函数
- =>的左侧表示函数的参数,右侧表示函数体
- 如果=>右侧只有一条语句,该条语句将作为return的参数
var f = x => x;
console.log(f(100))
// 相当于
var f1 = function(x)
{
return x;
}
// 如果箭头函数的参数没有,或有多个参数
var f2 = ()=> 123;
f2 = function()
{
return 123;
}
// 箭头函数和变量结构结合使用
var f5 = ({first, last}) => first + '.' + last;
var person = {first:'Bill', last:'Gates'};
console.log(f5(person));
//相当于
function f6(person)
{
return person.first + '.' + person.last;
}
console.log(f6(person));
可以简化回调函数
var arr2 = [1,2,3,4,5].map(x=>x*x)
console.log(...arr2)
var result2 = values.sort((a,b)=>a-b);
console.log(...result2)
rest参数也可以用于箭头函数
var fun = (...nums)=>nums; // rest参数也可以用于箭头函数
console.log(fun(1,2,3,4,5,6))
对象的扩展
属性和方法简洁表示
属性定义时
// ES5
var obj1 = {name:'Bill'};
console.log(obj1.name)
// ES6
var name = 'Mike';
var obj2 = {name};
console.log(obj2.name);
返回值时
// ES6中的返回方式
function fun1(x,y,z)
{
return {x,y,z};
}
// ES5中的返回方式
function fun2(x,y,z)
{
return {x:x,y:y,z:z};
}
console.log(fun1(1,2,3))
console.log(fun2(4,5,6));
方法定义时
// ES5:方法的传统表示法
var obj3 = {
process:function()
{
return "I love you."
}
};
// ES6:方法的简洁表示法
var obj4 = {
process()
{
return "How are you?"
}
}
console.log(obj3.process());
console.log(obj4.process());
简洁定义示例
let price = 7000;
var product =
{
productName:'iPhone7 Plus',
price,
buy(name, currentPrice)
{
console.log('已经购买');
return {name, currentPrice,discount:price - currentPrice};
}
}
console.log(product.productName);
console.log(product.buy('John', 6500));
属性名表达式
ES5,有三种向对象中添加属性的方法,其中两种是动态的,一种是静态的
// 静态添加属性
var obj1 = {
name:'Bill'
};
// 动态一
obj1.age = 30;
// 动态二
obj1['salary'] = 2000;
使用表达式
obj1['hello' + 'world'] = 'hello world';
const p = 'x';
obj1[p+p] = 'xx';
console.log(obj1.name)
console.log(obj1.age)
console.log(obj1.salary)
console.log(obj1.helloworld);
console.log(obj1.xx);
ES6,定义时方括号内使用表达式
var obj2 =
{
name:'Mike',
['product' + 1]:'iPhone8'
}
console.log(obj2.product1);
var y = 'hello';
var obj3 =
{
[y]:'world' // 如果要解析变量,需要使用方括号
};
console.log(obj3.hello)
var obj4 =
{
['hello']:'hello world' // ok,如果属性名直接是一个字符串值,可以不适用方括号
// 当然,使用方括号也可以
}
console.log(obj4.hello)
方法名也可以
let obj5={
['pro' + 'cess']()
{
console.log('process')
}
};
obj5.process();
属性名表达式和简洁表示法不能同时使用,否则会报错
var name1 = 'kkk';
/* error
* var obj6 = {
[name1]
}*/
var obj6 = {
[name1]:'xxx' // 属性名使用表达式,右侧必须指定属性值
}
console.log(obj6.kkk)
Object.is
console.log(Object.is('abc', 'abc'));
console.log(Object.is({},{}));
Object.is方法
- +0和-0不相等
- NaN等于自身
console.log(+0 === -0); // true
console.log(Object.is(+0, -0)); // false
console.log(NaN === NaN); // false
console.log(Object.is(NaN, NaN)); // true
Object.assign
复制对象属性,assign参数有两个:
- targetObject
- …rest参数(sourceObject)
var target = {id:10};
var source1 = {name:'iPhone'};
var source2 = {price:7000};
var source3 = {location:'中国'};
Object.assign(target, source1,source2,source3);//将source1 2 3属性添加到target
console.log(target);
若有重复属性,后面的覆盖前面的
target = {a:1};
source1 = {a:20};
source2 = {b:30};
source3 = {b:50};
Object.assign(target, source1, source2, source3);//a 20 b 50
// Object.assign(target, source1, source3, source2);//a 20 b 30
console.log(target);
有嵌套式时,直接覆盖,只复制自身的属性
var target = {a:{b:'c',d:'e'}};
var source = {a:{b:'ok'}};
Object.assign(target, source);// a {b:'ok'}
console.log(target);
不可枚举的属性和继承的属性不会复制, 不可枚举:enumerable = false
var target = {a:'a'};
var source = Object.defineProperty({b:'b'},'name',{
enumerable:false,
value:'Bill'
});
console.log(target)
console.log(source)
Object.assign(target, source)//a b
console.log(target)
可以复制数组
var targetArray = [1,2,3];
var sourceArray = [4,5];
Object.assign(targetArray,sourceArray)
console.log(targetArray); // [4,5,3]
应用
为对象添加属性
class MyClass
{
constructor(x,y)
{
Object.assign(this, {x,y})
}
}
var my = new MyClass(10,20);
console.log('x', my.x);
console.log('y', my.y);
// 方法二
Object.assign(my, {name:'Bill'});
console.log('name', my.name);
// 方法三,类为原型添加
Object.assign(MyClass.prototype, {price:1200});
console.log('price', my.price);
为对象添加方法
Object.assign(MyClass.prototype, {
add(x,y)
{
return x + y;
}
});
console.log('add', my.add(10, 40));
克隆对象
function cloneObject(origin)
{
return Object.assign({},origin);
}
var origin = {id:20, name:'Bill'};
console.log('origin',cloneObject(origin));
合并多个对象
var target = {a:10}
var source1 = {b:20}
var source2 = {c:30}
var source3 = {d:40}
const merge = Object.assign(target, source1, source2, source3);
console.log(merge)
为属性指定默认值
const DEFAULTS =
{
name:'Mike',
price:2000
}
function process(options)
{
var obj = {}
Object.assign(obj, DEFAULTS, options)
return obj;
}
console.log(process({a:20,price:3000}))//a 20 name Mike price 3000
Symbol
为了解决对象属性冲突问题,第7种数据类型
//let s = Symbol(); // s就是独一无二的
let s = Symbol("s"); // s就是独一无二的
console.log(typeof s);//Symbol
let ss = Symbol("ss");
console.log(typeof ss);//Symbol
console.log(s);//Symbol(s)
console.log(ss);//Symbol(ss)
独一无二
let aa = Symbol("x");
let bb = Symbol("x");
if(aa === bb)
{
console.log("aa === bb");
}
else
{
console.log("aa != bb");
}
//"aa != bb
Symbol不能与其他类型的值进行运算,否则会报错
不能隐式转换到字符串
可以显式转换为String
var sym = Symbol('Test Symbol');
//var sss = "hello" + sym; // 不能隐式转换到字符串
var str = String(sym); // 方法1
console.log(str)
console.log(sym.toString()); // 方法2
Symbol可以转为布尔值,但不能转为数值
var sym1 = Symbol("abc");
console.log(Boolean(sym)); // true
应用
作为属性名
var id = Symbol();
var name1 = Symbol();
var obj1 = {};
// 方法1
obj1[id] = 200;
obj1[name1] = 'Mike';
// 方法2
var obj2 = {
[id]:300 // 如果不加方括号,name属性名的类型就是String
// 而不是Symbol
};
// 方法3
var obj3 = {};
Object.defineProperty(obj3, name1, {value:'John'});
console.log('obj1 id', obj1[id]);//200
- Symbol不能使用点(.)运算符
- 对象内部也需要使用方括号定义属性
let sym = Symbol();
let obj = {
// Symbol属性必须加方括号,否则就变成了字符串属性了
[sym]:function(){console.log('hello world')}
};
obj[sym]();
定义常量
let CONST ={
DEBUG:Symbol('debug'),
INFO:Symbol('info'),
WARNING:Symbol('warning')
};
console.log(CONST.INFO.toString());
const COLOR_WHITE = Symbol('white');
const COLOR_RED = Symbol('red')
遍历
getOwnPropertySymbols,只Symbol属性
var obj = {name:'Bill'};
var a = Symbol('a');
var b = Symbol('b');
obj[a] = 'Hello';
obj[b] = 'World';
var objectSymbols = Object.getOwnPropertySymbols(obj);
console.log(objectSymbols)
for in非Symbol属性
for(var key in obj)
{
console.log(key);
}
Reflect.ownKeys,同时获取Symbol和非Symbol属性
console.log(Reflect.ownKeys(obj));
可以利用Symbol的特性为对象添加一些非私有的,但又希望 只用于内部的方法或属性
var size = Symbol('size');
class Collection
{
constructor()
{
this[size] = 0; // 为对象添加一个Symbol类型的名为size的属性,初始值为0
}
add(item)
{
this[this[size]] = item;
this[size]++;
}
static sizeOf(instance)
{
return instance[size];
}
}
var cc = new Collection();
console.log(Collection.sizeOf(cc));//0
cc.add('ok');//0 ok
cc.add("xyz");//1 xyz
console.log(Collection.sizeOf(cc));//2
console.log(Object.keys(cc));//["0","1"]
console.log(Object.getOwnPropertySymbols(cc))//[Symbol('size')]
.for与.keyFor方法
Symbol.for首先会向全局环境注册名为key的Symbol变量
第一次调用for方法,会返回一个新的Symbol变量,如果以后再调用for方法,并且for方法的参数值(key)在全局环境中存在,那么for方法会返回这个已经注册的Symbol变量
var name1 = Symbol("name")
var name2 = Symbol("name")
console.log("name1 === name2", name1 === name2);//false
var id1 = Symbol.for("id")
var id2 = Symbol.for("id")
console.log("id1 === id2", id1 === id2);//true
keyFor:返回某个已经注册的Symbol变量的key
console.log(Symbol.keyFor(id1));//id
console.log(Symbol.keyFor(name1));//未定义,因为name1并没有全局环境注册
console.log(Symbol.keyFor(id2));//id
Proxy
用于拦截对象的操作
var obj1 = {name:'Bill',
process:function()
{
//console.log('----')
console.log('process Bill')
}
};
var obj2 = {name:'Mike',
process:function()
{
//console.log('----')
console.log('process Mike');
}
};
console.log(obj1.name);
obj1.process();
console.log(obj2.name);
obj2.process();
var obj = new Proxy(target, handler);
target:要拦截的对象
handler:拦截的动作,取代原来的方法,要想以前功能依然在,使用Reflect.method
var obj = new Proxy(obj1, {
get:function(target, key,receiver)
{
console.log('----');
return Reflect.get(target, key, receiver);
}
});
console.log(obj.name);
obj.process();
用代理拦截多个对象,单独将handler提出来
var handler = {
get:function(target, key,receiver)
{
console.log('----');
return Reflect.get(target, key, receiver);
}
}
var proxyObj1 = new Proxy(obj1, handler);
var proxyObj2 = new Proxy(obj2, handler);
console.log(proxyObj1.name);
proxyObj1.process();
console.log(proxyObj2.name);
proxyObj2.process();
读取拦截get
拦截属性的读取操作
var product = {
name:'iPhone'
};
//读取不存在的属性抛出异常
var proxy = new Proxy(product, {
get:function(target, property)
{
if(property in target)
{
return target[property];
}
else
{
throw new ReferenceError("属性 \"" + property + "\"不存在!");
}
}
});
console.log(proxy.name);
get代理的继承
let proto = new Proxy({},{
get(target, propertyKey,receiver)
{
console.log('GET ' + propertyKey);
return target[propertyKey];
}
});
let obj = Object.create(proto);
obj.name;
应用:利用get代理读取数组的负索引
var arr1 = [1,2,3];
//console.log(arr1[-1]);
// arr1[0] = arr1[-3] arr1[2] = arr1[-1]
function createSuperArray(...elements)
{
let handler = {
get(target, key, receiver)
{
let index = Number(key);
if(index < 0)
{
key = String(target.length + index)//-1-->length-1
}
return Reflect.get(target, key,receiver);
}
}
let target = [];
target.push(...elements);
return new Proxy(target, handler);
};
let arr2 = createSuperArray(1,2,3);
console.log(arr1[1]);//2
console.log(arr1[-1]);//未定义
console.log(arr2[1]);//2
console.log(arr2[-1]);//3
console.log(arr2[-2]);//2
console.log(arr2[-3]);//1
写操作拦截set
校验属性值
var product = {
name:'特斯拉汽车',
price:80 // 30-180
};
let validator = {
set:function(obj, key, value)
{
// 首先判断属性名
if(key == 'price')
{
if(!Number.isInteger(value))
{
throw new TypeError('价格必须是整数');
}
if(value < 30 || value > 180)
{
throw new RangeError('价格必须在30到180之间');
}
}
obj[key] = value;
}
};
let product1 = new Proxy(product, validator);
// product1.price = 200;
product1.price = 170;
console.log(product1.price);
// product1.price = 'abc';
控制属性是否可访问
对象的内部属性 属性名以下划线(_)开头
var handler = {
get(target, key)
{
invariant(key,'get');
return target[key];
},
set(target, key,value)
{
invariant(key,'set');
return true;
}
};
function invariant(key,type)
{
if(key[0] == "_")
{
throw new Error(`内部属性不能被访问 ${type}`);
}
};
var obj = {
name:'Bill',
_value:20
};
var objProxy = new Proxy(obj, handler);
console.log(objProxy.name);
// console.log(objProxy._value);
objProxy.name = "Mike";
// objProxy._value = 40;
拦截函数的调用:call和apply
改变函数返回值
var fun1 = function(){return '世界您好!';}
var handler = {
apply:function()
{
return 'hello world';
}
};
console.log(fun1());//世界您好!
var funProxy = new Proxy(fun1, handler);
console.log(funProxy());//hello world
修改原返回值
function sum(num1, num2)
{
return num1 + num2;
}
console.log(sum(20,40));//60
var twice = {
apply(target, ctx, args)
{
return Reflect.apply(...arguments) * 2;
}
};
var sumProxy = new Proxy(sum, twice);
console.log(sumProxy(20,40));//120
console.log(sumProxy.call(null, 20,40));//120
console.log(sumProxy.apply(null, [30,50]));//160
拦截in操作has
隐藏属性,只影响in判断
var handler = {
has(target, key)
{
//console.log("<" + key + ">");
if(key[0] == '_')
{
return false;
}
return key in target;
}
};
var obj = {name:'iPhone7', price:5800, _value:200};
var objProxy = new Proxy(obj, handler);
console.log('_value' in obj); // true
console.log('_value' in objProxy); // false
console.log('price' in obj); // true
console.log('price' in objProxy); // true
has方法并不会影响for in操作
for(key in objProxy)
{
console.log(key)
}
如果原对象不可配置或禁止扩展,那么这时has拦截会报错
var obj1 = {a:20};
Object.preventExtensions(obj1);
var proxy = new Proxy(obj1, {
has:function(target, key)
{
return false;
}
});
// 'a' in proxy //抛出异常
拦截new指令contrust
var handler = {
construct(target, args)
{
console.log('construct');
return new target(...args);
}
};
var proxy = new Proxy(function(){}, handler);
new proxy();//contrust
返回的不是对象,抛出异常
var handler1 = {
construct(target, args)
{
console.log('construct');
return 20;
}
};
var proxy1 = new Proxy(function(){}, handler1);
//new proxy1(); //抛出异常
可以返回别的对象
var handler2 = {
construct(target, args)
{
console.log('construct');
return {name:'Bill'};
}
};
var proxy2 = new Proxy(function(){}, handler2);
console.log(new proxy2().name);
拦截delete操作deleteProperty
var handler = {
deleteProperty(target, key)
{
delete target[key]; // 需要在该方法中再次删除对象属性
console.log('删除了' + key + "属性");
return true;
}
};
var obj = {name:'Bill', age:40};
var objProxy = new Proxy(obj, handler);
console.log(objProxy.age);
delete objProxy.age;
console.log(objProxy.age);
拦截define操作defineProperty
拦截动态添加属性
var obj = {};
var handler = {
defineProperty(target, key, descriptor)
{
console.log("<" + key + ">")
// 要想让被拦截的操作仍然发挥原来的作用,需要调用
// Reflect.method
// 否则defineProperty会进入递归
return Reflect.defineProperty(target, key, descriptor);
}
}
var proxy = new Proxy(obj, handler);
proxy.name = "Bill";
console.log('name' in proxy);
Reflect
将Object对象的一些明显属于语言层面的方法放到Reflect中
Object和Reflect的方法是一样的,以后再部署语言层面的方法会只部署在Reflect中
defineProperty、deleteProperty、has
Generator
是个状态机,并延后执行 ,不会立刻执行
普通函数加*,一个yield表示一个状态
function* helloworldGenerator()
{
console.log('第一次执行');
yield 'hello'; // 一个yield表示一个状态
console.log('第二次执行');
yield 'world';
console.log('第三次执行');
return 'ending'; // 非必须,也表示一个状态,最后一个状态
}
var hw = helloworldGenerator(); // 返回一个遍历器对象,状态的集合
var obj = hw.next(); // 状态切换,next返回一个普通对象
console.log(obj); // value-hello done-false
console.log(obj.value); // hello
console.log('-------------------------------');
obj = hw.next(); // 状态切换,next返回一个普通对象
console.log(obj); // value-world done-false
console.log(obj.value);
console.log('-------------------------------');
obj = hw.next(); // 状态切换,next返回一个普通对象
console.log(obj); // value-ending done-true
console.log(obj.value);
console.log('-------------------------------');
obj = hw.next(); // 状态切换,next返回一个普通对象
console.log(obj); // value-undefined done-true
console.log(obj.value); // undefined
console.log('-------------------------------');
yield
function sum(...values)
{
var n = 0;
for(let v of values)
{
n += v;
}
console.log('sum');
return n;
}
function* gen()
{
yield 10 + 20;
yield 10 * sum(1,2,3,4);
}
var obj = gen();
console.log(obj.next());
console.log(obj.next());
Generator函数可以不用yield,Generator就变成了暂缓执行的函数
function* fun()
{
console.log('fun');
}
var obj1 = fun();
obj1.next();
yield不能用于普通函数,否则会抛出异常
function process()
{
// yield 'abc';
}
yield用在一个表达式中,必须放在圆括号里
function* gen1()
{
// console.log('Hello' + yield 'world');
console.log('Hello' + (yield 'world'));
}
var obj2 = gen1();
obj2.next();// fun,暂停了
obj2.next();// helloundefined
yield语句用于函数参数或用于赋值表达式的右边,可以不加圆括号
function* gen2()
{
sum(yield '1', yield '2');
var input = yield 20;
}
gen2().next();
next()
function* gen()
{
var n = yield 20;
console.log(n);
}
var obj = gen();
obj.next(); //暂停
obj.next(200); // 需要给第二个next方法传递参数
- next方法执行时,yield后面的变量值即next方法返回的value值,next方法本身返回done和value两个属性的对象,并可以通过传值改变这属性value的值。
- next()传的参数即此次yield语句的值
function* gen1()
{
for(var i = 0; true;i++)
{
var reset = yield i;
if(reset)
{
i = -1;
}
}
}
var obj1 = gen1();
console.log(obj1.next());//0 false
console.log(obj1.next());//1 false
console.log(obj1.next());//2 false
obj1.next(true);//重新开始,i=-1后加一 // 0 false
console.log(obj1.next()); // 1 false
console.log(obj1.next()); // 2 false
console.log(obj1.next()); // 3 false
for of
切换状态,并不包含return的返回值
function* gen()
{
yield 'a';
yield 'b';
yield 'c';
yield 'd';
return 'x';
}
// a b c d
for(let v of gen())
{
console.log(v);//a b c d
}
fibonacci数列
function* fibonacci()
{
let [prev,curr] = [0, 1];
for(;;)
{
[prev, curr] = [curr, prev + curr];
yield curr;
}
}
/*var obj = fibonacci();
console.log(obj.next().value);
console.log(obj.next().value);
console.log(obj.next().value);*/
for(let n of fibonacci())
{
if(n > 1000) break;
console.log(n);
}
for…of循环可以写出遍历任何对象的方法。原生的JavaScript对象 没有遍历接口
function* objectEntries(obj)
{
let propKeys = Reflect.ownKeys(obj);//获得对象所有属性
for(let propKey of propKeys)
{
yield [propKey, obj[propKey]];
}
}
let arr = {name:'Bill', age:30};
for(let [key, value] of objectEntries(arr))
{
console.log(`${key}:${value}`);
}
throw()
var g = function*()
{
while(true)
{
try
{
yield;
}
catch(e)
{
if(e != 'a') throw e; //throw异常后结束generator函数
console.log('Generator内部错误', e);
}
}
}
var obj = g();
obj.next();
try
{
obj.throw('a');//内部 a
obj.throw('b');//外部 b
}
catch(e)
{
console.log('外部异常', e);
}
不用try catch,throw后直接跳出循环
var gg = function*()
{
while(true)
{
yield;
console.log('内部捕获', e);
}
};
var obj1 = gg();
obj1.next();
try
{
obj1.throw('a'); // 外部异常a
obj1.throw('b'); // 没有执行
}
catch(e)
{
console.log('外部捕获', e);
}
应用:提前结束状态
function fun(s)
{
console.log(s + s);
}
function *gen()
{
try
{
var a = yield fun('a');
var b = yield fun('b');
var c = yield fun('c');
}
catch(e)
{
console.log(e);
}
}
var obj2 = gen();
obj2.next();// aa
//console.log(obj2.next());
obj2.throw('aaa');//aaa,提前结束
obj2.next();//
obj2.next();
应用:函数内部抛出异常,如果内部没有try catch,也会被外部异常捕获
function *gen1()
{
var x = yield 10;
var y = x.toUpperCase();
yield y;
}
var obj3 = gen1();
obj3.next();
try
{
obj3.next(33);
}
catch(e)
{
console.log(e);
}
return()
也是结束状态,return方法返回值与其参数相同
function *gen()
{
yield 'a';
yield 'b';
yield 'c';
}
var obj = gen();
console.log(obj.next()); // a
console.log(obj.return('hello world')); // hello world
console.log(obj.next()); // undefined
如果有finally,return 会直接跳到finally的第一个yield语句,并返回yield后面表达式的值。如果finally里面没有yield,则返回 return的参数值
function* gen1()
{
yield 'a';
try
{
yield 'b';
yield 'c'; // 不会执行
}
finally
{
yield 'd';
yield 'e';
}
yield 'f'; // 不会执行
}
var obj1 = gen1();
console.log(obj1.next()); // 'a'
console.log(obj1.next()); // 'b'
console.log(obj1.return('hello')); // d
console.log(obj1.next()); // e,finally里的yield必须都执行完
console.log(obj1.next()); // hello,参数值也要指行完
console.log(obj1.next()); // undefined,return已提前结束状态
yield*与递归generator函数
递归函数:在函数内部调用函数自身
递归Generator函数:在Generator函数内部需要调用Generator函数自身
在Generator函数内部是否可以调用另外一个Generator函数?
在一个Generator函数中直接调用另外一个Generator函数是没有任何作用的
function* gen1()
{
console.log('x');
yield 'a';
console.log('y');
yield 'b';
}
function* gen2()
{
yield 1;
yield* gen1();//相当于把代码复制过来
//yield gen1();
yield 2;
}
var obj = gen2();
console.log(obj.next());
console.log(obj.next());
console.log(obj.next());
console.log(obj.next());
利用Generator递归函数枚举嵌套数组中的所有值
const array = ['a', [1,2], ['b', [3, [4, 'c']],'d'],["abc","xyz"]];
function* enumerateArray(array)
{
if(Array.isArray(array))
{
for(let i = 0; i < array.length;i++)
{
yield* enumerateArray(array[i]);
}
}
else
{
yield array;
}
}
for(let x of enumerateArray(array))
{
console.log(x);
}
将Generator函数作为对象属性
//方法一
var obj1 = {name:'Bill', *gen(){yield 10}};
console.log(obj1.gen().next());// 10 false
console.log(obj1.gen().next());// 10 false
//方法二
var obj2 = {name:'Mike', gen:function*(){yield 'hello world'}};
console.log(obj2.gen().next());
this
ES6规定这个遍历器是Generator函数的实例,这个实例也继承了Generator函数的prototype对象上的方法
function* gen()
{
}
gen.prototype.process = function()
{
return 'test process';
}
let obj = gen();
console.log(obj instanceof gen);
console.log(obj.process());
不能像普通函数的this一样在Generator函数中使用this
function* gen1()
{
this.name = 'Bill'; // 没有成功添加name属性
}
let obj1 = gen1();
console.log(obj1.name);
可以采用变通的方式在Generator函数中为对象添加属性
var genObj = {};
function* gen2()
{
yield this.name = 'Bill';
yield this.age = 30;
}
var g = gen2.bind(genObj)();
g.next();//调用三次才可
g.next();
g.next();
console.log(genObj.name);
console.log(genObj.age);
var genObj1 = {};
function* gen3()
{
this.name = 'Bill';
this.age = 30;
}
var g1 = gen3.bind(genObj1)();
g1.next();//调用一次即可
console.log(genObj1.name);
console.log(genObj1.age);
generator函数与状态机
普通函数
var state = 1;
var stateMachine = function()
{
if(state == 1)
{
console.log('状态1');
state++;
}
else if(state == 2)
{
console.log('状态2');
state++;
}
else
{
console.log('状态3');
state = 1;
}
}
stateMachine();
stateMachine();
stateMachine();
stateMachine();
stateMachine();
使用Generator函数切换状态
var genStateMachine = function*()
{
while(true)
{
console.log('状态1');
yield;
console.log('状态2');
yield;
console.log('状态3');
yield;
}
}
var gen = genStateMachine();
gen.next();
gen.next();
gen.next();
gen.next();
gen.next();
Promise
Promise是一种异步实现模式,允许Promise中的任务在另一个线程中执行
Promise是一个构造函数,构造函数的参数可以传递一个回调函数,回调函数就是在另一个线程中执行的任务
成功或失败两种结果success和fail,成功Promise.then
以前就有,es6进行了api标准化
function timeout(ms)
{
return new Promise((success,fail)=>{ //参数名随便起,回调函数
setTimeout(success,ms);
})
}
timeout(2000).then(()=>{
console.log('success');
})
function timeout1(ms)
{
return new Promise((success,fail)=>{
if(ms % 2 == 0)
{
setTimeout(success,ms);
}
else
{
setTimeout(fail, ms);
}
})
}
timeout1(2000).then(()=>{
console.log('success(2000)');
},()=>{
console.log('fail(2000)');
});
timeout1(1999).then(()=>{
console.log('success(1999)');
},()=>
{
console.log('fail(1999)');
});
异步下载HTML代码
var getHtml = function(url)
{
var promise = new Promise((success,fail)=>{
var client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = 'text';
client.setRequestHeader('Accept', "text/html");
client.send();
function handler()
{
if(this.readyState != 4)
{
return;
}
if(this.status == 200)
{
success(this.response);
}
else
{
fail(new Error(this.statusText));
}
}
})
return promise;
}
getHtml("basic.html").then((html)=>{
console.log(html);
},(error)=>
{
console.error(error);
})
then
then也可以有返回值,是一个新的Promise对象,第二个then的参数是第一个then的返回值
异步下载HTML代码
getHtml("basic.html").then((html)=>{
console.log(html);
return html.length;
}).then((length)=>{
console.log("html长度:" + length);
});
catch
异步下载HTML代码,统一捕获
getHtml("basic.html").then((html)=>{
console.log(html);
return getHtml("then.html");
}).then((html)=>
{
console.log(html);//第二次调用
}).catch((errorMsg)=>
{
console.log(errorMsg);
})
异步操作与async函数
通过Generator函数与Promise对象封装异步任务
Generator函数分段执行的,就是通过yield实现的可以在Generator函数中将大人物分解成多步,其中某些步骤 就可以交给Promise对象异步执行 这时Generator函数是暂停执行的,直到Promise对象执行完异步任务后,才会返回Generator函数 核心问题:Generator函数与Promise对象之间的数据交互 主要的方式:Generator函数通过next方法返回一个Promise对象,然后 Promise对象执行完异步任务后,将处理完的数据返回给Generator函数继续处理
demo:按txt方式下载一个json文档,然后通过Promise对象异步将该文档转换为json对象,再将json对象返回给Generator函数,最后在Generator函数中输出对象的所有属性值
var getJSON = function(url)
{
var promise = new Promise((success,fail)=>{
var client = new XMLHttpRequest();
client.open("GET",url);
client.onreadystatechange = handler;
client.responseType = 'text';
client.send();
function handler()
{
if(this.readyState != 4)
{
return;
}
if(this.status == 200)
{
success(this.response);
}
else
{
fail(new Error(this.statusText));
}
}
})
return promise;
}
function* gen()
{
var url = "data.json";
var result = yield getJSON(url);//
console.log("name:" + result.name);
console.log("age:" + result.age);
console.log("salary:" + result.salary);
}
var g = gen();
// Generator函数 -> Promise对象
var obj = g.next();
// 通过Promise对象将json文本转换为json对象
obj.value.then((json)=>{
var jsonObj = JSON.parse(json);
return jsonObj;
}).then((jsonObj)=>{
// 将解析完的JSON对象传回gen函数(Promise对象 -> Generator函数传递数据的过程)
g.next(jsonObj)
})
async
function timeout(ms)
{
return new Promise((success,fail)=>{
if(ms % 2 == 0)
{
setTimeout(success, ms);
}
else
{
// 必须调用fail,async函数和catch方法才会捕获异常
fail(new Error("毫秒必须是偶数"));
}
})
}
timeout(2000).then(()=>{
console.log('hello world 2000')
})
timeout(1999).catch((error)=>{
console.log(error);
})
使用async函数,要es7,es6不支持
async function asyncPrint(value, ms)
{
try
{
// await等待success方法被调用,调用后,立刻往下执行
await timeout(ms);
console.log(value);
}
catch(e)
{
console.log(e);
}
}
asyncPrint('hello world 3000', 3000);
asyncPrint('hello world 3000', 3001);
类
es5
function Product(name, price)
{
this.name = name;
this.price = price;
}
Product.prototype.toString = function()
{
return 'name:' + this.name + ', price:' + this.price;
}
var product = new Product('iPhone8 Plus', 7000);
product.printName = function()
{
console.log('商品名:' + this.name);
}
console.log(product.toString());
product.printName();
es6新方式
class NewProduct
{
constructor(name, price)
{
this.name = name;
this.price = price;
}
toString()
{
return 'name:' + this.name + ', price:' + this.price;
}
printName()
{
console.log('商品名:' + this.name);
}
}
var newProduct = new NewProduct('特斯拉电动车', 900000);
console.log(newProduct.toString());
newProduct.printName();
新的定义类的方式就是一个语法糖
console.log(typeof NewProduct); // function
枚举类的属性
console.log(Object.keys(NewProduct.prototype)); // [],在类中不在原型
console.log(Object.getOwnPropertyNames(NewProduct.prototype)); // ["constructor", "toString", "printName"];//方法在类原型属性中
console.log(Object.keys(newProduct)); // ["name", "price"],属性在实例对象本身
console.log(Object.getOwnPropertyNames(newProduct)); // ["name", "price"]
console.log(Object.keys(Product.prototype)); // ["toString"],es5传统方法
console.log(Object.keys(product)); // ["name", "price", "printName"],es5传统方法
构造方法(constructor)
类必须要有构造方法,如果未指定构造方法,系统会自动加一个没有参数的构造方法
因为使用new命令创建对象时需要调用类中的构造方法
constructor默认会返回当期类的实例对象,但也可以通过return返回其他的对象
class MyClass
{
constructor()
{
return new NewProduct('会员卡', 200);
}
}
var p = new MyClass();
console.log(p.toString());
p.printName();
console.log(typeof p);
console.log(p instanceof NewProduct);//p属于类NewProduct的实例
// name属性
class MyClass1{
}
var my = new MyClass1();
console.log(MyClass.name);
// console.log(my.name); // undefined
class表达式定义类
函数表达式定义函数
var myFun = function()
{
console.log("myFun");
}
myFun();
var myFun1 = function hello()//hello基本无用处
{
console.log(hello.name)
console.log(typeof hello);
console.log(typeof myFun1);
console.log(myFun1.name);
}
myFun1();
console.log(myFun1.name);
// hello(); error,只在myFun1函数内部可以使用
类表达式
//不加Me,该class的name为MyClass
var MyClass = class Me
{
getClassName()
{
console.log(Object.getOwnPropertyNames(MyClass));
console.log(Object.getOwnPropertyNames(Me));
return Me.name;
}
}
var c = new MyClass();
console.log(c.getClassName()); //Me
console.log(MyClass.name); //Me
var Person = class
{
constructor(name)
{
this.name = name;
}
printName()
{
console.log('姓名:' + this.name);
}
}
new Person('Bill').printName();
继承
class Person
{
constructor(name, age)
{
this.name = name;
this.age = age;
}
toString()
{
return 'name:' + this.name + ' age:' + this.age;
}
}
class Teacher extends Person
{
constructor(name, age, course)
{
super(name, age);
this.course = course;
}
toString()
{
return super.toString() + ' course:' + this.course;
}
}
var teacher = new Teacher('Bill', 30,'大数据');
console.log(teacher.toString());
- 在子类的构造方法中必须调用super,否则创建类实例时会抛出异常
- 因为子类中没有this,所以要通过执行super方法来获得this对象
class Teacher1 extends Person
{
constructor(name, age, course)
{
super(name, age);//必须
//this.course = course;
}
toString()
{
return super.toString() + ' course:' + this.course;
}
}
var teacher1 = new Teacher1('Bill', 30,'大数据');
- super的位置可以在构造函数随便放(Java必须在最前面),只有调用super后,才能使用this
class Teacher2 extends Person
{
constructor(name, age, course)
{
console.log('constructor');
super(name, age);
this.course = course;
}
toString()
{
return super.toString() + ' course:' + this.course;
}
}
var teacher2 = new Teacher2('Bill', 30,'大数据');
原生构造函数的继承
Boolean()、Number()、String()、Array()、Date()、Function()、RegExp()、Error()、Object()
ES5 不能继承原生构造函数
ES6可以继承原生构造函数
class NewArray extends Array{
constructor(...args)
{
super(...args);
}
}
var arr = new NewArray(1,2);
arr[2] = 'Bill';
arr[3] = 'Mike';
console.log(arr.length); // 4
arr.length = 0;
console.log(arr[0]); // undefined
过sum方法计算数组中所有数值类型元素的和
class SumArray extends Array
{
constructor(...args)
{
super(...args);
}
sum()
{
var n = 0;
for(let i = 0; i < this.length;i++)
{
if(typeof this[i] == 'number')
{
n += this[i];
}
}
return n;
}
}
var sumArray = new SumArray(1,2,'abc',true,3,'hello', 4);
console.log(sumArray.sum());
类的getter与setter方法
getter和setter方法就是可以监控对象属性读写的方法
class Person
{
constructor()
{
this._name = '';
}
get name()
{
console.log('get name()');
return '<' + this._name + '>';
}
set name(value)
{
console.log('set name(value)');
this._name = value;
}
}
var person = new Person();
person.name = 'Bill';
console.log(person.name);
Generator方法
利用Generator方法可以遍历对象中的数据
class MyClass
{
constructor(...args)
{
this.args = args;
}
// 给类添加一个默认的遍历器
*[Symbol.iterator]()
{
for(let arg of this.args)
{
yield arg;
}
}
// 普通方法
*gen()
{
for(let arg of this.args)
{
yield arg;
}
}
}
var my = new MyClass(1,2,3,4,5);
var obj = my.gen();
console.log(obj.next());//1
console.log(obj.next());//2
console.log(my[Symbol.iterator]().next());//1
// x == arg
for(let x of new MyClass('a','b','c'))
{
console.log(x);
}
类的静态方法和静态属性
成员方法:必须通过类的实例调用
静态方法:必须通过类本身调用
不能通过类实例调用,这一点和Java不同
成员方法会被继承,而静态方法不会被继承
class MyClass
{
// 会被继承
process()
{
console.log('process');
}
static delete()
{
console.log('delete');
}
}
new MyClass().process();//类实例
// new MyClass().delete(); 类实例,抛出异常
MyClass.delete();//类本身
console.log('-----------------');
静态方法可以从super对象调用
class SubClass extends MyClass
{
static method()
{
console.log('method');
super.delete();
}
}
SubClass.method();
静态属性,并没有直接支持方法,但可以处理一下
class NewClass
{
}
NewClass.name = 'Bill';
NewClass.name1 = 'Mike';
NewClass.age = 30;
console.log(NewClass.name); // NewClass,而不是Bill,name属性本身有,无法人工改变
console.log(NewClass.name1);
console.log(NewClass.age);
new.target属性
使用new创建的实例,new才有target属性,否则target为undefined
// 写法1
function Person1(name)
{
// 通过new命令创建对象
if(new.target != undefined)
{
this.name = name;
}
else
{
throw new Error('必须使用new实例化类');
}
}
// 写法2
function Person2(name)
{
if(new.target === Person2)
{
this.name = name;
}
else
{
throw new Error('必须使用new实例化类');
}
}
var person1 = new Person1('李宁');
// var person11 = Person1.call(person1, 'Bill');
new.target在类中返回当前的Class
class MyClass
{
constructor(name)
{
// console.log(new.target);
// console.log(new.target === MyClass);
this.name = name;
}
}
new MyClass('John');
子类继承父类,new.target返回的是子类
class SubClass extends MyClass
{
constructor(name)
{
super(name);
console.log(new.target);
console.log(new.target === MyClass); // false
console.log(new.target === SubClass); // true
}
}
new SubClass('李宁');
抽象类
可以利用new.target的特性模拟实现抽象类
- 抽象类不能直接实例化,必须通过子类继承才能实例化
- 抽象类的抽象方法不能直接调用,必须在子类中覆盖后(override)才能调用
class AbstractPerson
{
constructor()
{
if(new.target === AbstractPerson)
{
throw new Error('抽象类不能实例化');
}
}
// 抽象方法
print()
{
// 内部抛出异常
throw new Error('抽象方法不能直接调用.')
}
// 普通方法
method()
{
console.log('method:普通方法');
}
}
class Teacher extends AbstractPerson
{
constructor()
{
super();
}
// override父类的抽象方法
print()
{
console.log('Teacher.method');
}
}
class Engineer extends AbstractPerson
{
constructor()
{
super();
}
}
new Teacher().print();
// new Engineer().print(); // error,调用了抽象类的print方法
// new AbstractPerson();
new Teacher().method(); // 调用普通方法