前端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库
什么是 ES6
ES6是指ECMAScript 6,也称为ES2015,是JavaScript的一种标准。它是JavaScript的一个重要更新版本,引入了许多新的语言特性和语法糖,使得JavaScript更加现代化、易读易写、可维护性更高,本文将介绍ES6的一些主要特性及其使用方法,希望对大家有所帮助,提高开发效率。
ES6的一些特性包括:
- 块级作用域变量声明(let和const)
- 箭头函数
- 模板字面量
- 解构赋值
- 默认参数值
- 类和继承
- 模块化
这些新特性可以在现代浏览器中直接使用,或者通过转换工具(如Babel)将ES6代码转换为ES5代码以支持旧版本的浏览器。
块级作用域
在 ES6 之,JavaScript中只有函数作用域和全局作用域,而在 ES6 引入了块级作用域,使得我们可以在代码块中声明变量,而不必担心变量污染全局作用域。
变量的作用域范围
函数作用域: 在函数内部声明的变量只能影响到变量所在的函数体内,无法从外部对函数内部的变量进行调用,被称为"函数作用域",变量在定义的环境(函数)中及嵌套的子函数中处处可用。
块级作用域: {} 这个花括号里面的作用域就是块级作用域。 外层作用域无法获取内层作用域
var 声明的变量是函数作用域
function foo() { var flag = true; if(flag) { var a = 'abc'; let b = 'ABC'; console.log(a); // 输出abc console.log(b); // 输出ABC } console.log(a); // 输出abc console.log(b); // 报错 } foo();
变量在定义的环境(函数)中以及嵌套的子函数中处处可用
var i = 100 for(var i=0; i<5; i++){ console.log(i) //1,2,3,4 } console.log(i) //5
var的话就会导致在 for 循环的变量 i 覆盖掉循环之前的变量i,所以我们需要一块私有领域来包裹住for里面产生的变量,所以产生了块级作用域。
let 声明的变量是块级作用域
用let命令新增了块级作用域,使外层作用域无法获取内层作用域,使变量更安全,但是会造成暂时性死区
// 块级作用域: 外层作用域无法获取内层作用域 function a(){ let a=100; if(a==100){ let a=300 console.log(a) //300 } console.log(a) //100 } a()
const常量:
const PI = 3.1415; PI = 3.1415926; // 报错
var、let和const的区别
- var 声明的变量会提升到作用域的顶部,而 let 和 const 不会进行提升
- var 声明的全局变量会被挂载到全局 window 对象上,而 let 和 const 不会
- var 可以重复声明同一个变量,而 let 和 const 不会
- var 声明的变量作用域范围是函数作用域,而 let 和 const 声明的变量作用域范围是块级作用域。
- const 声明的常量,一旦声明则不能再次赋值,再次赋值会报错(更改对象属性不会,因为对象地址没有变)
作用域提升:
在代码预编译时,javaScript 引擎会自动将 var 声明的语句提升到当前作用域的顶端,。
var声明的变量会提升到作用域的顶部, var可以在声明前调用,值默认为undefined
console.log(a); // 输出undefined console.log(b); // 报错 console.log(PI); // 报错 var a = 'abc'; let b = 'ABC'; const PI = 3.1415;
挂载到全局变量:
全局环境下绑定的就是全局对象,浏览器下是window,node下是global。
var在全局作用域声明变量时会创建一个新的全局变量作为全局对象的属性 。var在全局声明变量时会被挂载到 window 对象上,可以使用window. 的形式访问该变量,或者直接使用变量名的方式。
如果省略变量前面的 let 或者 var 则该变量会成为一个全局变量
var varInfo = 'var 在声明变量' let letInfo = 'let 在声明变量' const PI = 3.1415; // 定义函数,测试两个变量是否在函数体外部可以使用 function test(){ // 在函数作用域中声明的变量 在函数执行退出时立刻销毁 var msg = '这是局部变量' // 没有用 var、let 声明的变量会被挂载到全局 message = '这是全局变量' } test() // 在全局作用域下声明的变量 console.log(varInfo) // var 在声明变量 console.log(window.varInfo) //var 在声明变量 console.log(letInfo) //let 在声明变量 console.log(window.letInfo) // undefined console.log(window.PI); // undefined // 在函数作用域下 声明的变量 console.log(window.message) //这是全局变量 console.log(message) //这是全局变量 console.log(msg) // 报错 es6.html:33 Uncaught ReferenceError: msg is not defined 不能访问函数内部的变量
通过以上案例我们可以发现几点:
- 在全局作用域下 var 声明的变量会被挂载到 window 上,而 let、 const不会。
- 在函数中声明的变量只在函数中生效(函数作用域),当函数执行退出时变量也随即被销毁,因此不能访问函数内部的变量,而在函数内部可以访问函数外部的全局作用域中的变量。(作用域链)
- 没有被声明的变量会被挂载到 window 上。
重复声明变量:
var a = 'abc'; var a; console.log(a); // 输出abc let b = 'ABC'; let b;// 报错
对象的扩展
对象字面量的扩展
- 属性初始值的简写
- 对象方法的简写: 消除了冒号和 function 关键字。
- 可计算属性名:在定义对象时,对象的属性值可通过变量来计算 [ ]。
const name = 'why' const firstName = 'first name' const person = { name, [firstName]: 'ABC', sayName () { console.log(this.name) } }
对象的新增方法
Object.is
通过 Object.is 来判断是否为同一个对象
console.log(+0 === -0) // true console.log(Object.is(+0, -0)) // false console.log(NaN === NaN) // false console.log(Object.is(NaN, NaN)) // true
Object.assign
一个对象接受来自另一个对象的属性和方法。 后者源对象 会覆盖目标对象中已有的同名属性 Object.assign方法不能复制属性的 get 和 set。
const person1 = { age: 23, name: 'why' } const person2 = { age: 32, address: '广东广州' } const person3 = Object.assign({}, person1, person2) console.log(person3) // { age: 32, name: 'why', address: '广东广州' }
Object.keys
返回一个由一个给定对象的自身可枚举属性组成的数组
Object.values
返回一个由一个给定对象的自身可枚举属性值组成的数组. 甚至 Object.values用于扁平化数组例如:
const obbj = { 'a':[2,4,6], 'b':[1,2,4,23], 'c':[32,43,54], 'd':[4,56,20], } let merge = Object.values(deps).flat(Infinity);
其中使用Infinity
作为flat
的参数,使得无需知道被扁平化的数组的维度。
方法
方法仅仅是一个具有功能而非数据的对象属性。将方法定义为一个函数,它会有一个内部[[HomeObject]]属性来容纳这个方法从属的对象。
const person = { // 是方法 [[HomeObject]] = person sayHello () { return 'Hello' } } // 不是方法 function sayBye () { return 'goodbye' } const friend = { sayHello () { return super.sayHello() + '!!!' } } Object.setPrototypeOf(friend, person) console.log(friend.sayHello()) // Hello!!!
friend.sayHello()
方法的[[HomeObject]]
属性值为friend
。friend
的原型是person
。super.sayHello()
相当于person.sayHello.call(this)
。
箭头函数
ES6 中引入了箭头函数,它是一种更加简洁的函数定义方式,在箭头函数中没有属于自己的 this 和arguments 对象,他们会从外部作用域继承 this 和 arguments。
const add = (a+b)=> a+b
箭头函数的定义方式更加简洁
模板字符串
模板字符串,它是一种更加灵活的字符串定义方式。模板字符串使用反引号()包裹字符串,可以在字符串中插入变量和表达式。
const age =18; const message = My name is ${name}, and I am ${age} years old.; console.log(message);//输出:My name is Alice, and I am 18 years old.
在上面的代码中,使用模板字符串定义了一个字符串变量message,并在其中插入了变量name和age的值。
在${}
中甚至可以放入任意的JavaScript表达式,进行运算,以及引用对象属性。
const name = 'lili'; const score = 80; const result = `${name}的考试成绩${score > 60?'':'不'}及格`
[] 结合模板字符串实现动态取值
对象的取值除了用 .
意外还可可以使用 []
访问动态属性名。
let obj = { title1: '嘻嘻嘻', title2: '啦啦啦' } const number = 2 let name = obj[`title${number}`]
也可以通过[]
为对象动态添加属性
cosnt index = 5 obj[`title${index}`] = '哈哈哈';
解构
解构是一种更加方便的变量赋值方式。解构赋值可以从数组或对象中提取值,并将其赋值给变量。
对象解构
必须为解构赋值提供初始化程序,同时如果解构 null 或者 undefined ,解构会发生错误。
const person = { name: 'AAA', age: 23 } let name, age // 必须添加(),因为如果不加,{}代表是一个代码块,而语法规定代码块不能出现在赋值语句的左侧。 ({ name, age } = person) console.log(name) // AAA console.log(age) // 23
解构赋值
const person = { name: 'AAA', age: 23 } let { name, age, sex = '男' } = person // 同名 赋值 等价于 let {name:name,age:age,sex ='男' } = person console.log(sex) // 男
解构默认值
使用解构赋值表达式时,如果指定的局部变量名称在对象中不存在,那么这个局部变量会被赋值为undefined,此时可以随意指定一个默认值。
const person = { name: 'AAA', age: 23 } let { name, age, sex = '男' } = person // 同名 赋值 等价于 let {name:name,age:age,sex ='男' } = person console.log(sex) // 男
let { name: name, age: age } = person含义是:在person对象中取键为name和age的值,并分别赋值给name变量和age变量。
非同名赋值
const person = { name: 'AAA', age: 23 } let { name: newName, age: newAge } = person console.log(newName) // AAA console.log(newAge) // 23 // 在 vue 中的应用 const { params, query } = this.$route // 解构data, 重命名为res const { data: res } = await axios.get('/api')
嵌套对象解构
const person = { name: 'AAA', age: 23, job: { name: 'FE', salary: 1000 }, department: { group: { number: 1000, isMain: true } } } // 在person中提取键为job、在person的嵌套对象department中提取键为group的值,并把其赋值给对应的变量。 let { job, department: { group } } = person console.log(job) // { name: 'FE', salary: 1000 } console.log(group) // { number: 1000, isMain: true }
注意解构的对象不能为 undefined
、null
。否则会报错,所以我们要给被解构的对象一个默认值避免报错的发生。
const {a,b,c,d,e} = obj || {};
数组解构
使用数组字面量,且解构操作全部在数组内完成,解构的过程是按值在数组中的位置进行提取的。
const colors = ['red', 'green', 'blue'] let [firstColor, secondColor] = colors // 按需解构 let [,,threeColor] = colors console.log(firstColor) // red console.log(secondColor) // green console.log(threeColor) // blue
交换变量
let a = 1; let b = 2; [a, b] = [b, a]; console.log(a); // 2 console.log(b); // 1
嵌套数组
const colors = ['red', ['green', 'lightgreen'], 'blue'] const [firstColor, [secondColor]] = colors console.log(firstColor) // red console.log(secondColor) // green
解构数组中的不定元素的原理能实现数组的复制
const colors = ['red', 'green', 'blue'] const concatColors = colors.concat() const [...restColors] = colors console.log(concatColors) // ['red', 'green', 'blue'] console.log(restColors) // ['red', 'green', 'blue']
es6 中的运算符(??、??=、?.、?:)
?? 非空运算符
?? 运算符也被称为空值合并操作符。
如果第一个参数为 null 或 undefined 则返回第二个参数,否则返回第一个参数。
null ?? 8 // => 8 undefined || 8 // =>8 6 ?? 8 // => 6 0 ?? 8 // => 0 false ?? 8 // => false '' ?? 8 // => '' function(obj){ var a = obj ?? {} } 等价于 function(obj){ var a; if( obj === null || obj === undefined){ a = {} } else { a = obj; } }
而 || 运算符则是 判断两个值之间的关系,如果其中一个值为真,则返回该值,否则返回另一个值。若左侧操作数为假值时返回右侧操作数。
null || 8 // => 8 undefined || 8 // =>8 6 || 8 // => 6 0 || 8 // => 8 false || 8 //=> 8 '' || 8 // => 8 function(obj){ var a = obj || {} } 等价于 function(obj){ var a; if(obj === 0 || obj === "" || obj === false || obj === null || obj === undefined){ a = {} } else { a = obj; } }
js 中的假值包含:未定义 undefined、空对象 null、数值 0、 NaN、布尔 false,空字符串''。
?? 运算符允许我们在忽略错误值(如 0 和空字符串)的同时指定默认值。
??= 空赋值运算符
??= 被称为空赋值运算符,与 ?? 运算符相似仅在左值为 null / undefined 时对其赋值。
var a = null var b = 8 console.log(a ??= b) // => 8 相当于 console.log(a = (a ?? b)) // => 8 let c = 0; c ??= b; console.log(c); // 0
a 的值为 null 或 undefined 时,则将 b 的值赋给a变量,否则 a 保持变量原有的值不变。
仅当值为 null 或 undefined 时,此赋值运算符才会赋值。
?. 可选链操作符
可选链运算符,用于判断一个对象是否存在某个属性或方法,如果存在则返回该属性或方法的值,否则返回undefined。
const person = { name: 'lili', age: 18, pet:{ name: 'haha' } }; const name = person?.name // 'lili' const sex = person?.sex // undefined const petName = person?.pet?.name // 'haha'
?. 同时也支持链式调用
?: 三元运算符
?: 三元运算符,用于根据一个条件来返回两个不同的值。
例如:a ? b : c 表示如果a的值为真,则返回b的值,否则返回c的值。
function isPass(score) { return (score > 60) ? '及格' : '不及格' } console.log(isPass(59)) // => 不及格 console.log(isPass(80)) // => 及格
symbol
Symbol作为原始数据类型的一种,表示独一无二的值, Symbol的应用其实利用了唯一性的特性。
如何使用 symbol 呢?
let a = Symbol('a') typeof a // 'symbol' // 使用Object()函数 可以得到一个Symbol()的对象形式 let b = Object('b') typeof b // 'object'
symbol的应用
作为对象的属性
将symbol 作为对象的键名,可以避免键名的冲突
let a = Symbol() // 创建一个Symbol let obj = {} // 创建一个对象 obj[a] = "hello world" // 通过obj[]将Symbol作为对象的键
全局共享 symbol
Symbol.for() 实现了 Symbol 的全局共享
遍历全局注册表中的的Symbol,当搜索到相同描述,那么会调用这个Symbol,如果没有搜索到,就会创建一个新的Symbol。
let a = Symbol.for("a") let b = Symbol.for("a") a === b // true
let a = Symbol("a") let b = Symbol.for("a") a === b // false
- 使用Symbol("a")直接创建,所以该Symbol("a")不在全局注册表中
- 使用Symbol.for("a")在全局注册表中寻找描述为a的Symbol,并没有找到,所以在全局注册表中又创建了一个描述为a的新的Symbol
- 秉承Symbol创建的唯一特性,所以a与b创建的Symbol不同,结果为false
如何判断 Symbol 是否在全局注册表中呢?
Symbol.keyFor()帮我们解决了这个问题,他可以通过变量名查询该变量名对应的Symbol是否在全局注册表中
let a = Symbol("a") let b = Symbol.for("a") Symbol.keyFor(a) // undefined Symbol.keyFor(b) // 'a'
Symbol 的类型不能强转
const uid = Symbol.for('uid') let object = { [uid]: 123 } const symbols = Object.getOwnPropertySymbols(object) console.log(symbols.length) // 1 console.log(symbols[0]) // Symbol(uid)
而当我们在使用console.log()方法打印Symbol,会调用Symbol的String()方法,因此也可以直接调用String()方法输出Symbol。然而尝试将Symbol和一个字符串拼接,会导致程序抛出异常,Symbol也不能和每一个数学运算符混合使用,否则同样会抛出错误。
toPrimitive 类型转化
对于大多数标准对象,数字模式有以下特性,根据优先级的顺序排序如下:
- 调用
valueOf()
方法,如果结果为原始值,则返回。 - 否则,调用
toString()
方法,如果结果为原始值,则返回。 - 如果再无可选值,则抛出错误。
同样对于大多数标准对象,字符串模式有以下优先级顺序:
- 调用
toString()
方法,如果结果为原始值,则返回。 - 否则,调用
valueOf()
方法,如果结果为原始值,则返回。 - 如果再无可选值,则抛出错误。
set 、map
set 集合
Set 集合是一种有序列表,其中含有一些相互独立的非重复值,在 Set 集合中,不会对所存的值进行强制类型转换。
将 set 集合转为数组
const set = new Set([1, 2, 3, 4]) // 方法一:展开运算符 const arr1 = [...set] // 方法二:Array.from方法 const arr2 = Array.from(set) console.log(arr1) // [1, 2, 3, 4] console.log(arr2) // [1, 2, 3, 4]
还有一个小妙招哦
在合并数组的同时可以利用 set 进行去重合并
const a = [1,2,3] const b = [1,5,6] const c = [...new Set([...a,...b])] // [1,2,3,5,6]
Map 集合
Map类型是一种存储着许多键值对的有序列表,其中的键名和对应的值支持所有的数据类型,键名的等价性判断是通过调用Object.is方法来实现的。
map 支持的方法
proxy
Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。
let p = new Proxy(target, handler)
target 代表需要添加代理的对象,handler 用来自定义对象中的操作,比如可以用来自定义 set 或者 get 函数。
数据响应式
vue2中使用Object.definedProperty()方法来实现响应式,而在vue3 中使用Proxy :
- Proxy可以一次性为所有属性实现代理,无需遍历,性能更佳
- Proxy能监听到以前使用Object.definedProperty()监听不到的数据变动。
- 由于是ES6新增加的特性,所以浏览器兼容性方面比Object.definedProperty()差
Proxy 实现响应式:
Reflect是一个内置的对象,它提供拦截 JavaScript 操作的方法。
Reflect 与 Proxy 结合使用以实现和Object.defineProperty类似的功能,且性能更好。
let onWatch = (obj, setBind, getLogger) => { let handler = { get(target, property, receiver) { getLogger(target, property) return Reflect.get(target, property, receiver) }, set(target, property, value, receiver) { setBind(value, property) return Reflect.set(target, property, value) } } return new Proxy(obj, handler) } let obj = { a: 1 } let p = onWatch( obj, (v, property) => { console.log(`监听到属性${property}改变为${v}`) }, (target, property) => { console.log(`'${property}' = ${target[property]}`) } )
通过自定义 set 和 get 函数的方式,在原本的逻辑中插入了我们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。
迭代器、生成器
什么是迭代器?
迭代器是一种特殊的对象,它具有一些专门为迭代过程设计的专有接口,所有迭代器都有一个叫next的方法,每次调用都返回一个结果对象。 结果对象有两个属性,一个是value表示下一次将要返回的值;另外一个是done,它是一个布尔类型的值,当没有更多可返回的数据时返回true。 迭代器还会保存一个内部指针,用来指向当前集合中值的位置,每调用一次next方法,都会返回下一个可用的值。
function createIterator (items) { var i = 0 return { next: function () { var done = i >= items.length var value = !done ? items[i++] : undefined return { done: done, value: value } } } } var iterator = createIterator([1, 2, 3]) console.log(iterator.next()) // { value: 1, done: false } console.log(iterator.next()) // { value: 2, done: false } console.log(iterator.next()) // { value: 3, done: false } console.log(iterator.next()) // { value: undefined, done: true }
什么是生成器?
生成器是一种返回迭代器的函数,通过function关键字后的*号来表示,函数中会用到新的关键词yield。
生成器函数最重要的一点是:每执行完一条yield语句,函数就会自动终止:
yield 关键字只能在生成器内部使用,在其他地方使用会导致抛出错误,即使是在生成器内部的函数中使用也是如此。
function * createIterator () { yield 1 yield 2 yield 3 } const iterator = createIterator() console.log(iterator.next().value) // 1 console.log(iterator.next().value) // 2 console.log(iterator.next().value) // 3
可迭代对象
可迭代对象具有Symbol.iterator属性,是一种与迭代器密切相关的对象。Symbol.iterator通过指定的函数可以返回一个作用于附属对象的迭代器。在ES6中,所有的集合对象(数组、Set集合以及Map集合)和字符串都是可迭代对象,这些对象中都有默认的迭代器。由于生成器默认会为Symbol.iterator属性赋值,因此所有通过生成器创建的迭代器都是可迭代对象。
for-of循环每执行一次都会调用可迭代对象的next方法,并将迭代器返回的结果对象的value属性存储在一个变量中,循环将持续执行这一过程直到返回对象的done属性的值为true。
内建迭代器
数组、Set
集合和Map
集合,它们都内建了如下三种迭代器:
- entries:返回一个迭代器,其值为多个键值对。
- values:返回一个迭代器,其值为集合的值。
- keys:返回一个迭代器,其值为集合中的所有键名。
数组和Set集合:默认迭代器为values。
Map集合:默认为entries。
解构和for-of循环:如果要在for-of循环中使用解构语法,则可以简化编码过程:
解构和for-of循环:如果要在for-of循环中使用解构语法,则可以简化编码过程: const map = new Map([['name', 'AAA'], ['age', 23], ['address', '广东']]) for (let [key, value] of map.entries()) { console.log(key, value) // name AAA // age 23 // address 广东 }
Promise
Promise,它是一种更加方便的异步编程方式。Promise是一个对象,它表示一个异步操作的最终状态(成功或失败) 。我们可以使用Promise的then0和catch0)方法处理异步操作的结果。
function fetchData() { return new Promise((resolve, reject) => { fetch(https://api.example.com/data') .then (response => response. json()) .then(data => resolve(data)) .catch(error => reject (error)); }); } fetchData() .then(data => console. log (data)) .catch(error => console. error (error));
在上面的代码中,使用Promise封装了一个异步操作,并使用then()和catch()方法处理异步操作的结果。
以上是ES6的一些主要特性,它们使得 JavaScripts 的使用更加便捷,更有利于代码的阅读和理解。在实际开发中,我们可以根据需要选择使用这些特性,提高开发效率和代码质量。
前端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库