2020年已经到来,是否是该为了更好的2020年再战一回呢? ‘胜败兵家事不期,包羞忍耻是男儿。江东子弟多才俊,卷土重来未可知’,那些在秋招失利的人,难道就心甘情愿放弃吗!css
此文总结2019年以来本人经历以及浏览文章中,较热门的一些面试题,涵盖从CSS到JS再到Vue再到网络等前端基础到进阶的一些知识。前端
总结面试题涉及的知识点是对本身的一个提高,也但愿能够帮助到同窗们,在2020年会有一个更好的竞争能力。java
css篇
- juejin.im/post/5e040e…Javavscript篇
- juejin.im/post/5e2122…ECMAScript
是编写脚本语言的标准,意味着Javascript
遵循ECMAScript
标准中的规范变化,能够说是Javascript
的蓝图node
ECMAScript
和Javascript
本质上都跟一门语言有关,一个是语言自己,一个是语言的约束条件。c++
ECMAScript 6
是一个新的标准,它包含了许多新的语言特性和库,是Javascript
最实质的一次升级,好比箭头函数、字符串模板、generator
(生成器)、async/await
、解构赋值、class
等等,以及引入了module
模块概念es6
Class
Promise
Generator
生成器Symbol
类型Proxy
代理Set
& Map
rest
与展开运算符...
什么是块级做用域? 由一对花括号{}
中的语句集都属于一个块,在这个{}
代码块中定义的全部变量在这个代码块以外都是不可见的,所以称为块级做用域面试
为何须要块级做用域?算法
因为ECMAScript 6
以前只有全局做用域、函数做用域、eval做用域,没有块级概念,这会带来不少不合理的场景:编程
var i = 5
function fun(){
console.log(i)
if(true){
var i = 6
}
}
fun() // undefined
复制代码
for (var i = 0; i < 10; i++) {
console.log(i);
}
console.log(i); // 10
复制代码
为了解决这些问题,ECMAScript 6
新增了let
/ const
实现块级做用域数组
let
与 const
,与var
区别在哪?let
let
- 用于声明变量,用法与var
相似,但其声明的变量,只在let
所在的代码块中有效{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
复制代码
for (let i = 0; i < 10; i++) {
// ...
}
console.log(i) // ReferenceError: i is not defined
复制代码
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
-------------- var → let ------------------
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
复制代码
let
不存在变量提高 - var
所定义的变量,存在变量提高现象,便可以在声明以前使用(值为undefined
),let
改变了这一语法行为,它所声明的变量必定要在声明以后才能使用,否在报错console.log(bar); // ReferenceError
let bar = 2;
复制代码
let
,它所声明的变量就'绑定'在这个区域,不受外部影响var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
上面代码中,存在全局变量tmp,可是块级做用域内let又声明了一个局部变量tmp,致使后者绑定这个块级做用域,因此在let声明变量前,对tmp赋值会报错
复制代码
❗ 小知识:
ECMAScript 6
明确规定,若是区块中存在let | const
命令,这个区块对这些命令声明的变量,从一开始就造成封闭做用域,凡是在let
声明变量前,对该变量操做,都会报错(使用let、const命令声明变量以前,该变量都是不可用的,这在语法上,成为'暂时性死区')
let
不容许在相同做用域内,重复声明同一个变量const
const
- 声明一个只读的常量,一旦声明,常量的值就不能改变const PI = 3.1415;
PI // 3.1415
PI = 3; // TypeError: Assignment to constant variable.
复制代码
const
声明的变量不得改变值,这意味const
一旦声明变量,就必须当即初始化,不能留到之后赋值const foo // SyntaxError: Missing initializer in const declaration
复制代码
const
与 let
相同,只在声明所在的块级做用域内有效if (true) {
const MAX = 5;
}
MAX // Uncaught ReferenceError: MAX is not defined
复制代码
const
一样存在暂时性死区概念,只能在声明的位置以后使用if (true) {
console.log(MAX); // ReferenceError
const MAX = 5;
}
复制代码
const
声明的变量,也不能重复声明var message = "Hello!";
let age = 25;
// 如下两行都会报错
const message = "Goodbye!";
const age = 30;
复制代码
1 - var【声明变量】
var 没有块的概念,能够跨块访问,没法跨函数访问
2 - let【声明块中的变量】 - 存在暂时性死区
let 只能在块做用域里访问,不能跨块访问,更不能跨函数访问
3 - const【声明常量,一旦赋值便不可修改】 - 存在暂时性死区
const 只能在块级做用域里访问,并且不能修改值
Tips: 这里的不能修改,并非变量的值不能改动,而是变量所指向的那个内存地址保存的指针不能改动
4 - 在全局做用域下使用let和const声明变量,变量并不会被挂载到window上,这一点与var不一样
复制代码
ECMAScript 6
标准新增了一种新的函数:Arrow Function(箭头函数)
x => x*x
至关于
function(x) {
return x*x
}
复制代码
this
、arguments
、super
或者new.target
// Es 5
var getDate = function(){
reutrn new Date()
}
// Es 6
var getDate = () => new Date()
在箭头函数版本中,咱们只须要()括号,不须要 return 语句,由于若是咱们只有一个表达式或值须要返回,箭头函数就会有一个隐式的返回
复制代码
arguments
对象,因此调用第一个getArgs()
时会抛出错误,咱们能够经过...rest
来存储全部参数,并获取const getArgs = () => arguments
getArgs('1','2','3') // ReferenceError: arguments is not defined
const getArgs2 = (...rest) => rest
getArgs('1','2','3') // ["1", "2", "3"]
复制代码
this
,它捕获词法做用域函数的this
值const data = {
result:0,
nums:[1,2,3,4,5],
computeResult(){
// this → data对象
const addAll = () => {
return this.nums.reduce((total, cur) => total + cur, 0)
}
this.result = addAll()
}
}
复制代码
这个例子中,addAll
函数将复制computeResult
方法中的this
值,若是咱们在全局做用域声明箭头函数,则this
值为window
对象
this
对象,就是定义时所在的对象,而不是使用时所在的对象new
关键字,不然抛出错误arguments
对象,该对象在函数体内不存在(可用rest
参数代替)yield
命令,所以箭头函数不能用做Generator
函数上面四点,第一点尤为重要,this
对象的指向是可变的,但在箭头函数中,this
是固定的,不可变的
const obj = {
a: () => {
console.log(this.id)
}
}
var id = '1'
obj.a() // '1'
obj.a.call({
id:'2'
}) // '1'
复制代码
Javascript
中,生成实例对象的方法是经过构造函数,这种方式与传统的面向对象语言(好比c++,java)差别很大,ECMAScript 6
提供了更接近于传统面向对象的写法,引入了Class
类的概念,做为对象的模板。经过Class
关键字来定义类
Class
类是在Js中编写构造函数的另外一种方式,本质上它就是使用构造函数的语法糖,在底层中使用仍然是原型和基`于原型的继承
// Es 5
function Person(name, age, job){
this.name = name
this.age = age
this.job = job
}
Person.prototype.getPerson = function(){
return name + '|' + age + '|' + job
}
// Es 6
class Person {
constructor(name, age, job){
this.name = name
this.age = age
this.job = job
}
getPerson(){
return this.name + '|' + this.age + '|' + this.job
}
}
var person = new Person('chicago',22,'student')
person.getPerson() // chicago|22|student
复制代码
Class
类中继续存在,实际上,类的全部方法都定义在类的prototype属性上class Person {
constructor(name, age, job){
this.name = name
this.age = age
this.job = job
}
getPerson(){
return this.name + '|' + this.age + '|' + this.job
}
}
console.log(Person.prototype) // {constructor: ƒ, getPerson: ƒ}
复制代码
class A{
// ...
}
let a = new A()
console.log(a.constructor === A.prototype.constructor)
// 这其实很好理解,a实例上并无constructor属性,因此会经过原型链向上查找属性,最后在A类中找到constructor
复制代码
class Point{
constructor(x,y){
// ...
}
toString(){
// ...
}
}
Object.keys(Point.prototype) // [] 这里toString方法是Point类内部定义的方法,是不可枚举的
Object.getOwnPropertyNames(Point.prototype) // ["constructor","toString"]
复制代码
constructor
方法constructor
方法是类的默认方法,经过new
命令生成对象实例时,自动会调用该方法,一个类必须有constructor
方法,若是没有显式定义,一个空的constructor
会被默认添加class A{}
等同于
class A{
constructor(){}
}
复制代码
this
),咱们彻底能够指定返回另外一个对象class Foo{
constructor(){
return Object.create(null);
}
}
new Foo() instanceof Foo // false → constructor返回了一个全新的对象,致使实例对象再也不是Foo类的实例
复制代码
this
上),不然都是定义在原型上class A{
constructor(x, y){
this.x = x
this.y = y
this.getA = function(){
console.log('A')
}
}
toString(){
return '(' + this.x + ', ' + this.y + ')'
}
}
var a = new A(2,3)
point.toString(); // (2,3)
point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('getA') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty("toString") // true
复制代码
x、y、getA都是实例对象a自身的属性(由于定义在this变量上),因此hasOwnProperty方法返回true ,而toString是原型对象的属性(由于定义在A类上),因此hasOwnProperty方法返回false
Es5
一致,类的全部实例也共享一个原型对象var p1 = new Person('chicago')
var p2 = new Person('Amy')
p1.__proto__ === p2.__proto__ // true
- p1 / p2 都是Person的实例,它们的原型都是`Person.prototype`,因此`__proto__`天然是相同的
- 这也意味着能够经过实例的`__proto__`属性为'类'添加方法
p1.__proto__.getName = function(){
return 'i get name'
}
p1.getName() // i get name
p2.getName() // i get name
var p3 = new Person('Jack')
p3.getName() // i get name
复制代码
Es 5
同样,在'类'的内部能够经过使用get
和set
关键字,对某个属性设置存值函数与取值函数class A{
constructor(){
//...
}
get prop(){
return 'getter'
}
set prop(value){
console.log('setter:' + value)
}
}
let a = new A()
a.prop = 123 // setter:123
a.prop // getter
这里`prop`属性有对应的存值函数和取值函数。所以赋值和读取行为都被自定义了
❗ Ps - 存值函数和取值函数是设置在属性的Descriptor对象上的
复制代码
Class
的几个注意点new
来调用,不然会报错,这是与普通构造函数的一个主要区别,后者不须要new
也能够执行class Foo{
constructor(){}
}
Foo() // TypeError: Class constructor Foo cannot be invoked without 'new'
复制代码
use strict
指定运行模式(只要代码写在类之中,就只有严格模式可用)new Foo() // ReferenceError: Cannot access 'Foo' before initialization
class Foo{}
❗ Ps - Es6不会把类的声明提高到代码头部
复制代码
name
属性 - 本质上,Es6
的类只是Es5
的构造函数的一层包装,因此函数的许多特性都被class继承,包括name
class A{}
A.name // A → name属性老是返回跟在`class`关键字后面的类名
复制代码
this
指向 - 类的方法内部若是含有this,this指向类的实例,经过this.xxx = ...
的方式赋值,都是在实例自己上操做
static
)含有this
关键字,则this
指向的是类,而不是实例模板字符串是在Js
中建立字符串的一种新方式,咱们能够经过使用反引号让模板字符串化
// Es 5
var greet = 'Hi I\'m Chicago' // Es 6 var greet = `Hi I'm Chicago`
复制代码
${expr}
来界定// Es 5
var name = 'chicago'
console.log('hello' + name) // hello chicago
// Es 6
var name = 'chicago'
console.log(`hello ${name}`) // hello chicgao
复制代码
//ES5
var str = '\n' + ' I \n' + ' Am \n' + 'Iron Man \n';
// Es 6
var str = `
I
Am
Iron Man
`
复制代码
在ES6中当你的对象属性名和当前做用域中的变量名相同时,ES6的对象会自动的帮你完成键到值的赋值
// Es 5
var foo = 'Foo'
var bar = 'Bar'
var A = {
foo:foo,
bar:bar
}
// Es 6
var foo = 'Foo'
var bar = 'Bar'
var A = {
foo,
bar
}
复制代码
什么是解构赋值? 从数组和对象中提取值,对变量进行赋值,就称为解构赋值
let a = 1,b = 2,c = 3
等同于
let [a, b, c] = [1, 2, 3]
复制代码
从对象中获取属性,Es6
前的作法是建立一个与对象属性同名的变量,这种方式较繁琐,由于每个属性都须要一个新变量,Es6
中解构赋值就完美的解决这一问题
const person = {
name: "Chicago",
age: "22",
job:'student'
}
// Es 5
var name = person.name
var age = person.age
var job = person.job
// Es 6 解构赋值
var {name, age, job} = person
复制代码
let [foo,[bar],baz] = [1,[2],3]
let [,,c] = ['a','b','c']
let [a,,c] = ['a','b','c']
let [a,b,...c] = ['a'] // a:'a' b:undefined c:[]
复制代码
let [a] = [] // a:undefined
复制代码
let [a,b] = [1,2,3]
let [a,[b],d] = [1,[2,3],4] // a:1 b:2 c:4
复制代码
let [foo] = 1 // TypeError: 1 is not iterable
let [foo] = {} // TypeError: {} is not iterable
...
复制代码
function* fibs(){
let a = 0
let b = 1
while(true){
yield a;
[a,b] = [b,a + b]
}
}
let [first,second,third,fourth,fifth,sixth] = fibs()
sixth // 5
复制代码
let [foo = 'Foo'] = [] // foo:'Foo'
let [x,y = 'b'] = ['a'] // x:'a' y:'b'
let [x,y = 'b'] = ['a', undefined] // x:'a' y:'b'
❗ Ps:因为Es6内部使用严格相等运算符(===)来进行判断是否有值,因此只有一个数组成员严格等于undefined时,默认值才生效
let [x = 1] = [null] // x:null
❗ Ps:默认值能够引用解构赋值的其余变量,但该变量必须已经声明
let [x = 1, y = x] = [] // x:1 y:1
let [x = 1, y = x] = [2] // x:2 y:2
let [x = 1, y = x] = [1, 2] // x:1 y:2
let [x = y, y = 1] = [] // ReferenceError: Cannot access 'y' before initialization
复制代码
let {foo,bar} = { foo:'Foo', bar:'Bar' } // foo:'Foo' bar:'Bar'
let {baz} = { foo:'Foo', bar:'Bar' } // baz:undefined
复制代码
let { foo:baz } = { foo:'Foo', bar:'Bar' } // baz:'Foo'
{ 属性名:变量名 } - 真正被赋值的是后者
复制代码
var { x = 3 } = {} // x:3
var { message: msg = 'Hello Es 6' } = {} // msg:'Hello Es 6'
复制代码
Promise能够说是Es6中最重要的一个知识点,想要真正的学好Promise,须要较大篇幅,这里建议浏览阮一峰博客(es6.ruanyifeng.com/#docs/promi…
Promise
的意义 - Promise
是异步编程的一种解决方案,是一个对象,经过它能够获取异步操做的消息Promise
对象有如下特色
Promise
对象表明一个异步操做,具备三种状态:pending(进行中)
、fulfilled(已成功)
、rejected(已失败)
。只有异步操做的结果能够决定当前是哪种状态,任何其余操做都没法改变这个状态❗ 小知识:
Promise
对象的状态改变,只有两种可能
pending
变为fulfilled
pending
变为rejected
只要这两种状况发生,状态就凝固了(不会再变化),会一直保持这个结果,这时就称为resolved(已定型),若是改变已经发生,再对Promise
对象添加回调函数,也会当即获得这个结果
Event
彻底不一样,Event
的特色是,若是你错过了它,再去监听,是得不到结果的Promise
如何解决回调地狱?回调地狱 - 若是咱们在回调内部存在另一个异步操做,即出现多层嵌套问题,致使变成一段混乱且不可读的代码,此代码就称为'回调地狱'
这两个问题在回调函数中尤其突出,Promise
的诞生就是为了解决这两个问题
Promise
解决'回调地狱'
Promise
经过此三个方面解决问题:
let readFilePromise = (fileName) => {
fs.readFile(fileName, (err, data) => {
if(err){
reject(err)
}else{
resolve(data)
}
})
}
readFilePromise('xxx1').then( res => {
return readFilePromise('xxx2')
})
复制代码
上面代码中,回调函数经过在then()
中传入,实现了回调函数延迟绑定
let promise = readFilePromise('xxx1').then( res => {
return readFilePromise('xxx2') // 返回一个Promise对象
})
promise.then( //... )
复制代码
根据在then()
中回调函数的传入值建立不一样的Promise
,而后把返回的Promise
穿透到外层,后续可继续使用,上面promise
实际上就是内部返回的Promise
,promise
变量能够继续调用then()
,这即是返回值穿透
解决多层嵌套,就是结合回调函数延迟绑定
和返回值穿透
,实现链式调用
来解决
readFilePromise('xxx1').then( res => {
return readFilePromise('xxx2')
}).then( res => {
return readFilePromise('xxx3')
}).then( res => {
return readFilePromise('xxx4')
})
复制代码
链式调用
解决多层嵌套,那么每次任务执行结束后成功和失败的分别处理,又经过什么解决?
Promise
经过错误冒泡
的方式来解决问题readFilePromise('xxx1').then( res => {
return readFilePromise('xxx2')
}).then( res => {
return readFilePromise('xxx3')
}).catch( err => {
// 错误处理
})
复制代码
上面的代码中,不管是前面的错误仍是后面的错误,全部产生的错误都会一直向后传递,被catch()
接收到,这样一来就没必要重复就处理每个任务的成功和失败结果
then()
Promise
具备许多Api
then
- 为Promise实例添加状态改变时的回调函数catch
- 用于指定发生错误时的回调函数finally
- 用于指定无论Promise对象最后状态如何,都会执行的操做all
- 用于将多个Promise实例,包装成一个新的Promise实例race
- 与all同样,将多个实例包装成一个新的Promise实例allSettled
- 接受一组 Promise 实例做为参数,包装成一个新的 Promise 实例。只有等到全部这些参数实例都返回结果,无论是fulfilled仍是rejected,包装实例才会结束any
- 方法接受一组 Promise 实例做为参数,包装成一个新的 Promise 实例。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;若是全部参数实例都变成rejected状态,包装实例就会变成rejected状态resolve
- 将现有对象转为 Promise 对象reject
- 返回一个新的 Promise 实例,该实例的状态为rejectedtry
其中,then()
是Promise
中出现概率最高的,所以then()
返回值是必须理解的
对于一个Promise
来讲,当一个Promise
完成(fulfilled
)或者失败(rejected
),返回函数将被异步调用。具体的返回值依据如下规则返回:
then
中的回调函数返回一个值,那么then
返回的Promise
就会变成接受状态(resolved
),而且将返回的值做为接受状态的回调函数的参数值promise1().then( () => {
return 'I am return a value'
}).then( res => {
console.log(res) // I am return a value
})
复制代码
then
中的回调函数没有返回值,那么then
返回的Promise将会成为接受状态(resolved
),而且该接受状态的回调函数的参数值为undefined
promise1().then( () => {
console.log('nothing return')
}).then( res => {
console.log(res) // undefined
})
复制代码
then
中的回调函数抛出一个错误,那么then
返回的Promise
将会成为拒绝状态(rejected
),而且将抛出的错误做为拒绝状态的回调函数的参数值promise1().then( () => {
throw new Error('I am Error')
}).then( res => {
console.log(res) // 不执行
}).catch( err => {
console.log(err) // I am Error
})
复制代码
then
中的回调函数返回一个已是接受状态的Promise
,那么then
返回的Promise
也会成为接受状态,而且将那个Promise
的接受状态的回调函数的参数值做为该被返回的Promise
的接受状态的回调函数的参数值var promise1 = function(){
return new Promise((resolve,reject)=>{
resolve()
})
}
var promise2 = function(){
return new Promise((resolve,reject) => {
resolve('I am p2 and Im resolved')
})
}
promise1().then( () => {
// 返回一个已是接受状态的Promise
return promise2() // 若是不加return,这个回调将没有返回值,参考第二点
}).then( res => {
console.log(res) // I am p2 and Im resolved
})
复制代码
then
中的回调函数返回一个已是拒绝状态的Promise
,那么then
返回的Promise也会成为拒绝状态,而且将那个Promise
的拒绝状态的回调函数的参数值做为该被返回的Promise
的拒绝状态的回调函数的参数值var promise1 = function(){
return new Promise((resolve,reject)=>{
resolve()
})
}
var promise2 = function(){
return new Promise((resolve,reject) =>{
reject('I am p2 and Im rejected')
})
}
promise1().then( () => {
// 返回一个已是拒绝状态的Promise
return promise2() // 若是不加return,这个回调将没有返回值,参考第二点
}).then( res => {
console.log(res) // 不执行
}).catch( err => {
console.log(err) // I am p2 and Im rejected
})
复制代码
then
中的回调函数返回一个**未定状态(pending)**的Promise
,那么then
返回的Promise
也是未定状态,而且它的最终状态会与那个Promise
的最终状态相同,同时,它变为终态时调用的回调函数的参数值与那个Promise
变为终态时的回调函数的参数值相同var promise1 = function(){
return new Promise((resolve,reject)=>{
resolve()
})
}
var promise2 = function(){
// 定时器,初始状态未定,3s后更新状态
return new Promise((resolve,reject) => {
setTimeout(()=>{
resolve('p2 after 3s resolve')
// reject('p2 after 3s reject')
},3000)
})
}
promise1().then( () => {
return promise2()
}).then( res => {
console.log(res) // p2 resolve → p2 after 3s resolve
}).catch( err => {
console.log(err) // p2 reject → p2 after 3s reject
})
以上 then与catch均在3s后当promise2()状态改变才会执行
复制代码
Generator
函数是什么,有什么用?Generator
函数是ECMAScript 6
提供的一种异步编程解决方案,语法行为与传统函数彻底不一样。
Generator
函数能够理解成状态机,封装了多个内部状态Generator
函数也能够是一个普通函数,但具备两个特征
function
关键字与函数名之间有一个星好(*)yield
表达式,定义不一样的内部状态执行Generator
函数会返回一个遍历器对象,每一次Generator
函数里面的yield
表达式都至关于一次遍历器对象的next()
方法,而且能够经过next(value)
的方式传入自定义的值,来改变Generator
函数的行为
Generator
的用途Generator
能够暂停函数执行,返回任意表达式的值。这种特色使得 Generator 有多种应用场景,例如:
Iterator
接口模块使咱们可以将代码基础分割成多个文件,以得到更高的可维护性,而且避免将全部代码放在一个大文件中
在Es 6
支持模块以前,有两个流行的模块
基本上,Es 6
使用模块的方式很简单,import
用于从另外一个文件中获取功能或值,export
用于从文件中导出功能或值
Es 5 - CommonJs
// 导出
export.xxx = function(args){
// todo
}
// 引入
const xxx = requir('xxx').xxx
Es 6 - CommonJs
// 导出
export function xxx(args){
// todo
}
// 引入
import xxx from 'xxx'
复制代码
Es 6
模块与CommonJs
模块的差别CommonJs
模块输出的是一个值的拷贝,Es 6
模块输出的是值的引用
CommonJs
模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值Es 6
模块的运行机制与CommonJs
不同,JS
引擎对脚本静态分析的时候,遇到模块加载命令import
,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,Es 6
的模块化,原始值变了,import
加载的值也会跟着变。(Es 6
模块是动态引用,而且不会缓存值,模块里面的变量绑定其所在的模块)CommonJs
模块是运行时加载,Es 6
模块是编译时输出接口
CommonJs
模块就是对象,即再输入时是先加载整个模块,生成一个对象,而后再从这个对象上面读取方法Es 6
模块不是对象,而是经过export
命令显式指定输出的代码,import
时采用静态命令的形式,即在import
时能够指定加载某个输出值,而不是加载整个模块CommonJs
加载的是一个对象(model.export
属性),该对象只有在脚本运行完才会生成
Es 6
模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成
Symbol是Es 6新增的一种数据类型,是一种特殊的、不可变的数据类型,能够做为对象属性的标识符使用,表示独一无二的值
语法 - Symbol([description])
description
- 可选的字符串,可用于调式但不访问符号自己的符号的说明,若是不加参数,在控制台打印的都是Symbol
,不利于区分
用途 - 做为属性名的Symbol
let symbolProp = Symbol()
var obj = {}
obj[symbolProp] = 'hello Symbol'
// Or
var obj = {
[symbolProp] : 'hello Symbol';
}
// Or
var obj = {};
Object.defineProperty(obj,symbolProp,{value : 'hello Symbol'});
复制代码
Proxy用于修改某些操做的默认行为,也能够理解为在目标对象以前架设一层拦截,外部全部的访问都必须经过这层拦截,所以提供一种机制,能够对外部的访问进行过滤和修改
Es 6提供Proxy构造函数,用来生成Proxy实例 - var proxy = new Proxy(target,handler)
Proxy对象的全部用法,都是上面这种形式,不一样的只是handle参数的写法,其中new Proxy用来生成Proxy实例,target表示所要拦截的对象,handle用来定制拦截行为的对象
Js
中未设置的默认值是undefined
,Proxy
能够改变这种状况
const withZeroValue = (target, zeroValue) => {
new Proxy(target, {
get:(obj,prop) => (prop in obj) ? obj[prop] : zeroValue
})
}
> (obj, prop) => obj → target prop → target每个属性
let pos = {
x: 4,
y: 19
}
pos = withZeroValue(pos, 0)
console.log(pos.x, pos.y, pos.z) // 4 19 0
复制代码
Es 6 提供了新的数据结构Set,它相似于数组,可是成员的值是惟一的,没有重复的值
Set()
接受一个数组(或者具备iterable接口的其余数据结构)做为参数,用来初始化
const arr = new Set([1,2,3,4,4])
console.log([...set]) // Array(4) [1, 2, 3, 4] [...xxx]转化为Array
const items = new Set([1,2,3,4,5,5,5,5])
console.log(items) // Set(5) {1, 2, 3, 4, 5}
复制代码
经典面试考题:一行代码实现数组去重(字符串去重重复字符)
- 去除数组重复成员的方法
[...new Set(array)]
- 去除字符串的重复字符
[...new Set('abbbbc')].join('') // 'abc' Set → 数组 → 字符串
复制代码
❗ 小知识:
向Set加入值的时候,不会发生类型转换,因此5和'5'是两个不一样的值。Set内部判断两个值是否不一样,使用的算法成为
Same-value-zero equality
,它相似于精确相等运算符(===),主要区别是向Set加入值时认为 NaN 等于自身,而 === 认为 NaN 不等于自身
let set = new Set()
let a = NaN
let b = NaN
set.add(a)
set.add(b)
console.log(set) // Set(1) { NaN }
复制代码
同时,Set中两个对象老是不相同的
let set = new Set()
set.add({})
set.add({})
console.log(set) // Set(2) { {}, {} }
复制代码
Set.prototype.constructor
- 构造函数,默认就是Set
函数Set.prototype.size
- 返回Set
实例的成员总数Set.prototype.add(value)
- 添加某个值,返回Set结构自己Set.prototype.delete(value)
- 删除某个值,返回一个布尔值Set.prototype.has(value)
- 返回一个布尔值,表示该值是否为Set成员Set.prototype.clear()
- 清除全部成员,没有返回值Set.prototype.keys()
- 返回键名的遍历器Set.prototype.values()
- 返回键值的遍历器Set.prototype.entries()
- 返回键值对的遍历器Set.prototype.forEach()
- 使用回调函数遍历每一个成员**❗ Ps:**因为keys()、values()、entries()
返回的都是遍历器对象,且Set
结构没有键名,只有键值,因此keys()
,values()
效果相同,但entries()
会输出带有2个相同元素的数组
let set = new Set(['red','green','blue'])
for(let item of set.keys()){
console.log(item)
}
// red
// green
// blue
for(let item of set.values()){
console.log(item)
}
// red
// green
// blue
for(let item of set.entries()){
console.log(item)
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
复制代码
Map 是 Es6 提供的一种新的数据结构,它相似于对象,也是键值对的集合,可是'键'的范围不限于字符串,各类类型的值(包括对象)均可以看成键,即 Map 提供了一种
值 - 值
的对应,是一种更完善的Hash
结构实现
做为构造函数,Map
能够接受一个数组做为参数,该数组的成员是一个个表示键值对的数组
const map = new Map([
['name','张三'],
['title','Author']
])
map.size // 2
map.has('name') // true
map.get('name') // '张三'
map.has('title') // true
map.get('title') // 'Author'
复制代码
❗ 小知识:
事实上,不只仅是数组,任何具备
Iterable
接口,且每一个成员都是一个双元素数组的数据结构,均可以看成 Map 构造函数的参数,即 Set 和 Map 均可以用来生成新的 Map
Map.prototype.size
- 返回Map
实例的成员总数Map.prototype.set(key,value)
- 设置键名Key对应的键值Value,而后返回整个Map结构,若是Key已经存在,则键值更新Map.prototype.get(key)
- 读取Key对应的键值,若是找不到则返回undefinedMap.prototype.has(key)
- 返回一个布尔值,表示某个键是否在当前Map对象中存在Map.prototype.delete(key)
- 删除某个键,返回true,若是删除失败,则返回falseMap.prototype.clear()
- 清除全部成员,没有返回值Map.prototype.keys()
- 返回键名的遍历器Map.prototype.values()
- 返回键值的遍历器Map.prototype.entries()
- 返回键值对的遍历器Map.prototype.forEach()
- 使用回调函数遍历每一个成员展开运算符(spread) 是三个点(...),能够将一个数组转为用逗号分隔的参数序列
基本用法:拆解字符串与数组
var arr = [1,2,3,4]
console.log(...arr) // 1 2 3 4
var str = 'String'
console.log(...str) // S t r i n g
复制代码
展开运算符的应用
在使用Math.max()求数组最大值时,Es5能够经过apply作到(不友好且繁琐)
var array = [1,2,3,4,3]
var maxItem = Math.max.apply(null,array)
console.log(maxItem)
在Es6中,展开运算符可用于数组的解析,优雅的解决了这个问题
var array = [1,2,3,4,3]
var maxItem = Math.max(...array)
console.log(maxItem) // 4
复制代码
push
、concat
等方法- 把 arr2 塞进 arr1 中
// Es5
var arr1 = [0,1,2]
var arr2 = [3,4,5]
Array.prototype.push.apply(arr1,arr2)
// arr1 → [0,1,2,3,4,5]
// Es6
var arr1 = [0,1,2]
var arr2 = [3,4,5]
arr1.push(...arr2)
// arr1 → [0,1,2,3,4,5]
复制代码
var arr = [1,2,3]
var copyArr = [...arr]
console.log(copyArr) // [1,2,3]
let obj = {
a: 1
b:{
foo:'foo',
bar:'bar'
}
}
let objCopy = { ...obj }
console.log(objCopy) // {a:1,b:{foo:'foo',bar:'bar'}}
obj.a = 2
console.log(objCopy.a) // 1
obj.b.foo = 'FOO'
console.log(objCopy.b) // {foo:'FOO',bar:'bar'}
展开运算符...来实现拷贝属于浅拷贝,若是属性值是一个对象,拷贝的是地址
复制代码
var nodeList = document.querySelectorAll('div')
// querySelectorAll 方法返回的是一个 nodeList 对象。它不是数组,而是一个相似数组的对象。
console.log([...nodeList]) // [div,div,div,...]
复制代码
rest运算符(剩余运算符) 看起来与展开运算符同样,可是它是用于解构数组和对象。在某种程度上,剩余元素和展开元素相反,展开元素会'展开'数组变成多个元素,剩余元素会收集多个元素和'压缩'成一个单一的元素
rest能够看做是扩展运算符的一个逆运算,它是一个数组
rest参数用于获取函数的多余参数,这样就不须要使用arguments
对象了。rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中
例如实现计算传入全部参数的和
使用rest参数:
function sumRest(...m) {
var total = 0
for(var i of m){
total += i
}
return total
}
console.log(sumRest(1,2,3)) // 6
复制代码
rest运算符的应用
rest
参数代替arguments
变量// arguments写法
function sortNumbers(){
return Array.prototype.slice.call(arguments).sort()
}
// Es6 rest参数写法
const sortNumbers = (...numbers) => {
numbers.sort()
}
复制代码
var array = [1,2,3,4,5,6]
var [a,b,...c] = array
console.log(a) // 1
console.log(b) // 2
console.log(c) // [3,4,5,6]
复制代码
❗ 小知识:
rest参数可理解为剩余的参数,因此必须在最后一位定义,若是定义在中间会报错。
var array = [1,2,3,4,5,6];
var [a,b,...c,d,e] = array;
// Uncaught SyntaxError: Rest element must be last element
复制代码
const promise = new Promise((resovle,reject) => {
console.log(1)
resolve()
conosole.log(2)
})
promise.then(() => {
console.log(3)
})
console.log(4)
复制代码
Result:
1
2
4
3
复制代码
const promise1 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('success')
}, 1000)
})
const promise2 = new Promise((resolve,reject) => {
throw new Error('error!!!')
})
console.log('promise1', promise1)
console.log('promise2', promise2)
setTimeout(() => {
console.log('promise1', promise1)
console.log('promise2', promise2)
}, 2000)
复制代码
Result:
promise1 Promise { <pending> }
promise2 Promise { <pending> }
Uncaught (in promise) Error: error!!!
promise1 Promise { <resolved>: "success" }
promise2 Promise { <rejected>: Error: error!!! }
复制代码
解析:
promise有三种状态:pending,fulfilled或rejected,状态改变只能是 pending → fulfilled
或 pending → rejected
,状态一旦改变则不能再变。上面 promise2并非promise1,而是返回的一个新的 Promise 实例
const promise = new Promise((resolve,reject) => {
resolve('success1')
reject('error')
resolve('success2')
})
promise.then(res => {
console.log('then:', res)
}).catch(err => {
console.log('catch:', err)
})
复制代码
Result:
then:success1
复制代码
解析:
Promise的resolve
或reject
只有第一次执行有效,屡次调用没有任何做用(Promise状态一旦改变则不能再变)
Promise.resolve(1).then( res => {
console.log(res)
return 2
}).catch( err => {
return 3
}).then( res => {
console.log(res)
})
复制代码
Result:
1
2
复制代码
解析:
promise每次调用.then
或者.catch
都返回一个新的Promise,从而实现链式调用
const promise = new Promise((resolve,reject) => {
setTimeout(() => {
console.log('once')
resolve('success')
},1000)
})
const start = Date.now()
promise.then(res => {
console.log(res, Date.now() - start)
})
promise.then(res => {
console.log(res, Date.now() - start)
})
复制代码
Result:
once
success 1001
success 1002
复制代码
解析:
Promise的then
和catch
均可以被屡次调用,这里promise实例状态一旦改变,而且有了一个值,那么后续每次调用promise.then
或者promise.catch
都会拿到这个值
Promise.resolve().then(() => {
return new Error('error!')
}).then( res => {
console.log('then:',res)
}).catch( err => {
console.log('catch:', err)
})
复制代码
Result:
then: Error: error!
复制代码
解析:
.then
或者.catch
中return一个error对象并不会抛出错误,因此不会被后续的.catch
捕获,而是进行.then
,须要改为如下方式才会被.catch
捕获
1 - return Promise.reject(new Error('error!'))
2 - throw new Error('error!')
复制代码
由于返回任意一个非Promise的值都会被包裹成Promise对象,即return new Error('error!')
等于return Promise.resolve(new Error('error!')
const promise = Promise.resolve().then( () => {
return promise
})
promise.catch(console.error)
复制代码
Result:
TypeError: Chaining cycle detected for promise #<Promise>
复制代码
解析:
.then
或.catch
返回的值不能是promise自己,不然会形成死循环
Promise.resolve(1).then(2).then(Promise.resolve(3)).then(console.log)
复制代码
Result:
1
复制代码
解析:
.then
或.catch
的参数指望是函数,当传入的是非函数则会发生值穿透
Promise.resolve(1).then(function success(res){
console.log('success',res)
throw new Error('error')
},function fail1(err){
console.log('fail1',err)
}).catch(function fail2(err){
console.log('fail2',err)
})
复制代码
Result:
success 1
fail2 Error: error
at success (...)
复制代码
解析:
.then
能够接受两个参数,第一个是处理成功的参数,第二个是处理错误的函数,.catch
其实是.then
第二个参数的简便写法,可是用法上有一点须要注意:
.then
的第二个处理错误的函数捕获不了第一个处理成功的函数抛出的错误,然后续的.catch
能够捕获以前的错误Promise.resolve().then(function success1(res){
throw new Error('error')
},function fail1(err){
console.log('fail1',err)
}).then(function success2 (res) {}, function fail2 (err) {
console.error('fail2: ', err)
})
复制代码
process.nextTick(() => {
console.log('nextTick')
})
Promise.resolve().then( () => {
console.log('then')
})
setImmediate(() => {
console.log('setImmediate')
})
console.log('end')
复制代码
Result:
end
nextTick
then
setImmediate
复制代码
解析:
process.nextTick
、promise.then
均属于微任务microtask
,而setImmediate
属于宏任务macrotask
,仔事件循环EventLoop
中,每一个宏任务执行完后都会清空当前全部微任务,再进行下一个宏任务
Es 6
相关的手写题,会与JavaScript
一块儿总结,集中成一篇Vue.Js
知识点