ES6高频面试题

2020-04-23  本文已影响0人  小龙虾Julian
一、箭头函数需要注意的地方

当要求动态上下文的时候,就不能够使用箭头函数,也就是this的固定化
1、在使用 => 定义函数时,this的指向是定义时所在的对象,而不是使用时所在的对象
2、不能用作构造函数,也就是不能使用new命令,否则会抛出一个错误
3、不能使用arguments对象
4、不能使用yeild命令

class Animal {
    constructor() {
        this.type = "animal";
    }
    say(val) {
        setTimeout(function () {
            console.log(this);     //window
            console.log(this.type + " says " + val);
        }, 1000)
    }
}
var animal = new Animal();
animal.say("hi");     //undefined says hi

解析:《JavaScript高级程序设计》第二版中指出:“超时调用的代码是在全局作用域中执行的,因此函数中的this的值在非严格模式下指向window对象,在严格模式下是undefined”,也就是说非严格模式下,setTimeout中所执行的函数中的this永远指向window

class Animal {
    constructor() {
        this.type = "animal";
    }
    say(val) {
        setTimeout(() => {
            console.log(this);     //Animal
            console.log(this.type + ' says ' + val);
        }, 1000)
    }
}
var animal = new Animal();
animal.say("hi");     //animal says hi

箭头函数的特点:不需要使用function关键字来创建函数;省略return关键字;继承当前上下文中的this关键字

二、let和const

let是更完美的var,不是全局变量,具有块级函数作用域,大多数情况不会发生变量提升;const定义常量,不能重新赋值,如果值是一个对象,可以改变对象里的值。
1、let声明的变量具有块级作用域
2、let声明的变量不能通过window.变量名进行访问
3、形如for(let x ...)的循环是每次迭代都为 x 创建新的绑定

var arr = [];
for (var i = 0; i < 10; i++) {
    arr[i] = function () {
        console.log(i);
    }
}
arr[5]() //10,a[5]输出f(){console.log(i);},后面加个括号代表执行f()

解析:变量i是var声明的,在全局范围类都有效,所以用来计数的循环变量泄露为全局变量。所以每一次循环,新的i值都会覆盖旧值,导致最后输出都是10。

var arr = [];
for (let i = 0; i < 10; i++) {
    arr[i] = function () {
        console.log(i);
    }
}
arr[5]() //10,a[5]输出f(){console.log(i);},后面加个括号代表执行f()

解析:变量 i 是let声明的,每次迭代都是为x创建新的绑定

除了使用let定义变量,还可以使用闭包和立即执行函数

function showNum(i) {
    return function () {
        console.log(i)
    }
}
var a = []
for (var i = 0; i < 5; i++) {
    a[i] = showNum(i)(); //循环输出1,2,3,4
}
var a = []
for (var i = 0; i < 5; i++) {
    a[i] = (function (i) {
        return function () {
            console.log(i)
        }
    })(i)
}
a[2](); //2

把以下代码使用两种方法依次输出0——9

var funcs = []
for (var i = 0; i < 10; i++) {
    funcs.push(function () {
        console.log(i)
    })
}
funcs.forEach(function (func) {
    func(); //输出十个10
})

方法一:立即执行函数

var funcs = []
for (var i = 0; i < 10; i++) {
    funcs.push((function (value) {
        return function () {
            console.log(value)
        }
    }(i)))
}
funcs.forEach(function (func) {
    func(); //依次输出0-9
})

方法二:使用闭包

function show(i) {
    return function () {
        console.log(i)
    }
}
var funcs = []
for (var i = 0; i < 10; i++) {
    funcs.push(show(i))
}
funcs.forEach(function (func) {
    func(); //0 1 2 3 4 5 6 7 8 9
})

方法三:使用let

var funcs = []
for (let i = 0; i < 10; i++) {
    funcs.push(function () {
        console.log(i)
    })
}
funcs.forEach(function (func) {
    func(); //依次输出0-9
})
三、Set数据结构:Set本身是一个构造函数,它类似于数组,但是成员值都是唯一的
const set = new Set([1,2,3,4,4])
console.log([...set] )     // [1,2,3,4]
console.log(Array.from(new Set([2,3,3,5,6])));      //[2,3,5,6]
四、class:相对原型、构造函数、继承class更接近传统语法,写法能够让对象原型的写法更加清晰、面向对象编程的语法更加通俗。
class Animal {
    constructor() {
        this.type = 'animal'
    }
    says(say) {
        console.log(this.type + ' says ' + say)
    }
}
let animal = new Animal()
animal.says('hello') // animal says hello

class Cat extends Animal {
    constructor() {
        super()
        this.type = 'cat'
    }
}
let cat = new Cat()
cat.says('hello') // cat says hell

解析:可以看出在使用extend的时候结构输出是cat says hello 而不是animal says hello。说明contructor内部定义的方法和属性是实例对象自己的,不能通过extends 进行继承。在class cat中出现了super(),这是什么呢?因为在ES6中,子类的构造函数必须含有super函数,super表示的是调用父类的构造函数,虽然是父类的构造函数,但是this指向的却是cat。

五、模板字符串

形如下边这种形式的就是模板字符串

${varible}

以往我们在连接字符串和变量的时候需要使用这种方式string + varible + string,但是有了模版语言后我们可以使用这种模板字符串进行连接,如:

string ${varible} string

1、基本的字符串格式化,将表达式嵌入字符串中进行拼接,用${}来界定

//ES5 
var name = 'lux';
console.log('hello' + name);      //hello lux
//ES6
const name = 'lux';
console.log(`hello ${name}`);      //hello lux

2、ES5时我们通过反斜杠()来做多行字符串或者字符串一行行拼接,ES6反引号(``)直接搞定

//ES5
var template = "hello \
world";
console.log(template);      //hello world

//ES6
const template = `hello
world`;
console.log(template);      //hello 空行 world

3、字符串其他用法

// 1.includes:判断是否包含然后直接返回布尔值
let str = 'hello'
console.log(str.includes('e'))      // true

// 2.repeat: 获取字符串重复n次
let s = 'julian'
console.log(s.repeat(3))      // 'julianjulianjulian'
六、Promise

1、面试问题一

var promise = new Promise((resolve, reject) => {
    if (true) {
        resolve(value)
    } else {
        reject(error)
    }
})
promise.then(function (value) {
    // success
    }, function (value) {
    // failure
})

2、面试问题二

setTimeout(function () {
    console.log(1)
}, 0);

new Promise(function executor(resolve) {
    console.log(2);

    for (var i = 0; i < 10000; i++) {
        i == 9999 && resolve();
    }
    
    console.log(3);
    
    }).then(function(){
        console.log(4);
});
console.log(5);

结果: 2、3、5、4、1
解析:首先先碰到一个 setTimeout,于是会先设置一个定时,在定时结束后将传递这个函数放到任务队列里面,因此开始肯定不会输出1。然后是一个 Promise,里面的函数是直接执行的,因此应该直接输出 2 3。然后,Promise 的 then 应当会放到当前 tick 的最后,但是还是在当前 tick 中。 因此,应当先输出 5,然后再输出 4 , 最后在到下一个 tick,就是 1 。

3、面试问题三
jQuery的ajax返回的是promise对象吗?
jquery的ajax返回的是deferred对象,通过promise的resolve()方法将其转换为promise对象。

var jsPromise = Promise.resolve($.ajax('/whatever.json'));

4、面试问题四
promise只有2个状态,成功和失败,怎么让一个函数无论成功还是失败都能被调用?
使用promise.all():Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。
Promise.all方法接受一个数组作为参数,数组里的元素都是Promise对象的实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例。)
示例:
var p = Promise.all([p1,p2,p3]);
p的状态由p1、p2、p3决定,分为两种情况。
当该数组里的所有Promise实例都进入Fulfilled状态:Promise.all返回的实例才会变成Fulfilled状态。并将Promise实例数组的所有返回值组成一个数组,传递给Promise.all返回实例的回调函数
当该数组里的某个Promise实例都进入Rejected状态:Promise.all返回的实例会立即变成Rejected状态。并将第一个rejected的实例返回值传递给Promise.all返回实例的回调函数。

5、面试问题五

const promise = new Promise((resolve, reject) => {
    console.log(1)
    resolve()
    console.log(2)
})
promise.then(() => {
    console.log(3)
})
console.log(4)

结果:1、2、4、3
解析:Promise 构造函数是同步执行的,promise.then 中的函数是异步执行的。

6、面试问题六

const promise1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('success')
    }, 1000)
})
const promise2 = promise1.then(() => {
    throw new Error('error!!!')
})

console.log('promise1', promise1)
console.log('promise2', promise2)

setTimeout(() => {
    console.log('promise1', promise1)
    console.log('promise2', promise2)
}, 2000)
运行结果.png

解析:promise 有 3 种状态:pending(进行中)、fulfilled(已完成,又称为Resolved) 或 rejected(已失败)。状态改变只能是 pending->fulfilled 或者 pending->rejected,状态一旦改变则不能再变。上面 promise2 并不是 promise1,而是返回的一个新的 Promise 实例

7、面试问题七

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)
})

结果:then: success1
解析:构造函数中的 resolve 或 reject 只有第一次执行有效,多次调用没有任何作用,呼应代码二结论:promise 状态一旦改变则不能再变

8、面试问题八

Promise.resolve(1).then((res) => {
    console.log(res)
    return 2
}).catch((err) => {
    return 3
}).then((res) => {
    console.log(res)
})

结果:1 2
解析:promise 可以链式调用。提起链式调用我们通常会想到通过 return this 实现,不过 Promise 并不是这样实现的。promise 每次调用 .then 或者 .catch 都会返回一个新的 promise,从而实现了链式调用

9、面试问题九

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)
})

结果:once
success 1001
success 1002
解析:promise 的 .then 或者 .catch 可以被调用多次,但这里 Promise 构造函数只执行一次。或者说 promise 内部状态一经改变,并且有了一个值,那么后续每次调用 .then 或者 .catch 都会直接拿到该值

10、面试问题十

Promise.resolve().then(() => {
    return new Error('error!!!')
}).then((res) => {
    console.log('then: ', res)
}).catch((err) => {
    console.log('catch: ', err)
})

结果:then: Error: error!!!
at meHFL3s:11
解析:.then 或者 .catch 中 return 一个 error 对象并不会抛出错误,所以不会被后续的 .catch 捕获,需要改成其中一种:
return Promise.reject(new Error('error!!!'))
throw new Error('error!!!')
因为返回任意一个非 promise 的值都会被包裹成 promise 对象,即 return new Error('error!!!') 等价于 return Promise.resolve(new Error('error!!!'))。

11、面试问题十一

const promise = Promise.resolve()
    .then(() => {
        return promise
    })
promise.catch(console.error)

结果:TypeError: Chaining cycle detected for promise #<Promise>
解析:.then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环。

12、面试问题十二

Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)

结果:1
解析:.then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透

13、面试问题十三

Promise.resolve()
.then(function success (res) {
    throw new Error('error')
}, function fail1 (e) {
    console.error('fail1: ', e)
})
.catch(function fail2 (e) {
    console.error('fail2: ', e)
})

结果:fail2: Error: error
at success (Hf0wdrQ:12)
解析:.then 可以接收两个参数,第一个是处理成功的函数,第二个是处理错误的函数。.catch 是 .then 第二个参数的简便写法,但是它们用法上有一点需要注意:.then 的第二个处理错误的函数捕获不了第一个处理成功的函数抛出的错误,而后续的 .catch 可以捕获之前的错误

14、面试问题十四

process.nextTick(() => {
    console.log('nextTick')
})
Promise.resolve()
.then(() => {
    console.log('then')
})
setImmediate(() => {
    console.log('setImmediate')
})
console.log('end')

结果:end nextTick then setImmediate
解析:process.nextTick 和 promise.then 都属于 microtask,而 setImmediate 属于 macrotask,在事件循环的 check 阶段执行。事件循环的每个阶段(macrotask)之间都会执行 microtask,事件循环的开始会先执行一次 microtask

上一篇下一篇

猜你喜欢

热点阅读