1.let和const
ES2015(ES6) 新增加了两个重要的 JavaScript 关键字: let 和 const。
let 声明的变量只在 let 命令所在的代码块内有效。
const 声明一个只读的常量,一旦声明,常量的值就不能改变。
let的特性
- let 声明的变量只在 let 命令所在的代码块内有效,声明的变量不在全局作用域中
- let声明的变量有块级作用域,在块级作用域中let的变量,出了{}就无法使用
- let声明的变量在同一个域中不能重复声明
- let可以防止循环变量变成全局变量
var得到全局变量,循环完之后还能使用。对后面的代码可能会产生影响
let有块级作用域,在{}外调用会报错,不会对后面的代码产生影响
- 使用let声明没有变量提升,var有变量提升
变量提升:在预编译时,js会优先处理变量和函数的声明,再执行变量和函数的赋值(仅限于var定义的变量)
// var有变量提升
console.log(d1)
// 预编译:GO{d1:undefined} log出undefined,不会报错
var d1 = 20
console.log(d1) // 20
// let没有变量提升,必须先定义再使用
console.log(d2)
// 直接报错Cannot access 'd2' before initialization
let d2 = 30
console.log(d2)
闭包出现时:
计时器for循环:计时器结束后统一调用,var = i是全局变量,执行定时器之前,i的值已经被循环修改为4,定时器启动时打印三个四,而不是123
let定义就不会出现这种情况
原理:每创建一个定时器,就会形成一个块级作用域,块级作用域内的i,互不影响,最后执行定时器的时候,打印的是各自块级作用域中的i。不会出现var变量i在全局作用域中,每次自加都重新赋值所有值的情况。
// 解决方式1:立即执行函数——每次拿到i的值,立即打印
for (var i = 1; i <= 3; i++) {
(function (index) {
setTimeout(function () {
console.log(index)
}, 1000)
})(i)
}
// 解决方式2:使用let定义
for (let j = 1; j <= 3; j++) {
setTimeout(function () {
console.log(j)
}, 1000) // 123
}
暂时性死区
ES6 明确规定,代码块内如果存在 let 或者 const,代码块会对这些命令声明的变量从块的开始就形成一个封闭作用域。代码块内,在声明变量 PI 之前使用它会报错
var PI = "a";
if(true){
console.log(PI); // Cannot access 'PI' before initialization
const PI = "3.1415926"; // 换成let也会报错
}
const 如何做到变量在声明初始化之后不允许改变的?
其实 const 其实保证的不是变量的值不变,而是保证变量指向的内存地址所保存的数据不允许改动。此时,你可能已经想到,简单类型和复合类型保存值的方式是不同的。
对于简单类型(数值 number、字符串 string 、布尔值 boolean),值就保存在变量指向的那个内存地址,因此 const 声明的简单类型变量等同于常量。
而复杂类型(对象 object,数组 array,函数 function),变量指向的内存地址其实是保存了一个指向实际数据的指针,所以 const 只能保证指针是固定的,至于指针指向的数据结构变不变就无法控制了,所以使用 const 声明复杂类型对象时要慎重。
const
与let相同
声明的变量有块级作用域,在块级作用域中声明的变量,出了{}就无法使用
声明的变量在同一个域中不能重复声明
声明没有变量提升
与let不同
const 声明一个只读的常量,一旦声明,常量的值就不能改变。会报错不能修改常量Assignment to constant variable.
const声明的变量必须赋初值,否则无法使用,会报错没有声明常量Missing initializer in const declaration
对于对象类型,const声明的变量,虽然值不能改变,但是里面的内容可以改变
const arr = [1,2,3]
arr = [4,5,6] // 相当于重新创建了一个数组,并把数组的地址赋给了arr
arr[1] = 10
console.log(arr) // 不能修改数组,但是可以修改数组中的内容
区别
| **** | var | let | const |
|---|---|---|---|
| 块级作用域 | 没有 | 有 | 有 |
| 变量提升 | 有 | 没有 | 没有 |
| 能否被修改 | 能 | 能 | 不能(对象类型可以修改内容 |
使用
建议使用优先级 const > let > var
- const在编程语言中,一般表示常量,这样阅读代码的人不会随意修改,防止误操作
- js引擎对const做了优化,执行效率比较高
具体使用细节:
- 声明基本数据类型,如果确定变量后面会被修改,使用let(for循环中的循环变量)。如果确定不修改,使用const。如果不确定会不会修改,仍然先使用const,如果后期要改再改
- 声明对象类型,比如数组,对象,正则,一般很少修改地址,更多的是修改里面的内容。因此首选还是const
2.类和继承
在ES6中,class (类)作为对象的模板被引入,可以通过 class 关键字定义类。
class 的本质是 function。
它可以看作一个语法糖,让对象原型的写法更加清晰、更像面向对象编程的语法。
语法
class 类名 {
// 定义构造函数
constructor(参数1,参数2){
this.属性名1 = 参数1
this.属性名2 = 参数2
}
// 定义方法
方法名(参数列表){
方法体
}
}
extends
通过 extends 实现类的继承。
classChildextendsFather{ ... }
// 定义一个父类
class Person {
constructor(name,age){
this.name = name
this.age = age
}
// 定义方法
eat(){
console.log('干饭')
}
}
// 定义一个学生类继承人类(class A extends B
class Student extends Person{
// 添加子类的特殊方法
study(){
console.log('学生要学习')
}
}
// 创建一个学生对象
const stu = new Student('张三',23)
console.log(stu)
stu.eat()
stu.study()
super
子类 constructor 方法中必须有 super ,且必须出现在 this 之前
调用父类构造函数,只能出现在子类的构造函数
调用父类方法, super 作为对象,在普通方法中,指向父类的原型对象,在静态方法中,指向父类
不可继承常规对象。
// 定义一个父类
class Person {
constructor(name,age){
this.name = name
this.age = age
}
// 定义方法
eat(){
console.log('干饭')
}
}
// 定义一个学生类继承人类
class Student extends Person{
// 定义构造函数
constructor(name,age,number){
// this.name = name
// this.age = age
super(name,age)
this.number = number
}
// 添加子类的特殊方法
study(){
console.log('学生要学习')
}
}
静态成员
静态属性
用static修饰的属性,只能通过类名调用
Person.country
静态方法
勇static修饰的方法,只能通过类名调用
Person.run()
3.解构赋值
解构赋值是对赋值运算符的扩展。
他是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值。
在代码书写上简洁且易读,语义更加清晰明了;也方便了复杂对象中数据字段获取。
语法
数组模式匹配
const [变量列表] = 数组
对象模型的解构
const {变量列表} = 数组
const {name:myName,age:myAge,list:[a,b,c]} = {
name:'张三',
age:24,
list:[99,80,100]
}
4.箭头函数
箭头函数提供了一种更加简洁的函数书写方式。基本语法是:
参数 => 函数体
完整格式
(参数列表) => {函数体}
// 优化,如果函数体只有一条语句,并且前面有return关键字,那么{}和return都可以省略
const max4 = (num1,num2) => num1 > num2 ? num1 : num2
const res = max4(99,88)
console.log(res)
注意:
箭头函数中this指向window对象而不是调用该方法的对象,window中没有name属性,获取不到name值
箭头函数绑定事件时,this也会丢失绑定到window上,获取不到调用者的属性
// 使用箭头函数书写字面量对象
var stu2 = {
name:'张4',
age:26,
study:()=>{
console.log(this) // this指向window对象
console.log(this.name + '在学习') // window中没有name属性,获取不到name值
}
}
// 使用箭头函数绑定事件
btn.addEventListener('click',()=>{
console.log(this) /* window */
console.log(this.innerHTML) // undefined
})
5.数组遍历
数组遍历的四种方法
const arr = [1,2,3]
// forEach方式
arr.forEach((item,index) => {
console.log(item,index)
})
// for ... of 遍历(只能获取数组元素,获取不到数组元素的索引)
// 语法:for(const 元素名称 of 数组名){语句体}
for(const item of arr){
console.log(item)
console.log(arr.indexOf(item))
// 只能通过这种方式获取数组元素的索引,但如果元素相同只会获取第一次的索引
}
// for...in遍历(获取的是索引
// 语法:for(const 索引 in 数组名){语句体}
for(const index in arr){
console.log(index)
console.log(arr[index]) //只能通过索引获取元素
}
// for...in主要用于遍历对象
const stu = {name:'张三',age:23}
for(const key in stu){
console.log(key) // name age 对象的属性名
console.log(stu.key) // undefined
console.log(stu[key]) // 只有痛过这种方式才能获取到属性值
}
// for..of还可以使用解构的方式遍历(遍历的同时还可以解构)
var array = [ {name:"河南省",cities:["郑州","洛阳","开封"]},
{name:"辽宁省",cities:["沈阳","大连","鞍山"]},
{name:"山东省",cities:["青岛","济南","烟台"]},
];
for(const {name,cities} of array){
console.log(name)
console.log(cities) //打印出了该省的城市
}
6.元素偏移量
概念
offset:偏移量,使用它的相关属性可以动态获取元素位置(偏移值),大小等。
作用
获取元素-距离-带定位父元素-的位置(获取的值是不带单位的)
获取自身元素的大小(宽高,不带单位)
属性
element.offsetParent
返回元素的偏移容器
返回带有定位的父级元素,如果父级元素都没有定位,则返回body
element.offsetTop
相对于垂直偏移位置的偏移容器
相当于上外边距
返回当前元素相对于定位父元素上方的偏移值
element.offsetLeft
相对于水平偏移位置的偏移容器
相当于左外边距
返回当前元素相对于定位父元素左方的偏移值
element.offsetWidth
返回元素的宽度,包含边框和内边距,但不含外边距
element.offsetHeight
返回元素的高度,包含边框和内边距,但不含外边距