ES6 介绍
ES6的名称为ESMAScript2015(es2015),是2015年6月份发行的,它是最新ECMAScript的代表版本,一是因为相对与es5变化比较大,二是因为它的发行让标准命名规则发生了变化,ES6更准确的缩写名称应该叫ES2015,
ES6的出现最主要的解决了以下几个问题:
- 解决原有语法上的一些问题或不足(比如let,const)
- 对原有语法进行增强,更加易用(比如解构,展开,参数默认值,模板字符串)
- 全新的对象,全新的方法,全新的功能(promise)
- 全新的数据类型和数据结构(symbol,Set,Map)
let,const与var的区别
1.var 存在变量提升,let 不存在
//let
console.log(c) //Uncaught ReferenceError: Cannot access 'c' before initialization
let c = 100
//var
console.log(c) //underfined
var c =100
因为 let 不存在变量提升,所以上面 let 定义的变量会报错,报错的意思是我们想要打印 c 的值,必须先要初始化,然后再去使用它,必须遵循 先声明,后使用 的使用规则。
var 定义的变量结果为 underfined 代码相当于:
var c;//变量提升,但是值不能提升,所以是underfined
console.log(c) //underfined
c = 100
2.let在同一个作用域下不可以重复定义同一个变量值,而var可以
//let
let c = 100
let c = 200 //报错 Uncaught SyntaxError: Unexpected identifier
//var
var c = 100
var c = 200
console.log(c) //200 正常运行,会覆盖前一个值
3.有严格的作用域,var属于函数作用域,let属于块级作用域
ES5 中作用域有:全局作用域、函数作用域,没有块作用域的概念。
ES6 中新增了块级作用域,块作用域由**{ }**包括,if 语句和 for 语句里面的 { } 也属于块作用域。
//let
function fun(){
let n = 10
if(true){
let n = 20 //与上面的 n 不属于同一个作用域,所以不受影响
}
console.log(n) //10
}
fun()
//var
function fun(){
var n = 10
if(true){
var n = 20 //会覆盖上面的 n 的值
}
console.log(n)//20
}
fun()
4. const
4.1 const 声明的变量为只读的,一旦声明,常量的值就不能改变
const a =2;
a=3;//错误 Uncaught TypeError: Assignment to constant variable.
4.2 const 声明的变量一定要初始化,不能只声明不赋值,
const a; // 错误 Uncaught SyntaxError: Missing initializer in const declaration
//(const声明中缺少初始化程序)
4.3 const 声明的变量只在块级作用域内有效。
{
const a = 3
}
console.log(a) // Uncaught ReferenceError: a is not defined
但是用 const 声明的对象的属性是可以更改的,const 实质上保证的并不是变量的值不得改动,而是变量指向的 内存地址 的值不得改动,对于简单类型数据,值就保存在变量指向的内存地址中,相当于常量。而对于复合型的数据,变量指向的是内存地址保存的是一个指针。const 只能保证指针是不可以被更改,但指针指向的数据结构是可以被改变的。
//以下代码正常执行
const obj = {}
obj.name="xiaoke"
console.log(obj) // {name: "xiaoke"}
const arr = []
arr.push("xiaoke")
console.log(arr) // ["xiaoke"]
数组的解构
- 比如我们要取出arr中的值,我们可以使用下面的方式取出,a1,a2,a3代表了相应位置坐标的值
const arr = ['a','b','c']
const [a1,a2,a3] = arr
console.log(a1,a2,a3)//a b c
- 如果我们只想取索引为2的值,我们可以:
const arr = ['a', 'b', 'c']
const [, , a3] = arr //用逗号进行占位
console.log(a3)//c
- 如果我们想把b和c一起取出来我们可以:
const arr = ['a', 'b', 'c']
const [, ...arr2] = arr
console.log(arr2) //['b','c']
- 如果我们想取出一个数组中不确定存在的元素时,我们可以先给这个值赋值默认值,默认值的意思是数组中不存在此元素时,会输出默认值,存在这个值是输出存在的值:
存在
const arr = ['a', 'b', 'c', 'd']
const [a1, a2, a3, a4 = 'dd'] = arr
console.log(a1, a2, a3, a4)
console.log(arr)//a b c d
不存在
const arr = ['a', 'b', 'c']
const [a1, a2, a3, a4 = 'dd'] = arr
console.log(a1, a2, a3, a4)
console.log(arr)// a b c dd
对象的解构
- 对象的解构和数组的解构很相似,只是对象的解构不是按索引位置进行取值的,而是取出对象中的key值
const obj = { name: "xuke", age: 22 }
const { age } = obj
console.log(age) //22
- 对象和数组一样也可以设置默认值,如果我们想取出一个对象中不确定存在的元素时,我们可以先给这个值赋值默认值,默认值的意思是数组中不存在此元素时,会输出默认值,存在这个值是输出存在的值。
const obj = { name: "xuke", age: 22 }
const { hobby = "eat" } = obj
console.log(hobby) // eat
3.如果我们代码中已经定义了一个与obj中相同的属性名,我们可以使用下面的方式给属性名一个别名,然后再取出
const obj = { name: "xuke", age: 22 }
const name = 'keke'
const { name } = obj
console.log(newname) //会报错:SyntaxError: Identifier 'name' has already been declared
改正
const obj = { name: "xuke", age: 22 }
const name = 'keke'
const { name : newname } = obj
console.log(newname) //xuke
模板字符串
- 我们用反引号来包裹起来字符串: ``
const name = 'xuke'
const str = `my name is ${name}`
console.log(str) // my name is xuke
- 模板字符串支持换行
const name = 'xuke'
const str = `my name
is ${name}`
console.log(str)
//my name
//is xuke
- ${}中可以使用函数表达式,返回最终值
const str = `my age is ${true ? 22 : 10}`
console.log(str) //my age is 22
字符串的扩展方法
- startsWith() 判断是否以某个字符开头
- endsWith()判断是否以某个字符结尾
- includes()判断是否包含某个字符
const message = 'welcome to beijing.'
console.log(message.startsWith('welcome'))//true
console.log(message.startsWith('wel'))//true
console.log(message.endsWith('beijing'))//false
console.log(message.endsWith('.'))//true
console.log(message.includes('bei'))//true
参数默认值
- 当我们给函数传参的时候,我们可以使用以下方法设置默认值
function num(x = 20) {
return x += x
}
//x不传为undefined时 ,会使用我们的默认值 20
console.log(num()) //40
//传值的时候会使用我们传的值
console.log(num(10)) //20
注意:我们对参数设置默认值的时候,要把带有默认值的参数放到最后面
//错误的写法
function num(a = 20, x) {
return a + x
}
//正确的写法
function num(a , x = 20) {
return a + x
}
展开运算符(…)
- 合并数组
let a = [1,2,3];
let b = [4,5,6];
let c = [...a,...b]; // [1,2,3,4,5,6]
- 替代apply
function f(a,b,c){
console.log(a,b,c)
}
let args = [1,2,3];
// 以下三种方法结果相同
f.apply(null,args)
f(...args)
f(1,2,3)
//===========
function f2(...args){
console.log(args)
}
f2(1,2,3) // [1,2,3]
function f3(){
console.log(Array.from(arguments))
}
f3(1,2,3) // [1,2,3]
- 浅拷贝
//数组
var a = [1,2,4]
var b = [...a]
a.push(6)
console.log(b) // [1,2,4]
//对象
var a = {a:1}
var b = {...a}
a.a = 5
console.log(b.a) // 1
箭头函数
使用
const fun = function () {
return 'hello'
}
const fun2 = () => 'hello'
console.log(fun()) // hello
console.log(fun2())// hello
//===
const nums = [1, 2.3, 2, 46, 6, 4, 3, 2]
const result = nums.filter(num => num % 2 === 0)
console.log(result)//[ 2, 46, 6, 4, 2]
箭头函数不会改变this的指向
const Person = {
name:'xuke',
say1:function(){
console.log(this.name) //xuke 指向Person
},
say2:()=>{
console.log(this.name) //undefined 指向window
}
}
Person.say1()
Person.say2()
setTimeOut情况:
const Person = {
name: 'xuke',
say1: function () {
setTimeout(function () {//setTimeOut函数体里面的函数题被放到全局作用域去调用
console.log(this.name) //undefined
}, 1000)
setTimeout(() => {//setTimeOut函数体里面的箭头函数始终指向当前作用域中的this
console.log(this.name) //xuke
}, 1000)
}
}
Person.say1()
对象字面量
- 当我们对象中的
属性名
和值
相等的时候,我们可以把值
省略:
const name = 'xuke'
//普通用法
const obj1 = {
name:name,
age:12
}
//字面量用法
const obj2 = {
name,
age:12
}
console.log(obj1) //{ name: 'xuke', age: 12 }
console.log(obj2) //{ name: 'xuke', age: 12 }
- 当我们想用表达式生成对象的属性名的时猴,我们可以在
[]
中写入我们的表达式
//es2015之前
const name = 'xuke'
const obj = {
name,
age:12
}
const obj2 = {}
obj2[obj.name] = 'keke'
console.log(obj2) //{ xuke: 'keke' }
//es2015
const name = 'xuke'
const obj = {
name,
age:12
}
const obj2 = {
[obj.name]:'keke'
}
console.log(obj2) //{ xuke: 'keke' }
Object.assign()
Object.assign方法用于对象的合并,将源对象(source)
的所有可枚举属性,复制到目标对象(target
),如果目标对象与源对象有同名属性
,或多个源对象有同名属性,则后面的属性覆盖前面的属性
const source = {
a:123,
b:123
}
const target ={
a:456,
c:111
}
console.log(Object.assign(target,source)) //{a:123,c:111,b:123}
还可用于对象的浅拷贝,
const source = {
a:123,
b:123,
name:{
cc:'ccc'
}
}
const target = Object.assign({},source)
target.a = 333
console.log(target) //{ a: 333, b: 123 }
console.log(source) //{ a: 123, b: 123 }
Proxy
proxy真的用处很大,可是我项目中很少用到,总结一下。
proxy在目标对象的外层搭建了一层拦截
,外界对目标对象的某些操作,必须通过这层拦截.
1. 语法
var proxy = new Proxy(target, handler);
new Proxy()表示生成一个Proxy实例
,target参数表示所要拦截的目标对象
,handler参数也是一个对象,用来定制拦截行为
2. 基本用法
var target = {
name: 'poetries'
};
var logHandler = {
get: function(target, key) {
console.log(`${key} 被读取`);
return target[key];
},
set: function(target, key, value) {
console.log(`${key} 被设置为 ${value}`);
target[key] = value;
}
}
var targetWithLog = new Proxy(target, logHandler);
targetWithLog.name; // 控制台输出:name 被读取
targetWithLog.name = 'others'; // 控制台输出:name 被设置为 others
console.log(target.name); // 控制台输出: others
targetWithLog 读取属性
的值时,实际上执行的是 logHandler.get
:在控制台输出信息,并且读取被代理对象 target 的属性。
在 targetWithLog 设置属性
值时,实际上执行的是 logHandler.set
:在控制台输出信息,并且设置被代理对象 target 的属性的值
3. 保持只返回一个值
// 由于拦截函数总是返回35,所以访问任何属性都得到35
var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});
proxy.time // 35
proxy.name // 35
proxy.title // 35
4. Proxy 实例也可以作为其他对象的原型对象
var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});
let obj = Object.create(proxy);
obj.time // 35
proxy对象是obj对象的原型,obj对象本身并没有time属性,所以根据原型链,会在proxy对象上读取该属性,导致被拦截
5. Proxy的作用
- 拦截和监视外部对对象的访问
- 降低函数或类的复杂度
- 在复杂操作前对操作进行校验或对所需资源进行管理
6.Proxy使用场景
- 实现私有变量
var target = {
name: 'poetries',
_age: 22
}
var logHandler = {
get: function(target,key){
if(key.startsWith('_')){
console.log('私有变量age不能被访问')
return false
}
return target[key];
},
set: function(target, key, value) {
if(key.startsWith('_')){
console.log('私有变量age不能被修改')
return false
}
target[key] = value;
}
}
var targetWithLog = new Proxy(target, logHandler);
// 私有变量age不能被访问
targetWithLog.name;
// 私有变量age不能被修改
targetWithLog.name = 'others';
例1:在下面的代码中,我们声明了一个私有的 apiKey,便于 api 这个对象内部的方法调用,但不希望从外部也能够访问 api._apiKey
var api = {
_apiKey: '123abc456def',
/* mock methods that use this._apiKey */
getUsers: function(){},
getUser: function(userId){},
setUser: function(userId, config){}
};
// logs '123abc456def';
console.log("An apiKey we want to keep private", api._apiKey);
// get and mutate _apiKeys as desired
var apiKey = api._apiKey;
api._apiKey = '987654321';
很显然,约定俗成是没有束缚力的。使用 ES6 Proxy 我们就可以实现真实的私有变量了,下面针对不同的读取方式演示两个不同的私有化方法。第一种方法是使用 set / get 拦截读写请求并返回 undefined:
let api = {
_apiKey: '123abc456def',
getUsers: function(){ },
getUser: function(userId){ },
setUser: function(userId, config){ }
};
const RESTRICTED = ['_apiKey'];
api = new Proxy(api, {
get(target, key, proxy) {
if(RESTRICTED.indexOf(key) > -1) {
throw Error(`${key} is restricted. Please see api documentation for further info.`);
}
return Reflect.get(target, key, proxy);
},
set(target, key, value, proxy) {
if(RESTRICTED.indexOf(key) > -1) {
throw Error(`${key} is restricted. Please see api documentation for further info.`);
}
return Reflect.get(target, key, value, proxy);
}
});
// 以下操作都会抛出错误
console.log(api._apiKey);
api._apiKey = '987654321';
例2:让我们从一个简单的类型校验开始做起,这个示例演示了如何使用 Proxy 保障数据类型的准确性
let numericDataStore = {
count: 0,
amount: 1234,
total: 14
};
numericDataStore = new Proxy(numericDataStore, {
set(target, key, value, proxy) {
if (typeof value !== 'number') {
throw Error("Properties in numericDataStore can only be numbers");
}
return Reflect.set(target, key, value, proxy);
}
});
// 抛出错误,因为 "foo" 不是数值
numericDataStore.count = "foo";
// 赋值成功
numericDataStore.count = 333;
例3:对于那些调用频繁、运行缓慢或占用执行环境资源较多的属性或接口,开发者会希望记录它们的使用情况或性能表现,这个时候就可以使用 Proxy 充当中间件的角色,轻而易举实现日志功能
let api = {
_apiKey: '123abc456def',
getUsers: function() { /* ... */ },
getUser: function(userId) { /* ... */ },
setUser: function(userId, config) { /* ... */ }
};
function logMethodAsync(timestamp, method) {
setTimeout(function() {
console.log(`${timestamp} - Logging ${method} request asynchronously.`);
}, 0)
}
api = new Proxy(api, {
get: function(target, key, proxy) {
var value = target[key];
return function(...arguments) {
logMethodAsync(new Date(), key);
return Reflect.apply(value, target, arguments);
};
}
});
api.getUsers();
Reflect用法
概述
Reflect是为操作对象而提供的新API,那么我们为什么要去使用它呢?将Object对象的属于语言内部的方法放到Reflect对象上,即从Reflect对象上拿Object对象内部方法,比较方便,可读性更强。
1. Reflect.has(obj,name) 判断对象中的属性是否存在
const object = {
name:'xuke',
age:22
}
console.log(Reflect.has(object,'name'))//true
2. Reflect.set(target,propName,propValue)
const object = {
name:'xuke',
age:22
}
console.log(Reflect.set(object,'ww','dd'))//true object中不存在会直接添加
console.log(Reflect.set(object,'name','dd'))//true object中存在会覆盖
console.log(object) //{ name: 'dd', age: 22, ww: 'dd' }
3. Reflect.set(target,propName)
const object = {
name:'xuke',
age:22
}
console.log(Reflect.get(object,'name'))//xuke 属性名存在直接返回
console.log(Reflect.get(object,'ww'))//undefined 属性名不存在返回undefined
4. Reflect.deleteProperty(obj, name) 删除属性
const object = {
name:'xuke',
age:22
}
Reflect.deleteProperty(object,'name')
console.log(object) //{age:22}
5.Reflect.ownKeys (target) 用于返回对象的所有属性
const object = {
name:'xuke',
age:22
}
console.log(Reflect.ownKeys(object)) //[ 'name', 'age' ]
6. 上面列举了比较常用的方法,当proxy和Reflect配合起来一起使用:
const object = {
name:'xuke',
age:22
}
const proxy = new Proxy(object,{
get:(target,property)=>{
return Reflect.get(target,property) //获取被访问的属性
},
set:(target,property,value)=>{
Reflect.set(target,property,value) //设置被访问的属性与新值
}
})
console.log(proxy.name) //xuke
proxy.name = 'xiaohaha'
proxy.hobby = 'eat'
console.log(object) //{ name: 'xiaohaha', age: 22, hobby: 'eat' }