var、let、const之间有什么区别?
var: 在ES5中,顶层对象的属性和全局变量是等价的,用var声明的变量既是全局变量,也是顶层变量
注意:顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象
var a = 10;
console.log(window.a) // 10
使用var声明的变量存在变量提升的情况
console.log(a) // undefined
var a = 20
在编译阶段,编译器会将其变成以下执行
var a
console.log(a)
a = 20
使用var,我们能够对一个变量进行多次声明,后面声明的变量会覆盖前面的变量声明
var a = 20
var a = 30
console.log(a) // 30
在函数中使用使用var声明变量时候,该变量是局部的
var a = 20
function change(){
var a = 30
}
change()
console.log(a) // 20
而如果在函数内不使用var,该变量是全局的
var a = 20
function change(){
a = 30
}
change()
console.log(a) // 30
let : let是ES6新增的命令,用来声明变量
用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效
{
let a = 20
}
console.log(a) // ReferenceError: a is not defined.
不存在变量提升
console.log(a) // 报错ReferenceError
let a = 2
这表示在声明它之前,变量a是不存在的,这时如果用到它,就会抛出一个错误
只要块级作用域内存在let命令,这个区域就不再受外部影响
var a = 123
if (true) {
a = 'abc' // ReferenceError
let a;
}
使用let声明变量前,该变量都不可用,也就是大家常说的“暂时性死区”
最后,let不允许在相同作用域中重复声明
let a = 20
let a = 30
// Uncaught SyntaxError: Identifier 'a' has already been declared
注意的是相同作用域,下面这种情况是不会报错的
let a = 20
{
let a = 30
}
因此,我们不能在函数内部重新声明参数
function func(arg) {
let arg;
}
func()
// Uncaught SyntaxError: Identifier 'arg' has already been declared
const: const声明一个只读的常量,一旦声明,常量的值就不能改变
const a = 1
a = 3
// TypeError: Assignment to constant variable.
这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值
const a;
// SyntaxError: Missing initializer in const declaration
如果之前用var或let声明过变量,再用const声明同样会报错
var a = 20
let b = 20
const a = 30
const b = 30
// 都会报错
const实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动
对于简单类型的数据,值就保存在变量指向的那个内存地址,因此等同于常量
对于复杂类型的数据,变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的,并不能确保改变量的结构不变
const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
使用区别:
var、let、const三者区别可以围绕下面五点展开:
- 变量提升
- 暂时性死区
- 块级作用域
- 重复声明
- 修改声明的变量
- 使用
变量提升
var 声明的变量存在变量提升,即变量可以在声明之前调用,值为undefined
// 2023.4.25 更新
let和const不存在变量提升,即它们所声明的变量一定要在声明后使用,否则报错
let / const 不存在变量提升是不完全正确的,只能说由于暂时性死区的存在使得我们无法直观感受到变量提升的效果。
let 和 const 定义的变量都会被提升,但是不会被初始化,不能被引用,不会像var定义的变量那样,初始值为undefined。
当进入let变量的作用域时,会立即给它创建存储空间,但是不会对它进行初始化。
变量的赋值可以分为三个阶段:
- 创建变量,在内存中开辟空间
- 初始化变量,将变量初始化为undefined
- 真正赋值
关于let、var和function:
- let 的「创建」过程被提升了,但是初始化没有提升。
- var 的「创建」和「初始化」都被提升了。
- function 的「创建」「初始化」和「赋值」都被提升了。
// var
console.log(a) // undefined
var a = 10
// let
console.log(b) // Cannot access 'b' before initialization
let b = 10
// const
console.log(c) // Cannot access 'c' before initialization
const c = 10
暂时性死区
var不存在暂时性死区
let和const存在暂时性死区,只有等到声明变量的那一行代码出现,才可以获取和使用该变量
// var
console.log(a) // undefined
var a = 10
// let
console.log(b) // Cannot access 'b' before initialization
let b = 10
// const
console.log(c) // Cannot access 'c' before initialization
const c = 10
块级作用域
var不存在块级作用域
let和const存在块级作用域
// var
{
var a = 20
}
console.log(a) // 20
// let
{
let b = 20
}
console.log(b) // Uncaught ReferenceError: b is not defined
// const
{
const c = 20
}
console.log(c) // Uncaught ReferenceError: c is not defined
重复声明
var允许重复声明变量
let和const在同一作用域不允许重复声明变量
// var
var a = 10
var a = 20 // 20
// let
let b = 10
let b = 20 // Identifier 'b' has already been declared
// const
const c = 10
const c = 20 // Identifier 'c' has already been declared
修改声明的变量
// var
var a = 10
a = 20
console.log(a) // 20
//let
let b = 10
b = 20
console.log(b) // 20
// const
const c = 10
c = 20
console.log(c) // Uncaught TypeError: Assignment to constant variable
使用
能用const的情况尽量使用const,其他情况下大多数使用let,避免使用var
ES6新特性
关于ES6和JavaScript的关系
1、ES6是对于ES2015+的俗称,也可以说是通常叫法,那么,ES6是什么呢?
ES 全称是ECMAScript,它是JavaScript基础构建的一种语言,JavaScript正是建立在ECMAScript语言的基础规范中建立使用的,那么,ECMAScript的使用,对于JavaScript至关重要!
在我的理解中,ECMAScript是一种语言层面的东西,它只是定义了JavaScript以及在它基础之上建立的其他语言的语法规范,而JavaScript的语言,更关于一种平台性质在其中。
JavaScript包括 ECMAScript、DOM、BOM三个组成部分,DOM和BOM是web API提供的接口或者是JavaScript和浏览器之间进行交互的部分,实质就是操纵文档元素,进行展示布局,而ECMAScript在JavaScript中其中语法的作用,它不会去跟文档有直接的关系,但是他的数据处理完成后会通过web API展示在文档中。
ES6新特性的分类
新特性主要归为四大类:
- 解决原有语法上的一些不足
比如let 和 const 的块级作用域 - 对原有语法进行增强
比如解构、展开、参数默认值、模板字符串 - 全新的对象、全新的方法、全新的功能
比如promise、proxy、object的assign、is - 全新的数据类型和数据结构
比如symbol、set、map
1. let、const 块级作用域以及和 var 的区别 可参考上边的描述
2.解构-快速提取数组/对象中的元素
- 数组解构
- 单独解构-根据数组索引,将数组解构成单独的元素
const arr = [1, 2, 3]
const [a, b, c] = arr
console.log(a, b, c) //1,2,3
const [, , d] = arr
console.log(d) //3
默认值,解构时可以给变量设置默认值,数组没有这个元素的话
const arr = [1, 2, 3]
const [, , , defaultVal = '4'] = arr
console.log('设置默认值', defaultVal)
剩余解构-用 “…+变量名” 解构剩余参数到新数组,只能用一次
const arr = [1, 2, 3]
const [e, ...rest] = arr
console.log(rest) //[2, 3]
实例应用
// 拆分字符串
const str = 'xiaobai/18/200'
const strArr = str.split('/')
const [, age] = strArr
console.log(age) //18
对象解构
- 单个/多个解构-跟数组解构差不多
const obj = { name: 'xiaohui', age: 18, height: undefined }
const { name, age } = obj
console.log(name, age) // 'xiaohui', 18
解构+重命名-给解构出来的变量重命名
const obj = { name: 'xiaohui', age: 18, height: undefined }
const { name: objName } = obj
console.log(objName)
默认值-给解构变量设置默认值
const obj = { name: 'xiaohui', age: 18, height: undefined }
const { next = 'default' } = obj
console.log(next)
3.模板字符串
用法:使用``将字符串包裹起来
功能:可以换行、插值、使用标签函数进行字符串操作
示例:
- 换行/插值
//换行
const str = `fdsjak
fdsa`
console.log(str)
// 插值
const strs = `random: ${Math.random()}`
console.log(strs)
标签函数-可以对模板字符串的字符串和插值进行处理和过滤等操作
/**
* 字符串模板函数
* @param {array} strs 以插值为分隔符组成的字符串数组
* @param {string} name 插值的value,有多少个就会传入多少个
*/
const tagFunc = (strs, name, gender) => {
const [str1, str2, str3] = strs
const genderParsed = gender == '1' ? '男' : '女'
// 可以在此做过滤,字符串处理,多语言等操作
return str1 + name + str2 + str3 + genderParsed
0-0-0-}
// 带标签的模板字符串,
const person = {
name: 'xiaohui',
gender: 1,
}
// 返回值为标签函数的返回值
const result = tagFunc`my name is ${person.name}.gender is ${person.gender}`
console.log(result) //my name is xiaohui.gender is 男
4. 字符串扩展方法
- includes-是否包含
- startsWith-是否以什么开始
- endsWith-是否以什么结束
const str = 'abcd'
console.log(str.includes('e')) //false
console.log(str.startsWith('a')) //true
console.log(str.endsWith('a')) //false
5.参数默认值&剩余参数
给函数形参设置默认值
// 带默认参数的形参一般放在后面,减少传参导致的错误几率
const defaultParams = function (name, age = 0) {
return [age, name]
}
console.log(defaultParams(1))
使用…rest 形式设置剩余形参,支持无限参数
// 剩余参数,转化成数组
const restParams = function (...args) {
console.log(args.toString()) //1, 2, 3, 4, 5
}
restParams(1, 2, 3, 4, 5)
6.展开数组
const arr = [1, 2, 3]
console.log(...arr)
// 等价于es5中以下写法
console.log.apply(console, arr)
7.箭头函数
特性&优势:
1、简化了函数的写法
2、没有 this 机制,this 继承自上一个函数的上下文,如果上一层没有函数,则指向 window
3、作为异步回调函数时,可解决 this 指向问题
const inc = (n) => n + 1
console.log(inc(100))
const obj = {
name: 'aa',
func() {
setTimeout(() => {
console.log(this.name) //aa
}, 0)
setTimeout(function () {
console.log(this.name) //undefined
}, 0)
},
}
obj.func()
8.对象字面量增强
- 同名属性可以省略 key:value 形式,直接 key,
- 函数可以省略 key:value 形式
- 可以直接 func(),
- 可以使用计算属性,比如:{[Math.random()]: value}
/**
* 1、增强了对象字面量:
* 1,同名属性可以省略key:value形式,直接key,
* 2,函数可以省略key:value形式
* 3,可以直接func(),
* 4,可以使用计算属性,比如:{[Math.random()]: value}
*/
const arr = [1, 2, 3]
const obj = {
arr,
func() {
console.log(this.arr)
},
[Math.random()]: arr,
}
console.log(obj)
9.Object.assign(target1, target2, targetN)-复制/合并对象
/**
* Object.assign(target1, target2, ...targetn)
* 后面的属性向前面的属性合并
* 如果target1是空对象,可以创建一个全新对象,而不是对象引用
*/
const obj1 = {
a: 1,
b: 2,
}
const obj2 = {
a: 1,
b: 2,
}
const obj3 = Object.assign({}, obj1)
obj3.a = 5
console.log(obj3, obj2, obj1)
10.Object.is(value1, value2)
作用:比较两个值是否相等
console.log(NaN === NaN) //false
console.log(Object.is(NaN, NaN)) //true
console.log(0 === -0) // true
console.log(Object.is(0, -0)) //false
console.log(Object.is(1, 1)) //true
11.Proxy(object, handler)
作用:
- 代理一个对象的所有,包括读写操作和各种操作的监听
用法:
const P = {
n: 'p',
a: 19,
}
const proxy = new Proxy(P, {
get(target, property) {
console.log(target, property)
return property in target ? target[property] : null
},
defineProperty(target, property, attrs) {
console.log(target, property, attrs)
// throw new Error('不允许修改')
},
deleteProperty(target, property) {
console.log(target, property)
delete target[property]
},
set(target, property, value) {
target[property] = value
},
})
proxy.c = 100
console.log('pp', P)
优势:
拥有很多 defineProperty 没有的属性方法,比如:
- handler.getPrototypeOf() —Object.getPrototypeOf 方法的监听器
- handler.setPrototypeOf() —Object.setPrototypeOf 方法的监听器。
- handler.isExtensible() —Object.isExtensible 方法的监听器。
- handler.preventExtensions() —Object.preventExtensions 方法的监听器。
- handler.getOwnPropertyDescriptor() —Object.getOwnPropertyDescriptor 方法的监听器。
- handler.defineProperty() —Object.defineProperty 方法的监听器。
- handler.has() —in 操作符的监听器。
- handler.get() —属性读取操作的监听器。
- handler.set() —属性设置操作的监听器。
- handler.deleteProperty() —delete 操作符的监听器
- handler.ownKeys() —Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的监听器。
- handler.apply() —函数调用操作的监听器。
- handler.construct() —new 操作符的监听器。
对数组的监视更方便
以非侵入的方式监管对象的读写
12.Reflect
作用:
集成 Object 操作的所有方法,统一、方便,具体方法如下:
用于对对象的统一操作,集成 Object 相关的所有方法
1、apply:类似 Function.prototype.apply
2、Reflect.construct()
对构造函数进行 new 操作,相当于执行 new target(…args)。
3、Reflect.defineProperty()
和 Object.defineProperty() 类似。
4、Reflect.deleteProperty()
作为函数的 delete 操作符,相当于执行 delete target[name]。
5、Reflect.get()
获取对象身上某个属性的值,类似于 target[name]。
6、Reflect.getOwnPropertyDescriptor()
类似于 Object.getOwnPropertyDescriptor()。
7、Reflect.getPrototypeOf()
类似于 Object.getPrototypeOf(), 获取目标对象的原型。
8、Reflect.has()
判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同。
9、Reflect.isExtensible()
类似于 Object.isExtensible().判断对象是否可扩展,可以添加额外属性
Object.seal(封闭对象), Object.freeze(冻结对象)是不可扩展的
10、Reflect.ownKeys()
返回一个包含所有自身属性(不包含继承属性)的数组。(类似于 Object.keys(), 但不会受 enumerable 影响).
11、Reflect.preventExtensions()
类似于 Object.preventExtensions()。返回一个 Boolean。
12、Reflect.set()
将值分配给属性的函数。返回一个 Boolean,如果更新成功,则返回 true, 反之返回 false。
13、Reflect.setPrototypeOf()
类似于 Object.setPrototypeOf()。
const obj = {
name: 'reflect',
}
Reflect.preventExtensions(obj) //禁止扩展
console.log(Reflect.set(obj, 'age', 'xiaobai')) //false
console.log(obj) //{ name: 'reflect' }
console.log(Reflect.isExtensible(obj, 'name')) //false
console.log(Reflect.ownKeys(obj)) //[ 'name' ]
13.Promise
14.class&静态方法&继承
使用 class 关键字定义类
class Person {
constructor(props) {
this.props = props
}
}
方法
- 实例方法,需要实例化之后才能调用,this 指向实例
- 静态方法,用 static 修饰符修饰,可以直接通过类名调用,不需要实例化,this 不指向实例,而是指向当前类
class Person {
constructor(props) {
this.props = props
}
// 实例方法
eat() {}
// 静态方法
static run() {}
}
// 调用静态方法
Person.run()
const person = new Person('props')
// 调用实例方法
person.eat()
继承:子类使用 extends 关键字实现继承,可以继承父类所有属性
class Student extends Person {
constructor(props) {
super(props)
}
printProps() {
console.log(this.props)
}
}
const student = new Student('student')
student.printProps()
15.Set
说明:
Set 是一种类似于数组的数据结构
特性:
- 元素唯一性,不允许重复元素
- 使用 add 增加重复元素,将会被忽略
用途:
- 数组去重
- 数据存储
const arr = [1, 3, 1, 1, 1]
const set = new Set(arr)
set.add(1).add(1)
console.log(set.size) //2
const newArr = Array.from(set)
console.log(newArr) //[ 1, 3 ]
16.Map
说明:
类似 Object,以 key、value 形式存储数据
区别:
Map 键不会隐式转换成字符串,而是保持原有类型
实例:
const map = new Map()
map.set(1, 1)
map.set('name', 'map')
map.set(obj, obj)
console.log(map.get(1)) //1
/**
1 1
name map
{ '1': 1, true: true, a: 'a' } { '1': 1, true: true, a: 'a' }
*/
map.forEach((val, key) => {
console.log(key, val)
})
17.Symbol
说明:
JavaScript 第六种原始数据类型,用来定义一个唯一的变量
作用:
创建唯一的变量,解决对象键名重复问题
为对象、类、函数等创建私有属性
修改对象的 toString 标签
为对象添加迭代器属性
如何获取对象的 symbol 属性?
Object.getOwnPropertySymbols(object)