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
返回元素的高度,包含边框和内边距,但不含外边距