说一下var、let、const之间的区别?
var:
var声明的变量既是全局变量,也是顶层变量(顶层对象,在浏览器环境下指的是window对象,在node中指的是global对象)
var存在变量提升,可以对一个变量进行多次声明,后面声明的变量会覆盖前面的变量声明
在函数中使用var声明变量,变量是局部的,如果在函数内不使用var,那么这个变量就是全局的
let:
let是es6新增的命令,用来声明变量,他和var类似,但是let只在代码块中有效
不存在变量提升,不允许重复声明变量
let存在暂时性死区,是个块级作用域,
只要在let块级作用域内存在let命令,这个区域就不会受外部影响
在使用let声明变量之前,该变量都不可用,也就是我们常说的暂时性死区
const:
const声明一个只读的常量,一旦声明,常量的值就不能进行改变
const 一旦声明变量,必须立即初始化,不能留到以后赋值
const实际上保证的并不是变量的值不能改动,而是变量指向的内存地址所保存的数据不能改动
对于简单类型的数据,值就保存在变量指向的那个内存地址,因此等同于常量
区别:
可以根据以下5点展开
变量提升:var存在变量提升,let和const不存在变量提升
暂时性死区:var不存在暂时性死区,let和const存在暂时性死区
块级作用域:var不存在块级作用域,let和const存在块级作用域
修改声明的变量:var允许重复声明变量,let和const一律不允许重复声明变量
使用:能使用const尽量使用const,其他情况使用let,避免使用var
es6中数组新增了哪些扩展?
扩展运算符: (...)好比rest参数的逆运算,将一个数组转化为用逗号分隔的参数序列
可以实现简单的数组复制,数组的合并也更加简洁
通过扩展运算符实现浅拷贝,修改了引用指向的值,会同步反映到新的数组
构造函数新增的方法:
Array.from():将两类对象转为真正的数组,类似数组的对象和可遍历的对象
Array.of() 用于将一组值,转换为数组,没有参数时,返回一个空数组,当参数有一个时,是指定数组的长度
实例对象新增的方法:
copyWithin()将指定位置的成员复制到其他位置(会覆盖原有成员)
- target(必需):从该位置开始替换数据。如果为负值,表示倒数。
- start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。
- end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算
find()、findIndex() 用于找出第一个符合条件的数组成员,参数是一个回调函数,接受三个参数依次为当前的值、当前的位置和原数组
fill():使用给定值,填充一个数组
三个参数:分别是填充的值,填充的开始位置,填充的结束位置
entries(),keys(),values():keys()
是对键名的遍历、values()
是对键值的遍历,entries()
是对键值对的遍历
includes():用于判断数组是否包含给定的值
方法的第二个参数表示搜索的起始位置,默认为0
参数为负数则表示倒数的位置
flat(),flatMap():将数组扁平化处理,返回一个新数组,对原数据没有影响
flat()
默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组,可以将flat()
方法的参数写成一个整数,表示想要拉平的层数,默认为1
flatMap()
方法对原数组的每个成员执行一个函数相当于执行Array.prototype.map()
,然后对返回值组成的数组执行flat()
方法。该方法返回一个新数组,不改变原数组
flatMap()
方法还可以有第二个参数,用来绑定遍历函数里面的this
数组的空位
空位指的是某一个位置没有任何值
ES6 则是明确将空位转为undefined
,包括Array.from
、扩展运算符、copyWithin()
、fill()
、entries()
、keys()
、values()
、find()
和findIndex()
建议大家在日常书写中,避免出现空位
对象新增了哪些扩展?
属性的简写:
当我们键名和对应值名相等时,可以进行简写,方法也可以进行简写
注意:简写的对象方法不能用作构造函数,否则会报错
属性名表达式
es6允许字面量定义对象,将表达式放在括号中,表达式还可以用于定义方法名
super关键字
this关键字总是指向函数所在的当前对象,super关键字指向当前对象的原型对象
扩展运算符
在结构赋值中,未被读取的可遍历的属性,分配到指定的对象上
解构赋值必须是最后一个参数,否则会报错
解构赋值是浅拷贝
属性遍历
es6一共有5种方法遍历对象的属性
-
for...in:循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)
-
Object.keys(obj):返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名
-
Object.getOwnPropertyNames(obj):回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名
-
Object.getOwnPropertySymbols(obj):返回一个数组,包含对象自身的所有 Symbol 属性的键名
-
Reflect.ownKeys(obj):返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举
对象新增的方法:
Object.is()
严格判断两个值是否相等,与严格比较运算符(===)的行为基本一致,不同之处只有两个:一是+0
不等于-0
,二是NaN
等于自身
Object.assign()
Object.assign()
方法用于对象的合并,将源对象source
的所有可枚举属性,复制到目标对象target
Object.assign()
方法的第一个参数是目标对象,后面的参数都是源对象
注意:Object.assign()
方法是浅拷贝,遇到同名属性会进行替换
Object.getOwnPropertyDescriptors()
返回指定对象所有自身属性(非继承属性)的描述对象
Object.setPrototypeOf(),方法用来设置一个对象的原型对象
Object.getPrototypeOf()用于读取一个对象的原型对象
Object.keys(),返回自身的(不含继承的)所有可遍历(enumerable)属性的键名的数组
Object.values(),返回自身的(不含继承的)所有可遍历(enumerable)属性的键对应值的数组
Object.entries():返回一个对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对的数组
Object.fromEntries():用于将一个键值对数组转为对象
对象新增了哪些扩展?
参数
ES6
允许为函数的参数设置默认值,函数的形参是默认声明的,不能使用let或const再次声明
参数的默认值可以与解构赋值的默认值结合起来使用
属性
length: 函数的length属性,length将返回没有指定默认值的参数的个数
如果设置了默认值的参数不是尾参数,那么length属性也不会再计入后面的参数
name:返回函数的函数名
作用域
一旦设置了参数的默认值,函数进行声明初始化时,参数就会形成一个单独的作用域
等初始化结束之后,这个作用域也会消失
严格模式
只要函数参数使用了默认值,解构赋值,或者扩展运算符,那么函数的内部就不能显式设定为严格模式,否则会报错
箭头函数
=>定义函数,如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return
语句返回
注意点:
- 函数体内的
this
对象,就是定义时所在的对象,而不是使用时所在的对象 - 不可以当作构造函数,也就是说,不可以使用
new
命令,否则会抛出一个错误 - 不可以使用
arguments
对象,该对象在函数体内不存在。如果要用,可以用rest
参数代替 - 不可以使用
yield
命令,因此箭头函数不能用作 Generator 函数
谈谈你对es6中新增的set,map两种数据结构的理解?
set时一种叫做集合的数据结构,map是一种叫做字典的数据结构
集合:由一堆无序的相关联的,且不重复的内部结构组成的组合
字典:是一些元素的集合。每个元素有一个称作key 的域,不同元素的key 各不相同
区别?
- 共同点:集合、字典都可以存储不重复的值
- 不同点:集合是以[值,值]的形式存储元素,字典是以[键,值]的形式存储
set:
set是es6新增的数据结构,类似于数组,但是成员的值都是唯一的,没有重复的值,我们一般称为集合
set本身是一个构造函数,用来生成set数据结构
增删改查
-
add()添加某个值,返回
Set
结构本身 -
delete()删除某个值,返回一个布尔值,表示删除是否成功
-
has()返回一个布尔值,判断该值是否为
Set
的成员 -
clear()清除所有成员,没有返回值
遍历
- keys():返回键名的遍历器
- values():返回键值的遍历器
- entries():返回键值对的遍历器
- forEach():使用回调函数遍历每个成员
扩展运算符和Set
结构相结合实现数组或字符串去重
map
map类型是键值对的有序列表,而键和值都可以是任意类型
map本身是一个构造函数,用来生成map数据结构
增删改查
- size 属性返回 Map 结构的成员总数
- set()设置键名
key
对应的键值为value
,然后返回整个 Map 结构如果
key
已经有值,则键值会被更新,否则就新生成该键同时返回的是当前
Map
对象,可采用链式写法 - get()
get
方法读取key
对应的键值,如果找不到key
,返回undefined
- has()
has
方法返回一个布尔值,表示某个键是否在当前 Map 对象之中 - delete()
delete
方法删除某个键,返回true
。如果删除失败,返回false
- clear()
clear
方法清除所有成员,没有返回值
遍历
- eys():返回键名的遍历器
- values():返回键值的遍历器
- entries():返回所有成员的遍历器
- forEach():遍历 Map 的所有成员
你是怎么理解es6中的promise?
promise(承诺),他是异步编程的一种解决方案,比传统的解决方案更加合理和更加强大
promise解决异步操作的优点:
链式操作降低了编码难度
代码的可读性明显增强
状态:
promise有三种状态
pending:进行中
fulfilled:已成功
rejected:已失败
特点:
- 对象的状态不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态
- 一旦状态改变(从
pending
变为fulfilled
和从pending
变为rejected
),就不会再变,任何时候都可以得到这个结果
流程
用法
promise对象是一个构造函数,用来生成promise实例,promise构造函数接收一个函数作为参数,该函数的两个参数分别是resolve和reject
resolve函数的作用是将对象的状态从未完成变为成功
reject函数的作用是将对象的状态从未完成变为失败
实例方法
then
then
是实例状态发生改变时的回调函数,第一个参数是resolved
状态的回调函数,第二个参数是rejected
状态的回调函数
then
方法返回的是一个新的Promise
实例,也就是promise
能链式书写的原因
catch
catch()
方法是.then(null, rejection)
或.then(undefined, rejection)
的别名,用于指定发生错误时的回调函数
Promise
对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止
finally
finally()
方法用于指定不管 Promise 对象最后状态如何,都会执行的操作
all
Promise.all()
方法用于将多个 Promise
实例,包装成一个新的 Promise
实例
race
Promise.race()
方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例
使用场景
将图片加载写成一个promise,一旦加载完成,promise的状态就发生变化
你是怎么理解ES6中 Generator的?使用场景?
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同
回顾下上文提到的解决异步的手段:
- 回调函数
- promise
Generator函数
执行 Generator
函数会返回一个遍历器对象,可以依次遍历 Generator
函数内部的每一个状态
形式上,Generator
函数是一个普通函数,但是有两个特征:
function
关键字与函数名之间有一个星号- 函数体内部使用
yield
表达式,定义不同的内部状态
使用
Generator
函数会返回一个遍历器对象,即具有Symbol.iterator
属性,并且返回给自己
通过yield关键字可以暂停generator函数返回的遍历器对象的状态
异步解决的方案
回调函数:回调函数,就是把任务代码单独写在一个函数里,等到重新执行这个任务时,再调用这个函数
Promise 对象:就是为了解决回调地狱而产生的
generator 函数:yield表达式可以暂停函数执行,next方法用于恢复函数执行,这使得Generator
函数非常适合将异步任务同步化
async/await:代码更加简介,语义化更强
区别?
-
promise
和async/await
是专门用于处理异步操作的 -
Generator
并不是为异步而设计出来的,它还有其他功能(对象迭代、控制输出、部署Interator
接口...) -
promise
编写代码相比Generator
、async
更为复杂化,且可读性也稍差 -
Generator
、async
需要与promise
对象搭配处理异步情况 -
async
实质是Generator
的语法糖,相当于会自动执行Generator
函数 -
async
使用上更为简洁,将异步代码以同步的形式进行编写,是处理异步编程的最终方案
使用场景
Generator
是异步解决的一种方案,最大特点则是将异步操作同步化表达出来
你是怎么理解ES6中Proxy的?使用场景?
用来定义基本操作中的自定义行为
本质就是修改程序的默认行为,形同与在编程语言层面上做一些修改,属于元编程
元编程(Metaprogramming,又译超编程,是指某类计算机程序的编写,这类计算机程序编写或者操纵其它程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作
元编程的特点:与手工编写全部代码相比,程序员可以获得更高的工作效率,或者给与程序更大的灵活度去处理新的情形而无需重新编译
Proxy
亦是如此,用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
用法:
Proxy
为 构造函数,用来生成 Proxy
实例
参数:target(表示要拦截的目标对象,任何类型的对象包括原生数组,函数,甚至是另一个代理)
handler(通常以函数作为属性的对象,个属性中的函数分别定义了在执行各种操作时代理p的行为)
var proxy = new Proxy(target, handler)
取消代理
Proxy.revocable(target, handler);
使用场景
- 拦截和监视外部对对象的访问
- 降低函数或类的复杂度
- 在复杂操作前对操作进行校验或对所需资源进行管理
你是怎么理解ES6中Module的?使用场景?
模块,是能够单独命名并独立的完成一定功能的程序语句的集合(程序代码和数据结构的集合体)
两个基本的特征:外部特征和内部特征
-
外部特征是指模块跟外部环境联系的接口(即其他模块或程序调用该模块的方式,包括有输入输出参数、引用的全局变量)和模块的功能
-
内部特征是指模块的内部环境具有的特点(即该模块的局部数据和程序代码)
为什么需要模块化?
- 代码抽象
- 代码封装
- 代码复用
- 依赖管理
如果我们没有模块化,我们的代码会变得不容易维护,容易污染全局作用域
加载资源的方式通过script标签从上到下
依赖的环境主观逻辑偏重,代码较多就会比较复杂
大型项目资源难以维护,特别是多人合作的情况下,资源会让人崩溃
使用
模块功能主要由两个命令构成
export:用于规定模块的对外接口
一个模块就是一个独立的文件,该文件内部的所有变量,外部无法获取,如果希望外部能够读取内部的某个变量,就必须通过export关键字输出该变量
import: 用于输入其他模块提供的功能
使用export命令定义了模块的对外接口以后,其他的js文件就可以通过import加载这个命令
我们可以通过as 关键字配置别名
当我们需要加载整个模块,使用*
输入的变量都是只读的,不允许修改,但是如果是对象,允许修改属性
动态加载
允许仅在需要时动态加载模块,不必预先加载所有模块,性能优势明显
这个功能允许我们将import作为函数调用,将其作为参数传递给模块的路径
使用场景
如今,ES6
模块化已经深入我们日常项目开发中,像vue
、react
项目搭建项目,组件化开发处处可见,其也是依赖模块化实现
你是怎么理解ES6中 Decorator 的?使用场景?
Decorator,即装饰器,简单来说,装饰着模式就是一种在不改变原类和使用继承的情况下,动态的扩展对象功能的设计理论
本质也是一个普通的函数,用于扩展类属性和类方法
用法:
类的装饰:对类本身进行装饰,能够接收一个参数
类属性的装饰:
- 类的原型对象
- 需要装饰的属性名
- 装饰属性名的描述对象
首先定义yigereadonly装饰器,使用readonly装饰类的name方法,如果一个方法有对各装饰器,就像洋葱一样,从外到内,再由内到外执行
注意:装饰器不能用于装饰函数,因为函数存在变量声明情况