let movie = 'Lord of the Rings'
var movie = 'Batman v Superman' // SyntaxError: Identifier 'movie' has already been declared
var num1 = 10
if(num1 === 10){
var num2 = 20
}
console.log(num2) // 20
--------------------------------------------
let num1 = 10
if(num1 === 10){
let num2 = 20
}
console.log(num2) // ReferenceError: num2 is not defined
// 上面代码中,使用var声明的变量不存在块级作用域,可以直接在全局中访问
// let声明的变量存在块级作用域,所谓的`块级作用域`,通俗地来讲可以理解为一对花括号{}就是一个块级作用域
// 在这个作用域内用let声明的变量存在块级作用域,只能在该作用域下才能访问
// 所以打印num2那一行代码在全局作用域下,根据作用域链的查找顺序,num2是无法被查找到的
// 所以报了这样一个错误`ReferenceError: num2 is not defined`
console.log(a) // undefined
var a = 10
console.log(b) // ReferenceError: Cannot access 'b' before initialization
let b = 10
以上代码中,第二行用var声明的变量有变量提升机制,第一行代码和第二行代码相当于
var a
console.log(a) // undefined 声明了a但却没有赋值,所以打印undefined
a = 10
第三行和第四行用let声明的变量则没有变量提升的机制,即我们不能在它初始化之前访问它,编译器会报一个引用类型的错误,在MDN文档中这一现象被称之为暂时性死区
。用const声明的变量与let类似。
ES2015中还引入了const关键字,它的行为和let关键字一样,唯一的区别在于,用const定义的变量是只读的,也就是常量
const PI = 3.1415926
PI = 3.0 // TypeError: Assignment to constant variable.
以上代码会报一个类型错误,即尝试给常量或只读变量赋值,因为const声明的是一个常量,是不允许修改的。但是,以下情况会有所不同:
const jsFrameWork = {
name: 'Vue'
}
jsFrameWork.name = 'React'
console.log(jsFrameWork.name) // 正常输出 React
如果您尝试运行这段代码,它会正常地工作并且输出 React,但是前面已经说到const声明的变量是一个常量,是不允许修改的,那么这儿为什么可以正常执行呢?那就不得不说到js变量是如何在内存中存储的知识,当我们声明基础数据类型时,js会在栈内存中开辟一块空间,用于存储变量名和变量值;当我们声明引用数据类型时,会先在栈内存中开辟一块内存用于存储变量名和堆内存中的地址值,随后在堆内存中开辟一块空间用于存储其具体的值,并且将栈内存中的地址指向堆内存中具体的值的地址。
总结:对于非对象类型的变量,比如数值型、布尔类型、字符串类型,我们不可以改变const声明的变量的值,但是当我们遇到对象时,只读的const允许我们修改或重新赋值对象的属性,但变量本身的引用(指向堆内存中的引用地址)不可以修改,也就是不能对这个变量进行重新赋值。
如果您像下面这样尝试重新给jsFrameWork变量重新赋值,编译器就会抛出异常
const jsFrameWork = {
name: 'Vue'
}
jsFrameWork = {
name: 'React'
} // TypeError: Assignment to constant variable.
if(true){
const num = 20
}
console.log(num) // ReferenceError: num is not defined 存在块级作用域,无法访问num变量
如果声明了const变量却没有给变量赋值,则会抱一个语法错误:const声明缺少初始化值
const num
console.log(num) // SyntaxError: Missing initializer in const declaration
当我们使用const 声明基础数据类型时,通常将变量名大写。
ES6的模板字面量又称模板字符串,模板字面量真的很棒,因为我们在创建字符串的时候不必再拼串,在需要换行的时候也不必使用转义字符
比如,以下代码中
const book = {
name: '霍乱时期的爱情'
}
console.log('你正在阅读' + book.name + ',\n这是新的一行\n 这也是新的一行')
以上代码就是ES5和之前如果需要换行打印或者拼接字符串数据的语法,但是现在我们可以直接使用ES6提供的模板字面量的方法
const book = {
name: '霍乱时期的爱情'
}
console.log(`你正在阅读${book.name},
这是新的一行
这也是新的一行
`)
模板字面量使用反引号 ``作为标志(使用一对反引号包裹),在反引号内部可以使用${}访问其能够访问到的作用域下的任何变量名,当然如上面的代码所示,模板字面量支持换行和空格显示。
ES6中的箭头函数极大地简化了普通函数的语法,方便了js开发者,但是很多地方仍然需要我们注意!
当我们有如下的例子:
var circleAreaES5 = function circleArea(r){
var PI = 3.14
var area = PI * r * r
return area
}
console.log(circleAreaES5(10)) // 314
---- 以下为箭头函数写法 ----
const circleArea = (r) => {
const PI = 3.14
const area = PI * r * r
return area
}
console.log(circleArea(10)) // 314
这个例子最大的区别在于我们可以省略掉function
关键字,只使用=>
。
当只有一个参数时以及函数只有一条语句时,我们的写法还可以变得更简单,我们可以省略掉参数的括号
以及函数体的花括号
和return关键字
,如下
const PI = 3.14
const circleArea = r => PI * r * r
console.log(circleArea(10)) // 314
当说到箭头函数的this指向时,让我们先来总结一波普通函数的this指向:
let obj = {
name: '艾向阳',
sayHello: function(){
console.log(`hello ${this.name}`)
}
}
obj.sayHello() // 打印 hello 艾向阳
普通函数的this指向没有那么复杂,通常来说,谁调用函数this
就指向谁。除此之外,定义在全局中的函数的this指向顶级对象,在非严格模式下 浏览器中的 函数this 指向window对象,nodejs中的函数this指向global。
另外,调用call、apply、bind会修改函数this的指向,会将函数的this指向call/apply/bind的第一个参数,示例代码如下:
let obj = {
name: '艾向阳',
sayHello: function(){
console.log(`hello ${this.name}`)
}
}
let obj2 = {
name: '爱新觉罗'
}
obj.sayHello.call(obj2) // 打印hello 爱新觉罗
apply和bind的情况与此类似,只是他们传递参数和函数调用时机不同,这里不对call、apply、bind的区别做过多讲解,以后有时间会出一期三者的异同讲解和手写实现。
但在箭头函数中,this的指向就会有所不同,先总结一点:箭头函数没有自己的this,箭头函数的this是捕获其所在上下文的this值作为自己的this值,还是上面的例子:
let obj = {
name: '艾向阳',
sayHello: () => {
console.log(`hello ${this.name}`)
}
}
obj.sayHello() // 打印 hello undefined
很奇怪,当采用箭头函数的写法时,this并不指向函数的直接调用者,那么我们打印this,会发现this其实是指向它当前的上下文的this。除此之外,我们还可以通过Babel
(ES6之后代码转ES5的一个方便的工具)来更加深刻的理解箭头函数中的this指向:
// ES6
const obj = {
getArrow(){
return () => {
console.log(this === obj) // true
}
}
}
// ES5,由Babel转译
var obj = {
getArrow: function getArrow(){
var _this = this
return function (){
console.log(_this === obj) // true
}
}
}
在ES6中,当对象的key value变量名相同时,我们可以对对象进行简写,这也会大大提高开发效率和降低代码维护难度,示例如下
let name = '艾向阳'
const obj = {
name: name
}
consle.log(obj) // {name: '艾向阳'}
// ES6对象简写
const obj2 = {
name
}
// 打印obj2也会得到相同的结果 {name: '艾向阳'}
除了对象名和对象值可以简写,ES6允许我们在对象的属性中也可以对函数进行简写
const obj = {
name: '艾向阳',
sayHello: function sayHello(){
consle.log(this.name)
}
}
obj.sayHello() // 艾向阳
// 简写形式
const obj2 = {
name: '艾向阳',
sayHello(){
consle.log(this.name)
}
}
obj2.sayHello() // 艾向阳
注意:只有当对象的key与函数名相同时才能采用简写形式!
ES6中引入了数组结构的概念,可以用来一次初始化多个变量
let [x,y] = ['a','b']
// 以上代码和下面的代码效果是相同的
let x = 'a'
let y = 'b'
对象也可以采用这种解构的写法,这是我工作中经常用到的写法
async getTableData(){
try {
const {data,rsCode} = await getTableDataApi()
} catch(e){
}
}
上述代码中getTableDataApi是一个封装好的ajax请求,服务器会固定地返回一个对象,其中包括data,rsCode等字段,此时我们就可以采用对象解构的写法。
在ES6中,函数的参数还可以定义默认值。
function sum(x,y,z = 3){
return x + y + z
}
sum(1,2) // 返回 6
以上代码中,因为我们有没有传入参数z的值,所以采用它的默认值3,因此 1+2+3 = 6,在ES6之前上面的函数只能这样:
function sum(x,y,z){
if(x === undefined) x = 1
if(y === undefined) y = 2
if(z === undefined) z = 3
return x + y + z
}
在使用函数参数默认值时,需要注意:需要将设置了默认值的参数放在参数的最后,否则会出现错误,如下
function sum(x,z = 3,y){
return x + y + z
}
sum(1,2) // NaN
在ES5中我们可以使用apply函数将数组转化为参数,因此ES6有了展开运算符(…),以上面的sum函数为例,可以执行如下代码来传入参数x,y,z
let params = [1,2,3]
function sum(x,y,z){
return x + y + z
}
sum(...params) // 6
// ES5写法
sum.apply(undefined,params) // 6
在函数中,展开运算符(…)也可以代替剩余参数
arguments,当作剩余参数使用。
关于剩余参数的定义:JavaScript函数中有一个内置的对象,叫做arguments对象,它时一个类数组,包含函数调用时传入的参数,即使不知道参数的名称,我们也可以动态地通过arguments获取并使用这些参数