ES6
1、ECMAScript和JavaScript的关系
- ECMAScript是由网景的布兰登·艾奇开发的一种脚本语言的标准化规范,是规范
- JavaScript 是一种基于 ECMAScript 规范的脚本语言,是实现
2、ES var和let
- let声明的成员只会在所声明的块中生效
- var会变量升级
- let 应用场景:循环绑定事件,事件处理函数中获取正确索引
- for 循环会产生两层作用域
- let 修复了变量声明提升现象
3、for setTimeout 经典题目
for (var i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, 1000 );
}
解析:浏览器会输出5个6。
这是因为setTimeout是异步执行,每一次for循环的时候,setTimeout都执行一次,但是里面的函数没有被执行,而是被放到了任务队列里,等待执行。只有主线上的任务执行完,才会执行任务队列里的任务。也就是说它会等到for循环全部运行完毕后,才会执行fun函数,但是当for循环结束后此时i的值已经变成了6,因此虽然定时器跑了5秒,控制台上的内容依然是6。
- 解决方案:闭包
for (var i=1; i<=5; i++) {
(function(j) {
setTimeout( function timer() {
console.log( j );
}, j*1000 );
})(i);
}
我们可以发现跟预期结果一致,依次输出1到5,因是因为实际参数跟定时器内部的i有强依赖。 通过闭包,将i的变量驻留在内存中,当输出j时,引用的是外部函数的变量值i,i的值是根据循环来的,执行setTimeout时已经确定了里面的的输出了。 2. 解决方案 拆分结构
function timer(i) {
setTimeout( console.log( i ), i*1000 );
}
for (var i=1; i<=5;i++) {
timer(i);
}
- 解决方案:let
for (let i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, 1000 );
}
这个例子与第一个相比,只是把var更改成了let,可是控制台的结果却是依次输出1到5。
因为for循环头部的let不仅将i绑定到for循环中,事实上它将其重新绑定到循环体的每一次迭代中,确保上一次迭代结束的值重新被赋值。setTimeout里面的function()属于一个新的域,通过var定义的变量是无法传入到这个函数执行域中的,通过使用let来声明块变量能作用于这个块,所以function就能使用i这个变量了;这个匿名函数的参数作用域和for参数的作用域不一样,是利用了这一点来完成的。这个匿名函数的作用域有点类似类的属性,是可以被内层方法使用的。
4、const
- 在let上增加了只读特性
- 恒量声明过后不允许重新赋值
- 恒量只是要求内层指向不允许被修改,对于数据成员的修改是没有问题的
const obj = {}
obj.name = 'zce' // 可以
obj = {} //不可以
5、(...)展开运算符\剩余运算符
合并数组
let a = [1,2,3];
let b = [4,5,6];
let c = [...a,...b]; // [1,2,3,4,5,6]
数据构造 两个对象连接返回新的对象
let x = {
name: 'autumn'
}
let y = {
age: 18
}
let z = {...x,...y}
console.log(z)
解构赋值
let a = [1,2,3,4,5,6]
let [c,...d] = a
console.log(c); // 1
console.log(d); // [2,3,4,5,6]
//展开运算符必须放在最后一位
浅拷贝
//数组
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
函数传参
// ES5 的写法
function f(x, y, z) {
// ...
}
var args = [0, 1, 2];
f.apply(null, args);
// ES6 的写法
function f(x, y, z) {
// ...
}
var args = [0, 1, 2];
f(...args);
剩余参数
// function foo () {
// console.log(arguments)
// }
function foo (first, ...args) {
console.log(args)
}
foo(1, 2, 3, 4)
6、模板字符串
// 可以通过 ${} 插入表达式,表达式的执行结果将会输出到对应位置
const msg = `hey, ${name} --- ${1 + 2} ---- ${Math.random()}`
console.log(msg)
- 带标签的模板字符串
- 模板字符串的标签就是一个特殊的函数,
- 使用这个标签就是调用这个函数
const name = 'tom'
const gender = false
function myTagFunc (strings, name, gender) {
// console.log(strings, name, gender)
return strings[0] + name + strings[1] + gender + strings[2]
}
const result = myTagFunc`hey, ${name} is a ${gender}.`
console.log(result)
// hey,tom is a false.
7.字符串的扩展方法
const message = 'Error: foo is not defined.'
console.log(
message.startsWith('Error') // true 是不是Error开头
message.endsWith('.') // true 是不是.结尾
message.includes('foo') // true 是不是包含foo
)
8.参数默认值
// 以前的写法
function foo (enable) {
// // 短路运算很多情况下是不适合判断默认参数的,例如 0 '' false null
// // enable = enable || true (错误的写法
enable = enable === undefined ? true : enable
console.log(enable)
}
// 现在的写法
function foo (enable = true) {
console.log(enable)
}
foo(false)
foo()
9、箭头函数
- 写法不一样
- 普通函数存在变量提升的现象
- 箭头函数不能作为构造函数使用
- 两者this的指向不同
- 箭头函数的arguments指向它的父级函数所在作用域的arguments
- 箭头函数没有new.target
function inc (number) {
return number + 1
}
// 最简方式
const inc = n => n + 1
// 完整参数列表,函数体多条语句,返回值仍需 return
const inc = (n, m) => {
console.log('inc invoked')
return n + 1
}
console.log(inc(100))
// 常用场景,回调函数
const arr = [1, 2, 3, 4, 5, 6, 7]
arr.filter(function (item) {
return item % 2
})
arr.filter(i => i % 2)
10、箭头函数与this
-
普通函数的this指向的是谁调用该函数就指向谁
-
如果没有调用者,那这个this就指向window
-
箭头函数不会改变 this 指向
-
箭头函数的this指向的是在你书写代码时候的上下文环境对象的this,如果没有上下文环境对象,那么就指向最外层对象window。
// xixi是全局函数,普通函数没有人调用this的时候,指向window
function xixi() {
console.log(this)
}
xixi(this)
>>Window
// 第一个this指向obj,而setTimeout里面的匿名函数没有直接调用者,this指向window
let obj = {
num: 10,
xx: function() {
console.log(this);
setTimeout(function() {
console.log(this);
})
}
}
obj.xixi()
>>Object
>>Window
// 如果我把setTimeout里面的普通函数,改成箭头函数,里面的this就指向它外层的this,就是obj
let obj = {
num: 10,
xx: function() {
console.log(this);
setTimeout(() =>{
console.log(this);
})
}
}
obj.xixi()
>>Object
>>Object
11、对象字面量的增强
- 属性名与变量名相同,可以省略 : bar
- 方法可以省略 : function,这种方法就是普通的函数,同样影响 this 指向。
- 通过 [] 让表达式的结果作为属性名
method1: function () {
console.log('method111')
}
// 方法可以省略 : function
method1 () {
console.log('method111')
// 这种方法就是普通的函数,同样影响 this 指向。
console.log(this)
},
obj[Math.random()] = 123 // 以前
obj{
[Math.random()] = 123 // 现在
}
12、 Object.assign
- Object.assign 方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象上。
- 如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
- 如果只有一个target(目标对象),Object.assign会直接返回该对象
- 如果该参数不是对象,则会先转成对象,然后返回
- Object.assign方法是浅拷贝
var a = 1;
Object.assign(a);
console.log(a); // 1
console.log(typeof a); // number
console.log(typeof Object.assign(2)) // object
- 对于null, undefined 来说 无法转换成Object,就会在控制台下报错
Object.assign(null); // 报错
Object.assign(undefined); // 报错
- 对象合并,如果源对象是null的话或者是undefined的话,那么对象合并的时候不会报错,直接会跳过该对象的合并,直接返回目标对象。
var obj = {a: 1};
console.log(Object.assign(obj, null) === obj); // true
console.log(obj); // {a: 1}
var obj = {a: 1};
console.log(Object.assign(obj, undefined) === obj); // true
console.log(obj); // {a: 1}
- 如果是数值,布尔型,和字符串合并对象的话,都不会报错,但是字符串会以数组的形式表现。
先看数值合并对象如下代码:
var obj = {a: 1};
console.log(Object.assign(obj, 12) === obj); // true
console.log(obj); // {a: 1}
布尔型合并对象如下代码:
var obj = {a: 1};
console.log(Object.assign(obj, true) === obj); // true
console.log(obj); // {a: 1}
字符串合并对象如下代码:
var obj = {a: 1};
console.log(Object.assign(obj, "bcd") === obj); // true
console.log(obj); // {0: 'b', 1: 'c', 2: 'd', a: 1}
如上代码,只有字符串和对象合并,这是因为只有字符串有包装对象,会产生可枚举类型属性。比如如下代码:
console.log(Object('bcd')); // {0: "b", 1: "c", 2: "d", length: 3, [[PrimitiveValue]]: "bcd"}
console.log(Object(1111)); // {[[PrimitiveValue]]: 1111}
console.log(Object(true)); // {[[PrimitiveValue]]: true}
const source1 = {
a: 123,
b: 123
}
const source2 = {
b: 789,
d: 789
}
const target = {
a: 456,
c: 456
}
const result = Object.assign(target, source1)
console.log(target) // {a: 123,c:456,b:123}
console.log(result == target) // true
13、Object.is
- JS中==、===和Object.is()的区别
- == 等同,比较运算符,两边值类型不同的时候,先进行类型转换,再比较;
- === 恒等,严格比较运算符,不做类型转换,类型不同就是不等;
- Object.is()是ES6新增的用来比较两个值是否严格相等的方法,与===的行为基本一致。
- 先说===,这个比较简单,只需要利用下面的规则来判断两个值是否恒等就行了。
- 如果类型不同,就不相等
- 如果两个都是数值,并且是同一个值,那么相等;
- 值得注意的是,如果两个值中至少一个是NaN,那么不相等(判断一个值是否是NaN,可以用isNaN()或Object.is()来判断)。
- 如果两个都是字符串,每个位置的字符都一样,那么相等;否则不相等。
- 如果两个值都是同样的Boolean值,那么相等。
- 如果两个值都引用同一个对象或函数,那么相等,即两个对象的物理地址也必须保持一致;否则不相等。
- 如果两个值都是null,或者都是undefined,那么相等。
- 再说Object.is(),其行为与===基本一致,不过有两处不同: +0不等于-0。 NaN等于自身。
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
14、Object.defineProperty 的缺点
我们总结一下Object.defineProperty在劫持对象和数组时的缺陷:
- 无法检测到对象属性的添加或删除
- 无法检测数组元素的变化,需要进行数组方法的重写
- 无法检测数组的长度的修改
Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
var user = {};
var initName = ''
Object.defineProperty(user, "name", {
get: function(){
console.log('get name')
return initName
},
set: function(val){
console.log('set name')
initName = val
}
});
// get name
console.log(user.name)
// set name
user.name = 'new'
Vue.set(object, key, value)
this.$set(object, key, value)
Proxy
- 相较于Object.defineProperty劫持某个属性,Proxy则更彻底,不在局限某个属性,而是直接对整个对象进行代理,我们看一下ES6文档对Proxy的描述:
- Proxy可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
不管是数组下标或者数组长度的变化,还是通过函数调用,Proxy都能很好的监听到变化;而且除了我们常用的get、set,Proxy更是支持13种拦截操作。
可以看到Proxy相较于Object.defineProperty在语法和功能上都有着明显的优势;而且Object.defineProperty存在的缺陷,Proxy也都很好地解决了。
Proxy的优势如下
- Proxy可以直接监听整个对象而非属性。
- Proxy可以直接监听数组的变化。
- Proxy有13中拦截方法,如ownKeys、deleteProperty、has 等是 Object.defineProperty 不具备的。
- Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改;
- Proxy做为新标准将受到浏览器产商重点持续的性能优化,也就是传说中的新标准的性能红利。
const person2 = {
name: 'zce',
age: 20
}
const personProxy = new Proxy(person2, {
get (target, property) {
console.log('get', property)
return target[property]
},
set (target, property, value) {
console.log('set', property, value)
target[property] = value
}
})
personProxy.name = 'jack'
console.log(personProxy.name)
Reflect 对象
const obj = {
name: 'zce',
age: 18
}
// console.log('name' in obj)
// console.log(delete obj['age'])
// console.log(Object.keys(obj))
console.log(Reflect.has(obj, 'name'))
console.log(Reflect.deleteProperty(obj, 'age'))
console.log(Reflect.ownKeys(obj))