1.变量声明let和const
预解析机制 : var
有变量提升的机制
function fn(){
var a=1
console.log(a)
}
fn() //1
//以上代码等同于:
function fn(){
var a
a=1
console.log(a)
}
fn() //1
把声明赋值和打印的语句顺序交换
function fn(){
console.log(a)
var a=1
}
fn() //undefined
//以上代码等同于:
function fn(){
var a
console.log(a)
a=1
}
fn() //undefined
ES5的解析:从上到下解析,遇到var
关键字,就把该声明提前到函数的最顶部,如果不在函数内就提升到全局作用域的最顶部,赋值留在原位,因为先声明了变量a
,然后打印,最后再赋值,所以结果就是undefined
。
但是使用let
和const
就可以解决变量提升的问题,let
代表变量,const
代表常量。
let
function fn(){
let a=1
console.log(a)
}
fn() //1
把声明赋值和打印的语句顺序交换
function fn(){
console.log(a)
let a=1
}
fn() //Error: a is not defined
let
不存在变量提升的机制,不会被声明到最顶部,所以未声明就打印,会报错:变量未定义。
const
const PI=3.14
PI=3.1415926 //Error: Assignment to constant variable.
const
的声明必须赋值,赋值之后不可修改。那要怎么修改const
的值呢?
实际上是const
的指向不可修改,但是可以通过更改地址中的内容来修改了const
的值。
const stu={ name: '小明' }
//正确,改变地址中的内容
stu.name='小红'
//报错,改变了const的指向
stu={ name: '小红' }
TDZ(暂时性死区)
if (true) {
// TDZ开始
tmp = 'abc';
console.log(tmp); // Error: tmp is not defined
let tmp; // TDZ结束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
let
或者 const
,会将声明放入TDZ(暂时性死区),只有执行到变量声明语句时,变量才会从TDZ中取出来使用。总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
块级作用域
ES6在ES5 全局作用域和函数作用域的基础上开拓了块级作用域,使得变量的作用域更加清晰。
内层变量覆盖外层变量
var tmp = new Date();
function f() {
console.log(tmp);
var tmp = 'hello world';
}
f(); // undefined
函数f
执行后,输出结果为undefined
,原因在于变量提升,导致内层的tmp
变量覆盖了外层的tmp
变量。
用来计数的循环变量泄露为全局变量
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5
变量i
只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。
ES6新增了块级作用域,即一个{}
就是一个块级作用域。
{ let a=1 }
console.log(a) //报错
不同块级作用域可以定义重名的变量
{
let a='hello world'
{ let a='hello world' }
}
立即执行函数就可以用块级作用域来替代
// IIFE写法
(function(){
var a=...
...
})()
// 块级作用域写法
{
var a=...
...
}
2.变量的解构赋值
数组的解构赋值
解构赋值语法是一个Javascript表达式,这使得可以将数据从数组或对象提取到不同的变量中。
以前为变量赋值,只能直接指定值。
let a = 1
let b = 2
let c = 3
ES6就可以写成这样
let [a, b, c] = [1, 2, 3]
本质是上述匹配属于一种模式匹配, 也就是只要等号两边的模式相同,左边的变量就会被赋予对应的值。
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3
let [ , , third] = ["foo", "bar", "baz"];
third // "baz"
let [head, ...rest] = [1, 2, 3, 4];
head // 1
rest // [2, 3, 4]
let [bar, foo] = [1];
bar //1
foo //undefined
默认值
解构赋值允许指定默认值
let [ a = 1 ]= [ ]
a //1
ES6 内部使用严格相等运算符(===
),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined
,默认值才会生效。
let [x = 1] = [undefined];
x // 1
let [x = 1] = [null];
x // null
null
不严格等于undefined
,默认值不生效。
如果默认值是一个表达式,表示则是惰性求值的,只有在用到的时候才会求值。
function fn(){
return -1
}
let [a = fn()]=[]
console.log(a) //-1
let [b = fn()]=[1]
console.log(b) //1
默认值可以引用其他变量,但该变量必须已经声明。
let [x = 1, y = x] = [] // x=1; y=1
let [x = 1, y = x] = [2] // x=2; y=2
let [x = 1, y = x] = [1, 2] // x=1; y=2
let [x = y, y = 1] = [] // Error: y is not defined
x
用y
做默认值,但是y
还没有声明,所以报错。
对象的解构赋值
解构不仅可以用于数组,还可以用于对象。
对象的解构赋值与数组不同的是:数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
var {foo,bar} = {foo: 'aaa', bar: 'bbb'}
console.log(foo) //aaa
console.log(bar) //bbb
//以上代码等同于:
let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" }
var {baz} = {foo: 'aaa', bar: 'bbb'}
console.log(baz) //undefined
以上未找到baz
,解构赋值失败。
let { foo: baz } = { foo: "aaa", bar: "bbb" }
baz // "aaa"
foo // Error: foo is not defined
foo
是匹配的模式,baz
才是变量。真正被赋值的是变量baz
,而不是模式foo
。
let obj = {
p: [
'hello',
{ y: 'world' }
]
}
let { p: [x, { y }] } = obj
console.log(x,y) //hello word
字符串的解构赋值
字符串的解构赋值,符串会转化成一个类数组的对象。
const [a,b,c,d,e] = 'hello'
console.log(a,b,c,d,e) //h e l l o
类数组对象有length
属性,所以len
是5。
let {length : len} = 'hello';
len //5
函数参数的解构赋值
函数的参数也可以使用解构赋值
function add([x, y]){
return x + y;
}
add([1, 2]); // 3
3.字符串的扩展
- 模板字符串
拼接字符串一直是令人头痛的地方,一旦变量多起来,字符串的拼接就会变得尤为繁琐,而且可读性极低,模板字符串简直就是开发者的福音。用
${}
嵌入字符串中拼接,有几个变量就用几个${}
// es5
var name='world'
console.log('hello'+name)
//es6
var name='world'
console.log(`hello${name}`)
- 在ES5中通过反斜杠(
\
)来做多行字符串的拼接,ES6反引号(``)可以直接搞定。
// es5
var msg='hello \
world!'
// es6
const template=`
hello world!
`
- 对运算的支持
let a=1
let b=2
let result=`${a+b}`
console.log(result) //3
- 字符串是否存在
// es5
let name = 'Github'
let msg = 'hello world,my name is Github'
console.log(msg.indexOf(name)) //返回索引值
// es6
let name = 'Github'
let msg = 'hello world,my name is Github'
console.log(msg.includes(name)) //返回布尔值,更直观
- 复制字符串
console.log('hello world|'.repeat(3)) // hello world|hello world|hello world|
4.箭头函数
ES6提供了箭头函数,给函数的创建提供了一种简便方法。
三大特点:
省略function
关键字
省略return
关键字
继承当前上下文的this
关键字
// es5
[1,2,3].forEach((function (item) {
return item + 1
}).bind(this))
// es6
[1,2,3].forEach(item=>item+=1)
当参数只有一个时,可以省略()
,当函数表达式只有一个时,可以省略{}
和return
// 一个参数,一个表达式
let person = name => 'hello ' + name
console.log(person('world')) //hello world
// 多个参数,多个表达式
let person = (name,age) => {
const msg = `hello ${name},age is ${age}`
return msg
}
console.log(person('world',18)) //hello world,age is18
在使用别人的轮子时,因为不熟练经常会报错,那么这些框架是怎么抛出错误的呢?
let add = (a,b=1) =>{
if(a<=0){
throw new Error('The variable must be greater than 0')
}
return a+b
}
console.log(add(0)); // Uncaught Error: The variable must be greater than 0
5.对象扩展功能
- 在对象的键值对重名时,可以这样处理
// es5
function person(name,age){
return {
name:name,
age:age
}
}
// es6
function person(name,age){
return {
name,
age
}
}
- ES6改进了为对象字面量方法赋值的语法,可以省略
:
和function
关键字
// es5
function person(name){
name,
sayHi:function(){
console.log('Hi')
}
}
// es6
function person(name){
name,
sayHi(){
console.log('Hi')
}
}
- 合并对象
let obj1={name:'Github'}
let obj2={sex:'男'}
let obj3={age:18}
let obj = Object.assign({},obj1,obj2,obj3)
console.log(obj) // {name:"Github",sex:"男",age:18}
6.扩展运算符(...)
- 组装数组或者对象
const arr1=[1,2,3]
const arr2=[...arr1,4,5,6]
console.log(arr2) //[1,2,3,4,5,6]
const obj1={
a:1,
b:2
}
const obj2={...obj1,c:3,d:4}
console.log(obj2); //{a:1,b:2,c:3,d:4}
7.promise
在promise之前,回调地狱,可读性差、耦合度高、扩展性低 。promise大大提高了代码的可读性,用同步编程的方式来编写异步代码,极大的降低了代码耦合性而提高了程序的可扩展性。
promise执行多步操作非常好用,现在模仿一个多步操作的过程。
把大象装进冰箱需要几步?答:三步,打开冰箱门,把大象塞进去,关上冰箱门。
let flag = true;
function step1(resolve, reject) {
console.log('1.开始-打开冰箱门');
if (flag) {
resolve('打开冰箱门--完成');
} else {
reject('打开冰箱门--出错');
}
}
function step2(resolve, reject) {
console.log('2.开始-把大象塞进去');
if (flag) {
resolve('把大象塞进去--完成');
} else {
reject('把大象塞进去--出错');
}
}
function step3(resolve, reject) {
console.log('3.开始-关上冰箱门');
if (flag) {
resolve('关上冰箱门--完成');
} else {
reject('关上冰箱门--出错');
}
}
new Promise(step1).then(function (val) {
console.log(val);
return new Promise(step2);
}).then(function (val) {
console.log(val);
return new Promise(step3);
}).then(function (val) {
console.log(val);
return val;
});
/*
1.开始-打开冰箱门
打开冰箱门--完成
2.开始-把大象塞进去
把大象塞进去--完成
3.开始-关上冰箱门
关上冰箱门--完成
*/
8.class类
class声明和使用
// 类的声明
class Coder{
getName(name){
console.log(name);
}
}
// 类的使用
let Github=new Coder
Github.getName('Github') // Github
声明一个JsCoder的新类并继承Coder类
class Coder{
getName(name){
console.log(name);
}
}
class JsCoder extends Coder{}
let js=new JsCoder
js.getName('js')
9.模块化
前后端分离,前端业务逻辑也在日益复杂,前端也在借鉴后端的思想,ES6为我们增加了模块化操作来方便我们引用第三方库,尤其是在开发vue项目时,需要依赖大量第三方包,需要我们使用到模块化的思想来开发项目。
- import : 引入模块
- export : 输出模块
// a.js 导出
export var name='hello world'
export function add(a,b){
return a+b;
}
// b.js 导入
import { name,add } from "./a.js" //也可以分开导入
console.log(name) //hello world
console.log(add(1,2)) //3
总结
ES6的内容远不止这些,不过也算是高频使用了,更多内容可以去看一下阮大神的ES6在线书籍。对于即将入门vue的小伙伴来说,ES6的学习对你来说是必不可少的,ES6的语法为我们前端开发者提供了更多的便利,也是学习vue框架的一块敲门砖,祝大家能尽快地掌握ES6语法,提高开发效率,本人也是刚学习ES6不久,如果文中内容有误,请不要吝啬您的意见或建议,欢迎留评。