对象字面量的增强
ES6中对 对象字面量 进行了增强,称之为 Enhanced object literals(增强对象字面量)。
字面量的增强主要包括下面几部分:
- 属性的简写:Property Shorthand
- 方法的简写:Method Shorthand
- 计算属性名:Computed Property Names ```javascript var name = ‘zs’ var age = 18
// 对象字面量的方式创建对象,并且想要拥有上面的 name,age 属性 var obj = { name: name, // 在obj对象中声明name属性并赋值name变量的值zs age: age,
eat: function() { // 字面量定义方法,先定义属性名再写方法体 console.log(this.name + ‘ eating’); } }
obj[name + 123] = ‘hhh’ // 给 obj 对象添加一个动态计算的属性,只能动态添加
console.log(obj); // { name: ‘zs’, age: 18, eat: [Function: eat], zs123: ‘hhh’ }
//—- 对象字面量增强 —- var obj1 = { name, // 属性简写,直接写变量名,会自动创建同名属性并赋值 age,
eat() { // 方法简写,会自动创建方法名属性 console.log(this.name + ‘ eating’); },
[name + 123]: ‘hhh’ // 计算属性直接在定义对象时添加 }
console.log(obj1); // { name: ‘zs’, age: 18, eat: [Function: eat], zs123: ‘hhh’ }
<a name="xb2Yv"></a>
# 解构
ES6中新增了一个从数组或对象中按需方便获取数据的方法,称之为解构 Destructuring。
<a name="g7eEm"></a>
## 数组中解构
数组是靠索引取值的,所以解构也是按顺序的一个一个取。
```javascript
var names = ["abc", "cba", "nba"]
// 传统获取数组的值,babel 也会将解构转成这样
// var item1 = names[0]
// var item2 = names[1]
// var item3 = names[2]
// 对数组的解构: []
var [item1, item2, item3] = names
console.log(item1, item2, item3) // abc cba nba
// 解构后面的元素
var [, , itemz] = names
console.log(itemz) // nba
// 解构出一个元素,后面的元素放到一个新数组中
var [itemx, ...newNames] = names // 类似剩余参数操作符
console.log(itemx, newNames) // abc [ 'cba', 'nba' ]
// 解构的默认值为 undefined
var [itema, itemb, itemc, itemd] = names
console.log(itemd) // undefined 数组中并没有第四个元素,解构不出来,就按默认值来
// 可以给解构设置默认值
var [itema, itemb, itemc, itemd = "aaa"] = names
console.log(itemd) // aaa
对象中的结构
和数组不同的是对象中的属性是 key-value 的形式,所以解构取值和顺序没关系。
var obj = {
name: "why",
age: 18,
height: 1.88
}
// 对象的解构: {}
var { name, age, height } = obj
console.log(name, age, height)
// 和顺序无关,所以不需要逗号占位隔开
var { age } = obj
console.log(age)
// 将解构的值赋值给新变量,或者说是取了个别名
var { name: newName } = obj
console.log(newName)
// 取别名并赋结构默认值
var { address: newAddress = "广州市" } = obj
console.log(newAddress)duix
解构的场景
- 定义参数时的传参
当明知函数的参数是对象或者数组的时候,而我们只是想要里面的某个值,就可以在传参的时候解构取值。
- 函数返回值
函数的返回值是数组或者对象,则可以再接收参数的时候解构取自己需要的值
var arr = ['abc', 'cba', 'nba']
var obj = {
name: 'why',
age: 18,
height: 1.88
}
// 传统取值
function foo(arr, obj) {
// 希望获取数组的第二个值和对象的name属性
console.log(arr[1], obj.name)
}
foo(arr, obj) // cba why
// 参数解构取值
function fn([, result], {name}) {
console.log(result, name)
}
fn(arr, obj) // cba why
var obj = {
name: 'why',
age: 18,
height: 1.88
}
function foo(obj) {
obj.age += 1
return obj
}
// 传统想要处理过的对象属性
var newObj = foo(obj)
console.log(newObj.age); // 19
// 返回值解构
var {age: newAge1} = foo(obj)
console.log(newAge1); // 20
let / const
基本使用
从ES6开始新增了两个关键字可以声明变量:let、const。let、const在其他编程语言中都是有的,所以也并不是新鲜的关键字。
- let关键字:
从直观的角度来说,let和var是没有太大的区别的,都是用于声明一个变量
- const关键字:
const关键字是constant的单词的缩写,表示常量、衡量的意思;它表示保存的数据一旦被赋值,就不能被修改;但是如果赋值的是引用类型,那么可以通过引用找到对应的对象,修改对象的内容;因为 const 保存的其实对象堆内存的地址。
注意:另外let、const不允许重复声明变量;
⭐新版本下变量在内存中的存储
之前我们知道,在 ECMA 的早期版本,执行上下文中有变量对象 VO,但是现在的版本改了,已经不存在 VO 了。
现在执行上下文中的是变量环境 VE,变量和函数声明都会作为环境记录放入 VE 中。VE 也指向一个堆内存中的对象,这个对象依然会有 VE 中的属性和函数声明。
ECMA 是个规范,只是规定需要有 VE 指向这么一个对象。那么各家 JS 引擎实现可能会不一样。比如 v8 中的这个对象为**variables_**
,类型 VariableMap,通过 hashmap 来实现了它们的存储。
新版本下的 window 对象
在早期的版本中,全局定义的变量会添加到 window 对象上作属性,window 对象也能直接访问全局变量。
因为 GO 对象中的东西,window 对象也需要。所以早期 v8 引擎让 window 指向 GO,window 对象和 GO 对象其实就是同一个对象。
var foo = "foo"
var message = "Hello World"
// 全局定义的变量也添加到了 window 对象上
console.log(window.foo)
console.log(window.message)
// window 对象上的变量也相当于定义到了全局
window.message = "哈哈哈"
console.log(message)
而在最新的实现中已经变了。全局的参数和属性都会保存在variables_
对象中。v8 引擎已经不实现 window 对象,而是让包含 v8 引擎的浏览器来实现。window 对象已经独立出来作为一个独立对象,里面也包含了一些 v8 引擎没实现的东西。
但现在有个疑问了,为啥 var 现在定义的全局变量还是会被添加到 window 对象中作为属性?
因为浏览器为了兼容老代码,一直保持了 window 和 var 之间值的相等性。也就是会把 var 定义的变量从 variables_ 对象中同步到 window 对象,而 let、const 定义的变量则不会。
但这显然是过渡作法,随着 var 的弃用,window 对象肯定会剔除这些实现变成一个纯净的对象。
let/const 作用域提升
我们知道var声明的变量是会进行作用域提升的;但是如果我们使用let声明的变量,在声明之前访问会报错;
console.log(h) // ReferenceError: Cannot access 'h' before initialization
let h = 123
变量提升是怎么来的?在词法分析时,变量被添加到了执行上下文环境中,但是还没有执行,所以默认值是 undefined。
也就是还没执行变量的时候,变量就已经被创建出来了。而且访问该变量也是没有问题的,这就造成了一种变量还未声明但是可以访问的假象。
而 let、const 它们声明的变量也会在词法分析时被添加到上下文环境中,但和 var 不同的是,虽然变量被添加到了上下文环境中,但是变量在还没执行到初始化操作之前,是不允许访问的。
let、const 到底有没有作用域提升?
这个有争议,纯粹看你怎么定义作用域提升。但是有一点是肯定的,那就是 let、const 和 var 一样,声明的变量在赋值前就已经被创建出来了。
块级作用域
之前 JavaScript 只会形成两个作用域:全局作用域和函数作用域。
在 ES6 中新增了块级作用域的概念,并且对 let、const、function、class声明的类型有效。
但是尽管 ECMA 规范要求块级作用域对 function 有效,但是大部分浏览器为了兼容以前的代码, 让 function 是没有块级作用域限制的。
{
var n1 = 123
let n2 = 456
function demo() {
console.log("demo function")
}
class Person {}
}
console.log(n1); // 123,var 无视块级作用域,正常访问
console.log(n2); // n2 is not defined
demo() // demo function 浏览器开后门
new Person() // Person is not defined
if、switch、for 本来就是代码块。所以它们三个 es6 开始也会形成自己的块级作用域。
// if语句的代码就是块级作用域
if (true) {
var foo = 'foo'
let bar = 'bar'
}
console.log(foo) // foo
console.log(bar) // bar is not defined
// switch语句的代码也是块级作用域
switch ('red') {
case 'red':
var foo = 'foo'
let bar = 'bar'
}
console.log(foo) // foo
console.log(bar) // bar is not defined
// for语句的代码也是块级作用域
for (var i = 0; i < 10; i++) {
}
console.log(i) // 10
for (let j = 0; i < 10; i++) {
}
console.log(j) // j is not defined
块级作用域的应用场景
比如:页面有四个按钮想要点击按钮弹出提示点击了哪个,就给元素绑定点击事件。
<html lang="zh_CN">
<head></head>
<body>
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<button>按钮4</button>
</body>
</html>
const btns = document.getElementsByTagName('button')
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = function() {
console.log("第" + i + "个按钮被点击")
}
}
测试会发现,无论点击哪个按钮都会提示 点击了第四个按钮。因为点击事件会在全局代码执行完才会执行,这个时候 i 已经等于 4 了,回调函数去全局中获取 i 的值,所以获取的 i 都是 4。
没有 let 之前,我们只能借助函数作用域来包裹一下,强行套上一层作用域。并且函数需要使用立即执行函数。全局代码执行,立即执行函数就会把每次+1 的 i 当参数传进自己的函数代码块内,因为函数作用域的存在,回调函数就会先在立即执行函数中找到 i,而不会去全局找了。
const btns = document.getElementsByTagName('button')
for (var i = 0; i < btns.length; i++) {
(function(n) {
btns[i].onclick = function() {
console.log("第" + n + "个按钮被点击")
}
})(i) // i 当参数传给立即执行函数
}
显然这样是很麻烦的,而 let 识别代码块作用域就很好的解决这个问题。
for (let i = 0; i < btns.length; i++) {
btns[i].onclick = function() {
console.log("第" + i + "个按钮被点击")
}
}
上面的代码能不能用 const 定义 i 呢?
首先要说明一下,循环是怎么执行的。每循环一次,其实就会出现一个代码块,然后在每个代码块中 let 声明一个 i,这个 i 会接收上一个代码块 +1 的值来初始化。const 在每个代码块中声明没问题,但是 +1 的操作却做不到,所以不行。
但是在一些循环中不需要有值的变化,则 const 是可以的,比如for...of
遍历数组。
for (const item of arr) {
console.log(item)
}
暂时性锁区
在ES6中,我们还有一个社区提出的概念称之为暂时性死区:
它表达的意思是在一个代码块中,使用 let、const声明的变量,在声明之前,变量都是不可以访问的;
var foo = "foo"
// 整个 if 代码块就称为 foo 的死区
if (true) {
// 虽然 var 全局声明了 foo,但依然无法访问
console.log(foo) // ReferenceError: Cannot access 'foo' before initialization
let foo = "abc"
}
其实就是 let 声明的变量不能在赋值前使用的问题,报错都是一样的。
至于 var 全局声明了,还是不能访问,那是因为找属性是沿着作用域链查找。if 代码块已经形成作用域了,而作用域里已经有 foo 属性,就不会去全局找。
var、let、const 的选择
对于 var 的使用:
我们需要明白一个事实,var所表现出来的特殊性:比如作用域提升、window全局对象、没有块级作用域等都是一些历史遗留问题;其实是JavaScript在设计之初的一种语言缺陷。
对于let、const:
我们会优先推荐使用 const,这样可以保证数据的安全性不会被随意的篡改;
只有当我们明确知道一个变量后续会需要被重新赋值时,这个时候再使用 let;
这种在很多其他语言里面也都是一种约定俗成的规范,尽量我们也遵守这种规范;