- let and const
- Destructuring 解构赋值
- Spread and Reset SpreadJS扩展语句
- Arrow Functions 箭头函数
- Template Literals 模板字符串 ``
- Classes 类 继承
- Symbols es6第七种数据类型
- Iterators 迭代器
- Generators 生成器
- Promises
- Map 与Object对比
- WeakMaps
- Sets
- WeakSets
- Proxy 代理讲解+实例
es6英文网站:https://nodejs.org/en/docs/es6/
babel英文网站 (将es6转换为es5) https://babeljs.io/
一、 let 、 const
-
let 变量 只在本作用域中
function letTest() { let foo = true; if(true) { let foo = false; //只在本级block-->{}中有效 console.log(foo); //false } console.log(foo); //true } letTest(); // console.log(foo); //未定义 报错
-
if else语句中var变量在条件语句外仍然有效
function varTest() { var foo = true; if(true) { var foo = false; console.log(foo); //false 没毛病 } console.log(foo); //false 因为if里的var声明会被提到外面 } varTest(); console.log(foo); //外部未定义,报错
{里面是一块区域 block}
es6中避免使用var
const
-
不能修改声明的基本数据类型的值(number string boolean undefined
null)const str = 'ab'; str = 'bc'; console.log(str);// Uncaught TypeError: Assignment to constant variable. //其他四种同样报错
-
可以修改引用类型的引用
//引用类型保存在内存中,js不允许直接访问内存,so在操作的时候,其实是修改的是对象的引用 //例如:object: const foo = { one:'hello', two:{ three:'Goodbye' }; }; foo.name='world'; //增加name foo.two.three='not goodbye'; //修改foo.two.three里的内容 console.log(foo); //其他引用类型 Array Function 同objext可修改增加
二、SpreadJS 扩展语句
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Spread_syntax
- 解构赋值
var arr = [1,2,3,4,5]; var [a,b,c,d,e] = arr; console.log(a); //1
- ...+数组名
let arr = [1,2,3,4];
console.log(...arr); //1,2,3,4
```
var meats = ['bacon','ham'];
var food = ['apple',...meats,'kiwi','rice'];
console.log(food) //[ 'apple', 'backon', 'ham', 'kiwi', 'rice' ]
```
```
function addNum(a,b,c) {
console.log(a+b+c);
}
var num = [1,2,3];
addNum(num[0],num[1],num[2]);
addNum(...num); //6
```
三、箭头函数 Arrow Function
引入箭头函数有两个方面作用:更简短的函数 + 不绑定this
var materials = [ 'Hydrogen', 'Helium', 'Lithium', 'Beryllium' ]; materials.map(function(material) { return material.length; //字符串长度 }); // [8, 6, 7, 9] materials.map((material) => { return material.length; }); // [8, 6, 7, 9] materials.map(material => material.length); // [8, 6, 7, 9]
let circleArea3 = r=> 3.14 * r * r console.log(circleArea3(7));
-
讨厌的this 箭头函数出现之前
function Person() { // Person() 构造函数定义 `this`作为它自己的实例. this.age = 0; setInterval(function growUp() { // 在非严格模式, growUp()函数定义 `this`作为全局对象, // 与在 Person()构造函数中定义的 `this`并不相同. this.age++; //这个其实等同于window.age++,和Person里的this.age没有半毛关系 }, 1000); } var p = new Person();
-
在ECMAScript 3/5中,通过将this值分配给封闭的变量,解决this指向问题
function Person() { var that = this; that.age = 0; setInterval(function growUp() { // 回调引用的是`that`变量, 其值是预期的对象. that.age++;这个就是Person里的age,不再是指windo.age的 }, 1000); }
-
箭头函数里不会创建自己的this,它就是使用封闭执行上下文绑定的this值,
- so,下面代码传递给setInterval的函数(构造函数Person)内的this与封闭函数中的this值相同
function Person(){ this.age = 0; setInterval(() => { this.age++; // |this| 正确地指向person 对象 }, 1000); } var p = new Person();
四、模板字符串直接输出变量 ${变量名}
- 空格不折叠
let name ='apple';
console.log(`我最爱的水果是 ${name}因为酸甜`);
console.log("我最爱的水果是:"+ name +'因为' +
'好吃'); //输出不会换行,拼接字符串空格折叠
//我最爱的水果是苹果因为好吃
console.log(`我最爱的是 ${name}因
为好吃`); //输出会换行
//我最爱的是apple因
//为好吃
五、class类--constructor、 super 、 extends
class Person { //父类
constructor(name, age, weight) {
this.name = name;
this.age = age;
this.weight = weight;
}
displayName() {
console.log(this.name);
}
displayAge() {
console.log(this.age);
}
displayWeight() {
console.log(this.weight);
}
}
class Programmer extends Person{ //继承父类
constructor(name, age, weight,language) {
super(name,age,weight); //constructor里会先继承父类的super
this.language = language;
}
displayLanguage() {
console.log(this.language);
}
}
let sally = new Person('sally',21,49);
sally.displayName();
sally.displayAge();
sally.displayWeight();
console.log('---------------');
let bucky = new Programmer('bucky roberts',87,987,'javascript');
bucky.displayName();
bucky.displayAge();
bucky.displayLanguage(); //Programmer子类特有的,Person没有
六、Symbol
- 复习:JavaScript
- 5种基本数据类型--number string boolean undefined null +
- 1种复杂数据类型 -- Object
- es6添加了一种原始数据类型 : Symbol
- Symbol()不等于任何值,不能与其他任何类型的值进行运算,
```
let sym = Symbol('foo');
console.log(sym); //Symbol(foo)
console.log(typeof sym); //symbol
console.log(Symbol('foo') ===Symbol('foo')); //false
console.log(Number(3) ===Number(3)); //true
```
-
symbol不能被自动转换为字符串,尝试拼接symbol与字符串将得到TypeError错误
var s1 = Symbol("hello"); console.log(`输出的s1是:${s1}`); //报错 var s2 = s1.toString(); //或者 var s2 = String(s1); console.log(`输出的s1是:${s2}`); //输出的s1是 Symbol(hello)
具体应用英文: http://www.zsoltnagy.eu/es6-symbols-and-its-use-cases/
七、JavaScript迭代器 函数生成器
有几个特点:
1、函数名前面有一个‘*’
2、通过调用该函数生成一个控制器
3、调用next()方法开始执行函数
4、碰到yield,函数会暂停
5、再次调用next(),继续执行函数生成器是function 用来控制迭代器
yield 关键字:它可以暂停函数的执行,随后可以再进进入函数继续执行
太抽象,举例子:
-
for循环中就是简简单单的把输出的变量一次性输出
for (let i = 0; i < 5; i += 1) { console.log(i); } // 直接输出所有的 0 -> 1 -> 2 -> 3 -> 4
现在迭代生成器函数 .next()
-
function * generatorForLoop(num) { for (let i = 0; i < num; i += 1) { yield console.log(i); } } const genForLoop = generatorForLoop(5); genForLoop.next(); // first console.log - 0 genForLoop.next(); // 1 每调一次next,函数继续向下执行一次, genForLoop.next(); // 2 genForLoop.next(); // 3 genForLoop.next(); // 4
我们每next一下,就获取代码运一步的结果,不会一次性把所有的都输出
- 除了.next()可以迭代生成器,for循环也可以获取生成器变量的值
function * generator(arr) { for (const el in arr) yield el; } const gen = generator([0, 1, 2]); for (const g of gen) { console.log(g); // 0 -> 1 -> 2 } gen.next(); // {value: undefined, done: true} console.log(generator[0]) //undefined
注意:for in循环在这里不起作用,而且变量的获取也不能通过generator[0]这种方法获得
- 如何创建生成器
function * generator () {} function* generator () {} function *generator () {} let generator = function * () {} let generator = function* () {} let generator = function *() {} let generator = *() => {} // SyntaxError 报错 let generator = ()* => {} // SyntaxError 报错 let generator = (*) => {} // SyntaxError 报错
- ==上面可以看出,生成器的构建不能通过箭头函数的方式==
下面的生成器的例子用作方法,声明方式与function一致
class MyClass { *generator() {} * generator() {} } const obj = { *generator() {} * generator() {} }
- ==生成器函数创建必须初始化==
function * generator(arg = 'Nothing') { yield arg; } const gen0 = generator(); // 没毛病 const gen1 = generator('Hello'); // 也没毛病 const gen2 = new generator(); // 没有初始化,会报错 generator().next(); // 这可以有用,但每次都从头开始
八、Yield
-
函数中的return :意味着函数里return后面的所有都不会再执行到
function withReturn(a) { let b = 5; return a + b; //到这里就跳出这个函数了 b = 6; // 我们永远都不会到这一步,再改变b return a * b; // 当然更不会走到这一步了 } withReturn(6); // 11 withReturn(6); // 11
- Yield不同
function * withYield(a) { //生成器函数 let b = 5; yield a + b; b = 6; // it will be re-assigned after first execution yield a * b; } const calcSix = withYield(6); calcSix.next().value; // 11 calcSix.next().value; // 36
解释 yeild一次返回一个值,下一次你再调同一个函数的时候,会在上一次yield值后继续进行下去
-
在生成器中next()方法返回一个对象,这个对象包含两个属性:
- value 和 done,value 属性表示本次 yield 表达式的返回值,done 属性为布尔类型,表示生成器后续是否还有 yield 语句,即生成器函数是否已经执行完毕并返回
- 执行完毕即当在生成器函数中显式 return 时会导致生成器立即变为完成状态,即调用 next() 方法返回的对象的 done 为 true,
function* simpleGenerator(){
yield 'apples'; //到这一步,停一次
yield 'bacon'; //再停一次
console.log('ok,this is the line after the bacon');
yield 'corn'; //再停一次
}
let simple = simpleGenerator();
console.log(simple.next()); //{ value: 'apples', done: false }
console.log(simple.next().value); //bacon .value只要值,
console.log(simple.next().value); //ok,this is the line after the bacon +// corn
console.log(simple.next().value); //结束了,没有了,再想有next就是undefined
其实在生成器中,不仅yield可以使用,return也可以,只不过一旦遇到return后面的就都不会执行,直接跳出该函数了
-
function * generator() { yield 1; //停一次 return 2; //跳出生成器函数,后面的不会执行 yield 3; // 永远到不了这一步了 } const gen = generator(); gen.next(); // {value: 1, done: false} gen.next(); // {value: 2, done: true} gen.next(); // {value: undefined, done: true}
- yield委托
yield * 可以在另一个生成器中起作用,这样可以一直链接无数个生成器
function * anotherGenerator(i) { yield i + 1; yield i + 2; yield i + 3; } function * generator(i) { yield i; //第一步输出 10 yield* anotherGenerator(i); //到anotherGenerator里 分别停11 12 13 yield i+10; //回到i +20 停一次 } var gen = generator(10); console.log(gen.next().value); // 10 console.log(gen.next().value); // 11 console.log(gen.next().value); // 12 console.log(gen.next().value); // 13 console.log(gen.next().value); // 20 console.log(gen.next().value); // undefined console.log(gen.next().value); // undefined console.log(gen.next().value); // undefined console.log(gen.next().value); // undefined
- yield委托
function* getNextId(){
let id = 0;
while(id < 3) {
yield id++; //pauses
}
}
let createUser = getNextId();
console.log(createUser.next().value); //0
console.log(createUser.next().value); //1
console.log(createUser.next().value); //2
console.log('hello world');
console.log(createUser.next().value); //undefined
- 后续详细关于generator和yield,请关注 https://codeburst.io/what-are-javascript-generators-and-how-to-use-them-c6f2713fd12e
九、Map
Map 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。
键值对存储,意味着每个map实例里的key都不会重复
获取值通过map.get(键名) ,通过map.size获取Map里元素数量
var myMap = new Map(); console.log(typeof myMap); //object console.log(myMap.size); //没有set存进去键值对,size为0 myMap.set('foo','bar'); console.log(myMap.size); //1 myMap.set(12,'hh'); //键值对类型不受约束 console.log(myMap.get('foo')); //通过get获取键值对的值
详细了解Map和以及其与Object的区别,点击我这里
十、WeakMap
- 不像Map有size属性获取元素数量,
- 不能通过迭代forEach登遍历元素
- 最大区别:如果元素删除/释放,WeakMap就将该键值对从中删除,不会再获取到,因此这也是为什么WeakMap多用于声明私有变量
Map和WeakMap的区别:
var myMap = new Map();
var myWeakMap = new WeakMap();
var obj1 = {'foo':'bar'};
var obj2 = {'bar':'baz'};
myMap.set(obj1,'hello');
myWeakMap.set(obj2,'hello');
console.log(myMap.get(obj1)); //hello
console.log(myWeakMap.get(obj2)); //hello
//存储键值对和获取的方法一致
Map | WeakMap |
---|---|
key可以是任何数据类型 | key键值是弱引用,所以必须是Object类型(除了null);值任意 |
Map有.size获取元素数量 | 没有.size属性获取数量 |
可用.forEach()方法遍历 | 没有 .foreach()方法 |
键值对不会自动销毁 | 如果WeakMap里的key被删除了,对应的值也会被立马销毁垃圾回收 |
- WeakMap中,每个键对自己所引用对象的引用是 "弱引用",===》 如果没有其他引用和该键引用同一个对象,这个对象将会被当作垃圾回。
obj1 = null;
obj2 = null;
//map里元素不能主动删除释放
myMap.forEach(function (val,index) {
console.log(index,val); //{ foo: 'bar' } 'hello'
});
// myMap.forEach(function (val,index) { //报错,没有该方法
// console.log(index,val);
// });
console.log(myWeakMap.get(obj2)); //undefined 已经垃圾回收了,
myMap.delete(obj1);
myMap.forEach(function (val,index) {
console.log(index,val); //{ foo: 'bar' } 'hello'
});
console.log(myMap.size); //1
console.log(myWeakMap.size); //undefined ,没有该方法
十一、Set
Set对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用
.size获取元素数量;
.add(某一元素值)添加元素
.delete(某一值)来删除该元素
.has(某一值)判断set中是否有该元素
var mySet = new Set(); mySet.add(10); mySet.add('foo'); mySet.add({'foo':'bar'}); console.log(mySet.size); //3 mySet.delete('foo'); console.log(mySet.size); //2 console.log(mySet.has(10)); //true
for遍历Set里元素
for(let item of mySet) { console.log(item); //10 -> foo -> { foo: 'bar' } } for(let item of mySet.keys()) { console.log(item); //10 -> foo -> { foo: 'bar' } } for(let item of mySet.values()) { console.log(item); //10 -> foo -> { foo: 'bar' } } for(let [key,value] of mySet.entries()) { //key和value相等 console.log(key); //10 -> foo -> { foo: 'bar' } console.logvalue); //10 -> foo -> { foo: 'bar' } }
与数组Array相关;存储的样式有点类似array
console.log(mySet); //Set { 10, 'foo', { foo: 'bar' } } console.log([...mySet]); //[ 10, 'foo', { foo: 'bar' } ]
十二、WeakSet
Weakset以及与Set区别
仅仅是对象的集合,不像Set是任何类型的任意值
var mySet = new Set(); var myWeakSet = new WeakSet(); var obj1 ={}; var obj2 = {}; mySet.add(obj1); mySet.add(obj2); myWeakSet.add(obj1); myWeakSet.add(obj2); //只能添加Object类型元素 // myWeakSet.add(1); //报错
没有.size属性获取元素数量
console.log(mySet.size); //3 console.log(myWeakSet.size); //undefined
可以通过.has判断是否有某一元素
console.log(mySet.has(obj1)); //true console.log(myWeakSet.has(obj1)); //true
对象集合是弱引用,如果没有对应的引用,就会被垃圾回收,这也意味着就不会再存储列表里
console.log(mySet); //Set { {}, {} } console.log(myWeakSet); //WeakSet {}
不能被枚举
for(let item of mySet) { console.log(item) //{} -> {} 依然存在 } // for(let item of myWeakSet) { // console.log(item) //报错, :myWeakSet is not iterable // }
十三、Proxy
计算机术语中,什么是Proxy代理:
在计算术语中,代理位于你和你正在通信的事物之间。这个术语通常应用于代理服务器,它是一个介于web浏览器(Chrome、Firefox、Safari、Edge等)和web服务器(Apache、Nginx、IIS等)之间的设备。代理服务器可以修改请求和响应。例如,它可以通过缓存定期访问属性并将其提供给多个用户来提高效率。
在ES6中proxies就是位于你的代码和object之间。它允许你执行元编程操作,比如:拦截一个呼叫来检查或变更对象的属性。
- Proxy对象用于为基本操作定义定制行为(如属性查找、赋值、枚举、函数调用等)。
- 看似Proxy的相关专业词汇很多,其实你只需要熟记几个关键的:
handler -- 实现代理行为的对象
target -- 代理将虚拟化的原始对象。这可能是一个JavaScript的object对象,比如jQuery库或本地对象如数组,甚至是另一个代理。
traps -- 处理程序中定义的函数,当调用特定的属性或方法时,它提供对目标的访问。
- 通俗讲,Proxy代理是构造函数,返回Proxy对象,主要用于从外部控制对对象内部的访问
举个例子:我们创建target对象,有以下三个属性:
const target = { a:1, b:2, c:3 };
-
创建handler对象拦截所有的get操作,如果target里有该属性,就返回它,没有就输出数字:42
let handler = { get:function (target,name) { return ( name in target ? target[name] : 42 ) } }
-
现在,我们通过传递target和handler对象来创建一个新的代理。我们的代码可以与代理交互,而不是直接访问目标对象
const proxy = new Proxy(target,handler); console.log(proxy.a); //1 proxy代理了target对象 console.log(proxy.b); //2 console.log(proxy.c); //3 console.log(proxy.random); //42
现在我们进一步扩大代理处理机制,这样它只允许从a-z的单字符进行属性设置
const target = { a:1, b:2, c:3 }; const handler = { get:function (target,name) { return (name in target ?target[name] :42); }, set:function (target, prop,value) { if(prop.length == 1 && prop >='a' && prop <= 'z') { target[prop] = value; return true; } else { throw new ReferenceError(prop + '不能被设置'); //这里就抛出错误,不会再往下进行 return false; } } }; const proxy = new Proxy(target,handler); proxy.a = 10; proxy.b = 20; proxy.ABC = 30; //这个会报错,
Proxy的traps方法类型:
construct(target, argList) | 当使用new构建新对象时 |
---|---|
get(target, property) | 该方法必须返回属性value值 |
set(target, property, value) | 该方法用于拦截设置属性值的操作,成功时返回时true;严格模式下返回false的时候会抛出一个TypeError异常 |
deleteProperty(target,property) | 删除对象中的属性 ,返回的必须是true或false |
apply(target,thisArg,argList) | has(target,property) |
has(target,property) | 可以看作时针对in操作的钩子, 返回值必须是true或false |
ownKeys(target) | 访问Object.getOwnPropertyNames(), 必须返回一个可枚举的对象 |
getPrototypeOf(target) | 当地去代理对象的原型时调用,必须返回原型的对象或者null |
setPrototypeOf(target,prototype) | 设置原型对象,没有返回值 |
isExtensible(target) | 访问Object.isExtensible(),它决定了一个对象是否能有新添加的属性,不稀罕会true或false |
perventExtensions(target) | 防止新属性添加到对象中,必须返回true或false |
getOwnPropertyDescriptor(target,property) | 它返回undefined或属性的描述:value,writable,get,set,configurable,enumerable |
defineProperty(target,property,descriptor) | 定义或修改对象属性,必须返回true或false。当target属性成功定义返回true,否则返回false |
- Proxies代理允许你为人和对象创建通用的包装器,而不需要改变目标对象本身的代码
eg1: 创建剖析代理
用以计算属性被访问的次数。makeProfiler工厂函数,会返回Proxy对象,保留计算的状态:
function makeProfiler(target) { const count = {}, handler = { get:function (target,name) { //传的是你的对象 if(name in target) { count[name] = (count[name] || 0) + 1; //关键点 return target[name]; //get方法必须返回属性value值 } } }; return { proxy:new Proxy(target,handler), count:count } }
-
现在我们可以用上面封装好的proxy外壳应用于所有的对象或者其他代理,:
const myObject = { h:'hello', w:'world' };
-
创建myObject代理
const pObj = makeProfiler(myObject); console.log(pObj.proxy.h); //hello console.log(pObj.proxy.h); //hello 访问了两次,count = 2; console.log(pObj.proxy.w); //world console.log(pObj.count.h); //2 console.log(pObj.count.w); //1
eg2:双向绑定
数据绑定同步对象。当DOM发生变化时,JavaScript的MVC库通常使用它来更新内部对象,反之亦然。
-
- 创建一个input标签,id是inputName
创建myUser对象,
const myUser = { id:'inputname', name:'' }
当用户改变input输入值时,更新myUser里的name,通过onchange事件处理程序来实现
inputChange(myUser); function inputChange(myObject) { if (!myObject || !myObject.id) return; //如果传入的对象不存在或者没有id属性,return出去 const input = document.getElementById(myObject.id); input.addEventListener('onchange', function(e) { myObject.name = input.value; }); }
在js代码中修改myUse.name时,跟新input里的内容,这一步略微复杂,但是proxy代理提供了一个解决方案
const inputHandler = { set:function (target,prop,newValue) { if(prop =='name' && target.id ) { //prop指的是传入的对象里的属性: //更新对象的属性 target[prop] = newValue; document.getElementById(target.id).value = newValue; return true; //set方法返回值只有false或true; } else return false; } };
创建Proxy
const myUserProxy = new Proxy(myUser,inputHandler);
手动设置新name
myUserProxy.name = 'Lily'; console.log(myUserProxy.name); //Lily console.log(document.getElementById('inputName').value); //Lily
这不一定是最有效的数据绑定选项,但是
Proxy代理可以允许你在不更改其他代码的情况下改变好多现有对象的行为点击链接查看: 如何通过Proxy 结合原生js/或jQuery实现类似vue的数据双向绑定
目前Proxy的力量不一定是那么显而易见,但是它们提供了强大的元编程机会,JavaScript的创造者--Brendan Eich就认为,Proxy很赞!
-
目前,Proxy在node和各大浏览器都可以实现,除了IE11以外,但也要注意,不是所有的浏览器支持traps,查Proxy浏览器支持度:
坏消息: 目前不能通过转换工具如Babel将es6的Proxy编译成es5代码,谁让proxies这么强大呢!~