❝掌握ES的新特性 ❞
let与块作用域
在ES中作用域一般分为三种:
- 全局作用域
- 函数作用域
- 块级作用域
首先来看一下ES6新增加的特性块级作用域,至于作用域的深入讲解后续会专门出一篇文章进行讲解
在ES6之前的版本没有块级作用域,如下代码:在if块内部定义var
变量 foo
在if
块外都可以访问到,因为var
定义的变量是全局变量
if (true) {
var foo = "zce";
}
console.log(foo);
let
定义变量,在if
块的外部是无法访问的
if (true) {
let foo = "zce";
}
console.log(foo);//foo is not defined
除了在if块
,还有for
循环块的作用域,如下代码:使用var
定义的为全局变量在for
嵌套for
语句var i = 3
所以外层循环在i=3
的时候停止了循环
for(var i =0;i<3;i ){
for(var i =0;i<3;i ){
console.log(i);//当内层循环完毕 外层i也=3 所以循环只执行了一次
}
}
将var
改成let
,就可以使循环正确完成即:外层循环3次,外层每循环一次内层循环三次。因为let
所在的作用域属于for
,而每个for
循环中定义的let
变量都是在不同的作用域中的.所以for嵌套循环即使循环变量名字相同也不会有影响。「一般推荐不要使用同名的计数器」
for(let i =0;i<3;i ){
for(let i =0;i<3;i ){
console.log(i);//当内层循环完毕 不会影响外层循环
}
}
var
声明的变量还会存在事件绑定的问题,如下代码:
不管调用eles[0]
还是eles[1]
还是eles[2]
打印的结果都是3
,这是因为var i
是全局作用域中,i
它并没有保存在事件函数中。
var eles = [{},{},{}];
for(var i = 0;i<eles.length;i ){
eles[i].onclick=function(){
console.log(i);//打印的是全局作用域的i
}
}
eles[2].onclick();//3
看如下的调试信息i
存在Global
作用域也就是全局作用域中
在调用onclick
时候,可以看到[[Scopes]]
就是作用域,而里面只有Global
的作用域
然后我们看Global
里面可以找到 i = 3
一般这种问题,可以通过闭包来解决,是i保存在闭包的函数中,这样打印结果是正常的。
代码语言:javascript复制var eles = [{},{},{}];
for(var i = 0;i<eles.length;i ){
eles[i].onclick=(function(i){
return function(){
console.log(i);
}
})(i)
}
eles[1].onclick();//1
下面我们再来看一下闭包是如何获取的,通过调试来查看内部的信息,闭包的情况下i
的值存在Closure
作用域中
其实上述的问题就是块级作用域的问题,完全可以使用ES6的新特性let声明的块级作用域解决
代码语言:javascript复制for(let i = 0;i<eles.length;i ){
eles[i].onclick=function(){
console.log(i);
}
}
eles[2].onclick();//2
如下可以看到在Block
作用域中找到i=2
使用let的好处,如下代码两个i
互不影响
for(let i =0;i<3;i ){
let i = 'foo';
console.log(i);//foo
}
上述代码为什么两个i
互不影响呢?其实我们可以将循环进行拆解,如下的伪代码,两个i
在不同的作用域中是互不影响的
let i = 0;
if(i<3){
let i = 'foo';
console.log(i);
}
i ;
if(i<3){
let i = 'foo';
console.log(i);
}
i ;
if(i<3){
let i = 'foo';
console.log(i);
}
i ;
let
声明的变量不会变量提升,而var
会对变量进行提升
//let 的声明不会变量提升 必须先声明变量在使用变量
console.log(foo);//undefined
var foo = 'zce';
console.log(foo);//Cannot access 'foo' before initialization
let foo = 'zce';
- 总结let与var区别
- let不存在变量提升,var存在变量提升
- 暂时性死区:块级作用域只要let 声明变量,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
- let不允许重复声明,var可以重复声明
const 常量
const 声明一个只读的常量,声明过后不允许修改
- const 一旦声明赋值后就不允许修改
const name ='jake';
//const 一旦声明赋值后就不允许修改
name = '1223';
- const 声明的变量必须有初始值否则会报错
//const 声明变量必须有初始值否则报错
const name;
name = 'zce';
- const 只是不允许修改内存地址 如对象的属性是可以修改的
//const 只是不允许修改内存地址 如下对象的属性修改是可以的
const obj = {};
obj.name = 'zce';
//如下就会报错 因为内存地址改变了
obj = {}
不用var,主用const配合let,const 不允许修改内存地址
数组的解构
代码语言:javascript复制const arr = [100,200,300];
//解构
const [foo,bar,baz] = arr;
console.log(foo,bar,baz);//100 200 300
- 提取指定成员的位置
//提取指定位置的成员
const [,,baz] = arr;
console.log(baz);//300
- 提取指定位置之后的所有成员 是一个数组
const [foo,...rest] = arr;
console.log(rest);//[200,300]
const [...rest] = arr;
console.log(rest);//[100,200,300]
- 解构成员的个数小于被解构的数组长度 就会从前到后的提取
//解构成员的个数小于被解构的数组长度 就会从前到后的提取
const [foo] = arr;
console.log(foo);//100
- 如果解构成员的个数大于数组的长度,多出来的成员是undefined
//如果解构成员的个数大于数组的长度,多出来的成员是undefined
const [foo,bar,ace,more] = arr;
console.log(more);//undefined
- 给成员设置默认值
const [foo,bar,ace=123,more='default value'] = arr;
console.log(more);//default value
- 通过数组解构的方式可以大大的简化代码
const path = '/foo/bar/baz';
//常用写法
const temp = path.split('/');
const rootdir = temp[1];
console.log(rootdir);//foo
//通过数组解构的方式可以大大的简化代码
const [,rootdir] = path.split('/');
console.log(rootdir);
对象的解构
对象的解构比较简单,如下代码还可以通过解构给成员属性重新命名
代码语言:javascript复制//对象解构
const obj = {name:'zce',age:18}
//注意变量冲突
const name ='tom';
//通过:起一个别名解决冲突
const {name:objName = 'jack',age}=obj;
console.log(name,age,objName);
const {log} = console;
log('foo');
console.log();
模板字符串
- 支持换行
//支持换行
const str = `hello es2015,
this is a string`;
- 支持嵌入变量以及表达式
const name= 'tom';
const msg = `hey,${name} --- ${1 2 }===${Math.random()}`;
console.log(msg);
- 支持代表标签的模板字符串
const str1 = console.log`hello world`;
const name = 'tom';
const gender = true;
//可以接收到name gender的返回值
function myTagFunc(strings,name,gender){
console.log(strings,name,gender);//[ 'hey,', ' is a ', '' ] tom true
const sex = gender ? '男' : '女';
return strings[0] name strings[1] sex strings[2];
}
const result = myTagFunc`hey,${name} is a ${gender}`;
console.log(result);//hey,tom is a 男
字符串的扩展
代码语言:javascript复制//es6 字符串的扩展 判断字符串当中是否包含指定的内容
const message = "Error: foo is not defined.";
console.log(message.startsWith('Error'));//字符串头部匹配
console.log(message.endsWith('.'));//字符串尾部匹配
console.log(message.includes('foo'));//字符串中是否包含某个 字符串参数
默认参数值及剩余参数
一般的参数写法如下
代码语言:javascript复制//一般的写法
function foo(enable){
//需要判断函数是否传递了
//不能这样写:enable = enable || true; 这样如果传入false还会使用的默认值true
enable = enable === undefined ? true : enable;
console.log('foo invoked - enable:');
console.log(enable);
}
foo(false);
ES6的新特性增加了函数参数的默认值 这样就不用判断参数是否为空了
代码语言:javascript复制//es6 新的写法 注意如果有多个参数 默认参数一定要放在参数列表最后面 可以定义多个默认参数
function foo(bar,enable = true,tag = "TAG"){
console.log(bar,enable,tag);
}
foo('hello');
函数接受剩余的参数,接受任意个数的参数 一般通过arguments接受
ES6 引入了...
的形式 已数组的形式去接收当前位置开始所有的形参 同样只能出现在形参列表的最后一位,而且只能出现一次
function foo(first,...args){
console.log(first,args);
}
foo(1,2,3,4);//[ 1, 2, 3, 4 ]
展开数组
代码语言:javascript复制// ... 操作符展开数组 而不用在根据下标进行展开数组
const arr= ['foo','bar','baz'];
console.log(arr[0],arr[1],arr[2]);
//以前的写法 打印数组
console.log.apply(console,arr);
//新特性直接通过...操作符可输出数组的值 大大减少了操作
console.log(...arr);
箭头函数
传统定义的函数
代码语言:javascript复制//传统定义函数
function inc(number){
return number 1;
}
- 箭头函数 没有括号 则作为结果返回
const inc = n => n 1;
console.log(inc(100));
- 多行代码 需要使用括号 并且需要些return进行返回
//多行代码 需要使用括号 并且需要些return进行返回
const inc = (n, m) => {
console.log('inc invoked');
return n m;
}
const arr = [1, 2, 3, 4, 5, 6];
//过滤偶数 箭头函数使代码更加剪短易读
console.log(arr.filter(i => i % 2));
- 箭头函数与this 箭头函数不会改变this的指向
如果使用箭头函数那么箭头函数的this是什么,那么箭头函数的里面的this就是什么 不会发生改变
代码语言:javascript复制const person = {
name: "tom",
// syaHi: function () {
// console.log(`he,my name is ${this.name}`);//tom
// }
syaHi: ()=> {
// 箭头函数与普通函数的区别 不会改变this的指向 也就是说在箭头函数外面的this是什么 函数里面this就是什么
console.log(`he,my name is ${this.name}`);//undefined
},
sayHiAsync:function(){
// const _this = this;//借助闭包的机制 保存当前作用域的this
// setTimeout(function() {
// 最终会放在全局对象上调用 无法拿到当前作用域的this对象 拿到的是全局对象
// console.log(_this.name);//undefined
// }, 1000);
//还可以使用箭头函数来解决 因为箭头函数的作用域就是当前作用域的对象 不会改变this
setTimeout(()=> {
//最终会放在全局对象上调用 无法拿到当前作用域的对象
console.log(this.name);//tom
}, 1000);
//一般需要用到闭包解决的this都可以使用箭头函数解决
}
}
person.sayHiAsync();
「一般需要用到闭包解决的this都可以使用箭头函数解决」
对象字面量新特性
代码语言:javascript复制/* 对象字面量新特性 */
const bar = '345';
const obj = {
foo:123,
bar,//变量名与添加的属性名一致
method(){//添加方法可以省略掉:和function
console.log(this);//内部的this指向当前的对象
}
}
console.log(obj);//{ foo: 123, bar: '345', method: [Function: method] }
obj.method();
//通过方括号动态使用属性名 可以使用任意的表达式 表达式的结果将会作为对象属性的属性名
obj[Math.random()] = 123;//计算属性名
对象扩展方法
Object.assign
将多个源对象中的属性复制到目标对象中
const source1 = {
a: 123,
b: 123
}
const target = {
a: 456,
c: 456
}
//第一个参数是目标对象 也就是所有源对象的属性都会复制到目标对象中
const result = Object.assign(target, source1);
console.log(result);//{ a: 123, c: 456, b: 123 } 后面对象的属性覆盖第一个对象的属性 可以看到a属性被源对象覆盖了
console.log(target);//{ a: 123, c: 456, b: 123 }
console.log(result === target);//返回的对象和目标对象完全相等的
//复制一个对象应用
//比如下面的例子 向函数传递一个对象 如果函数改变了对象的某个属性的属性值 那么外部的对象也会发生改变
function func(obj) {
obj.name = 'func obj';
console.log(obj);
}
const obj = {name:'global obj'};
func(obj);//{ name: 'func obj' }
console.log(obj);//{ name: 'func obj' }
//可以使用object.assign 复制一个对象就不会改变外部对象
function copyFunc(obj){
const o = Object.assign({},obj);
o.name = 'func obj';
console.log(o);//{ name: 'func obj' }
}
const obj2 = {name:'global obj'};
copyFunc(obj2);
console.log(obj2);//{ name: 'global obj' }
Object.is
扩展方法 判断两个值是否相等
//== 会在比较之前自动转换类型
//=== 严格判断类型
console.log(0==false,0===false);//true false
console.log( 0===-0);//true 三等运算符是无法比较 0 和 -0
console.log(NaN===NaN);//false 两个NaN不相等的
//Object.is() 新的一种全等的方法 0和-0可以区分开 两个NaN是相等的 一般不推荐这种方法还是使用===运算法进行比较
console.log(Object.is( 0,-0));//false
console.log(Object.is(NaN,NaN));//true
Proxy
监视某个对象属性的读写,以前ES5使用Object.defineProperty 在vue3.0以前的版本使用这样的方法进行属性响应从而实现了数据绑定Object.defineProperty 需要针对每一个属性进行设置,ES6 提供Proxy监视对象的读写过程 Proxy可以监视对象的所有属性 要强大很多,Proxy 能够监视到更多对象操作,Proxy 更好的支持数组对象的监视;Proxy 是以非侵入的方式监管了对象的读写.
代码语言:javascript复制//代理对象 监视对象读和写
const person = {
name:'zce',
age:20
}
//返回一个代理对象 第一个参数就是需要代理的目标对象 第二个参数是代理的处理对象
const personProxy = new Proxy(person,{
//监视属性的访问
get(target,property){
//target 目标对象 property属性名
// console.log(target,property);//{ name: 'zce', age: 20 } name
//返回值是外部访问属性的结果 先判断目标对象是否存在属性
return property in target ? target[property] : 'default';
},
//监视属性的写入
set(target,property,value){
//target 目标对象 property属性名 value属性值
console.log(target,property,value);//{ name: 'zce', age: 20 } gender true
//可以做一些数据校验
if(property === 'age'){
//判断属性值是否为数字
if (!Number.isInteger(value)) {
throw new TypeError(`${value} is not an int`);
}
}
target[property] = value;
}
});
console.log(personProxy.name);//zce
console.log(personProxy.xxx);//default
personProxy.gender= true;
personProxy.age = 123;
Proxy 对比 VS Object.defineProperty
更多的方法可以查询MDN-Proxy
- Proxy 能够监视到更多对象操作 , Object.defineProperty 只能监视对象属性的读写
const proxy = new Proxy(person,{
deleteProperty(target,property){
console.log(`delete ${property}`);
//监听属性的删除
delete target[property];
}
});
delete proxy.age;
console.log(person);//{ name: 'zce', gender: true }
- Proxy 更好的支持数组对象的监视
const list = [];
const listProxy = new Proxy(list,{
set(target,property,value){
console.log(target,property,value);//[] 0(数组的下标) 100(值)
target[property] = value;
return true;//写入成功
}
});
listProxy.push(100);
- Proxy 是以非侵入的方式监管了对象的读写
Object.defineProperty 会侵入对象,如下代码defineProperty会在对象p
中新增_name
和_age
属性,其实就是对p对象侵入了。
const p = {};
Object.defineProperty(p,'name',{
get(){
console.log("name 被访问");
return p._name;
},
set(value){
console.log("name 被设置");
p._name = value;
}
});
Object.defineProperty(p,'age',{
get(){
console.log("age 被访问");
return p._age;
},
set(value){
console.log("age 被设置");
p._age = value;
}
});
p.name = 'jake';
console.log(p.name);//jake
- Proxy无操作转发代理
let target = {};
let p = new Proxy(target, {});
p.a = 37; // 操作转发到目标
console.log(target.a); // 37. 操作已经被正确地转发
- 操作 DOM 节点
let view = new Proxy({
selected: null
}, {
set: function(obj, prop, newval) {
let oldval = obj[prop];
if (prop === 'selected') {
if (oldval) {
oldval.setAttribute('aria-selected', 'false');
}
if (newval) {
newval.setAttribute('aria-selected', 'true');
}
}
// 默认行为是存储被传入 setter 函数的属性值
obj[prop] = newval;
// 表示操作成功
return true;
}
});
let i1 = view.selected = document.getElementById('item-1');
console.log(i1.getAttribute('aria-selected')); // 'true'
let i2 = view.selected = document.getElementById('item-2');
console.log(i1.getAttribute('aria-selected')); // 'false'
console.log(i2.getAttribute('aria-selected')); // 'true'
Reflect
Reflect 属于一个静态类,Reflect 内部封装了一系列对对象的底层操作 14个静态方法 其中废弃掉了一个,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers的方法相同,Reflect 成员方法就是Proxy处理对象的默认实现
代码语言:javascript复制const obj = {
foo:'123',
bar:'456'
}
const proxy = new Proxy(obj,{
get(target,property){
console.log('watch logic~');
return Reflect.get(target,property);
}
});
console.log(proxy.foo);
「Reflect 的意义:提供统一一套用于操作对象的API」
代码语言:javascript复制const obj = {
name:'zce',
age:18
}
//传统的写法
// console.log('name' in obj);
// console.log(delete obj['age']);
// console.log(Object.keys(obj));
//统一的方法 推荐使用Reflect来操作对象
console.log(Reflect.has(obj,'name'));//是否存在属性
console.log(Reflect.deleteProperty(obj,'age'));
console.log(Reflect.ownKeys(obj));
Class
一般定义一个类型是通过函数 原型的方式去创建一个类
代码语言:javascript复制function Person(name){
this.name = name;
}
Person.prototype.say = function(){
console.log(`hi my name is ${this.name}`);
}
Person.t = "123";
Class写法
代码语言:javascript复制class Person{
constructor(name){
this.name = name;
}
//实例方法
say(){
console.log(`hi my name is ${this.name}`);
}
//静态方法
static create(name){
//注意静态方法中的this是指向调用的作用域 而不是实例的作用域
return new Person(name);
}
}
// const p = new Person('tom');
const p = Person.create('tom')
p.say();
//继承
class Student extends Person{
constructor(name,number) {
super(name);//super始终指向父类 可以调用父类的方法super.say()
this.number = number;
}
hello(){
super.say();//调用父类的方法
console.log(`my school number is ${this.number}`);
}
}
const s = new Student('jake',123);
s.hello();
Set
Set 数据结构 集合 Set内部成员不允许重复
代码语言:javascript复制const s = new Set();
s.add(1).add(2).add(3).add(4).add(2);
// s.forEach(i=>console.log(i));
// for (const iterator of s) {
// console.log(iterator);
// }
console.log(s.size);//4
console.log(s.has(100));//has 是否包含某个值 false
console.log(s.delete(3));//删除集合某一个值 true
s.clear();//清除集合
console.log(s);
//Set 主要用于数组去重
const arr = [1,2,1,3,4,1];
const result = Array.from(new Set(arr));//去重的数组 转换为一个新的数组
//也可以通过展开运算符
console.log([...new Set(arr)]);//[ 1, 2, 3, 4 ]
console.log(result);//[ 1, 2, 3, 4 ]
Map
Map 数据结构 键值对集合 可以用其他类型的数据最为键
代码语言:javascript复制//如Object的问题
const obj = {};
obj[true] = 'value';
obj[123] = 'value';
obj[{a:1}] = 'value'
console.log(Object.keys(obj));//[ '123', 'true', '[object Object]' ] 全部转换为了字符串 以toString的结果作为键
console.log(obj['[object Object]']);//value
console.log(obj[{}]);//value
console.log(obj['true']);//value
//Map 是键值对映射集合 键可以是任意类的数据
const m = new Map();
const tom = {name:'tom'};
m.set(tom,90);//键可以是任意类型的数据
console.log(m);//Map { { name: 'tom' } => 90 }
console.log(m.get(tom));//90
m.forEach((value,key)=>{
console.log(value,key);//90 { name: 'tom' }
})
console.log(m.has(tom));//true
console.log(m.delete(tom));//true
m.clear();
console.log(m);
Symbol
主要作用:为对象添加独一无二的属性名
ES6 之前对象的属性名都是字符串 而字符串都是有可能会重复的 重复的话就会冲突
如下例子 缓存的对象 约定的方式解决
代码语言:javascript复制const cache = {};
//a.js
cache['a_foo'] = Math.random();
//b.js
//不知道之前存在
cache['b_foo'] = '123';
console.log(cache);//{ foo: '123' }
Symbol 表示一个独一无二的值 ES6 支持Symbol作为属性名
代码语言:javascript复制const s = Symbol();
console.log(s);//Symbol()
console.log(typeof s);//symbol
console.log(Symbol() === Symbol());//false
console.log(Symbol('foo'));//Symbol(foo)
console.log(Symbol('bar'));//Symbol(bar)
console.log(Symbol('baz'));//Symbol(baz)
//对象支持symbol作为键
const ss=Symbol('foo');
const aa = Symbol('foo');
const obj = {
[ss]:123,
};
obj[aa]='test'
console.log(obj[ss],obj[aa]);//123 test
- 实现对象的私有成员
//a.js==================
//对外暴露对象
const name = Symbol();
const person = {
[name]:'ace',
say(){
console.log(this[name]);
}
}
exprot person;
//b.js ==============
import person from 'a.js';
console.log(person.say());//ace
//b.js 就无法获取name的属性因为没有对外暴露,而且是Symbol独一无二的值
- Symbol 每次调用都是一个全新的一个值,for() 相同的字符串返回相同的Symbol值 维护的是字符串和Symbol的对应关系
const s1 = Symbol.for('foo');
const s2 = Symbol.for('foo');
//for() 相同的字符串返回相同的Symbol值 维护的是字符串和Symbol的对应关系
console.log(s1===s2);//true
//如果for() 方法传入的不是字符串就会 默认转换为字符串 注意如果传入true和'true' 结果是一样的 注意!!
console.log(Symbol.for(true)===Symbol.for('true'));// true
console.log(Symbol.iterator);
console.log(Symbol.hasInstance);
- Symbol 定义的对象属性名,不能够被迭代找到
console.log(Symbol.iterator);//Symbol(Symbol.iterator)
console.log(Symbol.hasInstance);//Symbol(Symbol.hasInstance)
const obj2 = {
//为对象实现迭代器会经常用到
[Symbol.toStringTag]:"XObject", //[object XObject]
}
console.log(obj2.toString());//[object XObject] [object Object] 自定义对象的toString标签
//symbol 不能被迭代获取到 如下面的方式都无法获取symbol属性名
const obj3 = {
[Symbol()]:'Symbol value',
foo:'normal value'
}
//for..in..无法获取Symbol
for (const key in obj3) {
console.log(key);//foo
}
//keys无法获取到Symbol
console.log(Object.keys(obj3));//[ 'foo' ]
//即使将对象转换为json字符串 依然找不到Symbol
console.log(JSON.stringify(obj3));//{"foo":"normal value"}
//可以通过getOwnPropertySymbols获取symbol属性,注意他只能获取symbol类型的属性名 普通的无法获取
console.log(Object.getOwnPropertySymbols(obj3));//[ Symbol() ]
for..of 循环
for...of... 可以作为所有遍历的方式
代码语言:javascript复制//for
//for..in..
//forEach
//for..of.. 循环遍历所有数据结构的统一方式
const arr = [100,200,300,400];
//拿到的数组每一个元素
for (const iterator of arr) {
console.log(iterator);
if(iterator > 200){
break;
}
}
//forEach 不能跳出循环 终止循环
//arr.some()
//arr.every()
const s= new Set(['foo','bar']);
for (const iterator of s) {
console.log(iterator);
}
const m = new Map();
m.set('foo',123);
m.set('bar',456);
for (const [key,value] of m) {//解构数组
console.log(key,value);//[ 'foo', 123 ] [ 'bar', 456 ]
}
Object 对象不能被for_of迭代 需要自定义迭代器,也就是说只要实现了迭代器就可以被for..of循环,ES6这个新特性主要是方便统一所有的循环,只要循环的对象实现了迭代器就可循环迭代
代码语言:javascript复制//Object 对象不能被for_of迭代 需要自定义迭代器
const obj = {foo:123,bar:456}
for (const iterator of object) {//object is not defined
}
可迭代接口 用于for..of
循环 提供了Iterable
接口,只要数据结构实现了Iterable
接口就可以被for..of
遍历Symbol.iterator
方法实现
for..of
遍历的原理 内部实现Symbol.iterator
方法,如下代码比如Set对象
const set = new Set(['foo', 'bar', 'baa']);
const iterator = set[Symbol.iterator]();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
/*
{ value: 'foo', done: false }
{ value: 'bar', done: false }
{ value: 'baa', done: false }
{ value: undefined, done: true }
*/
下面我们将Object实现[Symbol.iterator]
属性
const obj = {
store:['foo','bar','baz'],
[Symbol.iterator]: function () {
let index = 0;
const self = this;
return {//iterable
//约定内部必须要有一个next方法 iterator
next: function () {
//约定的迭代结果接口:IterationResult value 当前迭代的数据 done 用来表示迭代有没有结束
const result = {
value: self.store[index],
done: index >= self.store.length
}
index ;
return result;
}
}
}
};
for (const iterator of obj) {
console.log(iterator);
}
- 迭代器模式 场景:协同开发一个任务清单应用
//a同学的代码 ====================================
const todos = {
life:['吃饭','睡觉','打豆豆'],
learn:['语文','数学','外语'],
//一般提供一个迭代的回调函数 但是这样并不好用 需要根据某个key来进行迭代 如果key值改变以后 其他调用的代码都需要改变
each:function(callback){
const all = [].concat(this.life,this.learn,this.work);
for (const iterator of all) {
callback(iterator);
}
},
//对外提供统一遍历的接口 直接使用for..of循环即可 不用关系内部的结构是什么样的 从语言层面实现的迭代器模式 可以适用任何的数据结构
[Symbol.iterator]:function(){
const all =[...this.life,...this.learn];
let index = 0;
return{
next:function(){
return{
value:all[index],
done:index >= all.length
}
}
}
}
}
// b同学的代码 =======================
// for (const iterator of todos.life) {
// console.log(iterator);
// }
// for (const iterator of todos.learn) {
// console.log(iterator);
// }
//不推荐使用回调函数的方式
todos.each(item=>{
console.log(item);
});
console.log('-------------------------------------');
//推荐统一的循环迭代 不会被对象的属性key改变所影响
for (const iterator of todos) {
console.log(iterator);
}
生成器 Generator
生成器 Generator 避免异步编程中回调嵌套过深提供更好异步编程解决方案
代码语言:javascript复制function * foo() {
console.log('zce');
return 100;
}
const result = foo();
console.log(result);//Object [Generator] {} 调用函数返回一个生成器对象
console.log(result.next());//zce { value: 100, done: true }
生成器对象也实现了iterable接口
代码语言:javascript复制function * f(){
console.log(1111);
yield 100
console.log(222);
yield 200
console.log(3333);
yield 300
}
const generator = f();
//一旦遇到yield就会停下来不往下执行 当再调用next()才会继续执行
console.log(generator.next());//1111 { value: 100, done: false }
console.log(generator.next());//2222 { value: 200, done: false }
console.log(generator.next());//3333 { value: 300, done: false }
console.log(generator.next());//{ value: undefined, done: true }
使用Generator 函数实现iterator方法
代码语言:javascript复制// 生成器的应用:发号器
function * createIdMaker(){
let id = 1;
while(true){
yield id ;//不必担心死循环
}
}
const idMaker=createIdMaker();
console.log(idMaker.next().value);
console.log(idMaker.next().value);
console.log(idMaker.next().value);
console.log(idMaker.next().value);
//使用Generator 函数实现iterator方法
const todos = {
life:['吃饭','睡觉','打豆豆'],
learn:['语文','数学','外语'],
//通过Generator函数进行改造
[Symbol.iterator]:function * (){
const all =[...this.life,...this.learn];
for (const iterator of all) {
yield iterator;
}
}
}
for (const iterator of todos) {
console.log(iterator);
}
Array 的扩展方法
代码语言:javascript复制const arr = ['foo',1,NaN,false];
//一般查找数组元素通过find()方法找到元素的下标 但是他不能查找NaN
//includes可以查找NaN的数值 直接返回true/false
console.log(arr.includes(NaN));//true
//指数运算符----------------------------------------
console.log(Math.pow(2,10));//2的10次方
console.log(2 ** 10);//2的10次方
ECMAScript 2017
代码语言:javascript复制const obj={
foo:'foo',
bar:'bar'
}
//Object.values 返回值数组-----------------
console.log(Object.values(obj));//[ 'foo', 'bar' ]
//Object.entries 返回键值对数组--------------
console.log(Object.entries(obj));// [ [ 'foo', 'foo' ], [ 'bar', 'bar' ] ]
for (const [key,value] of Object.entries(obj)) {
console.log(key,value);//foo foo
}
//将一个对象转换成Map对象
console.log(new Map(Object.entries(obj)));//Map { 'foo' => 'foo', 'bar' => 'bar' }
//Object.getOwnPropertyDescriptors-----------------
const p1 = {
firstName:'Lei',
lastName:'Wang',
get fullName(){
return this.firstName ' ' this.lastName;
},
test(){
console.log(this.firstName ' ' this.lastName);
}
}
console.log(p1.fullName);//Lei Wang
const p2 = Object.assign({},p1);//Object.assign 存在的问题 get属性方法无法复制
p2.firstName = 'zce';
console.log(p2.fullName);//Lei Wang
//配合getter setter 属性使用
const descriptors = Object.getOwnPropertyDescriptors(p1);
//Object.defineProperties() 方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象。
const p3 = Object.defineProperties({},descriptors);
p3.firstName = 'zce';
console.log(p3.fullName);//zce Wang
//String.prototype.padStart/String.prototype.padEnd------------------
const books ={
html:5,
css:16,
js:128
}
for (const [name,count] of Object.entries(books)) {
console.log(`${name.padEnd(16,'-')}|${count.toString().padStart(3,'0')}`);
}
//在函数参数中添加尾逗号
const arr=[
100,
200,
300,
400,
]