笔试
大题
1.原型继承是如何实现的?
function People() {
this.type = 'prople'
}
People.prototype.eat = function () {
console.log('吃东西啦');
}
function Man(name) {
this.name = name;
this.color = 'black';
}
// 方法:原型继承
// 缺点:原型是所有子类实例共享的,改变一个其他也会改变。
Man.prototype = new People()
Man继承People的属性和方法,之后由Man生成的实例可以访问到Man原型对象Man.prototype上的方法,而Man.prototype = new People()这个过程又让Man.prototype可以访问到People上的属性和方法。
这里面试官可能看完这道题之后随即提出new的过程是什么样子的? new的过程一共有几步来实现的? 答:4步
function myNew(fn,...args){
// 1.创建一个空对象
let obj = {}
// 2.构建起这个对象和构造函数的原型链
obj._proto_ = fn.prototypy
// 3.将构造函数的this指向这个新对象
let res = fn.apply(obj,args)
// 4.根据返回值判断 如果构造函数有返回值就返回res 若没有返回值就返回这个对象
return res instanceof Object ? res : obj
}
2.解释一下forEach和map的区别,以及各自的应用场景?
共同点:
- 都是循环遍历数组中的每一项
- 回调函数都是传入三个参数:数组中的当前项item,当前项的索引index,原始数组input。
- 匿名函数中的this都是指window
- 只能遍历数组
不同点:
- map有返回值,返回的是一个数组,相当于把数组中的每一项都执行一下回调函数然后生成新值,最后返回一个新的数组。
- forEach没有返回值,返回的是undefined。
使用场景:一般在会在打印每个数组的元素时或者由数组中每个元素来进行操作和判断时,会使用到forEach,而map一般使用在对数组做一个映射,由原数组生成一个新数组的时候再使用。
3.promise解决的回调函数的什么问题,promise的优缺点是什么?
promise是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和更加强大,解决了回调函数产生的回调地狱的问题。
pending(进行中)fulfilled(已成功)rejected(已失败)
优点
- 可以进行链式调用,编码更简单,代码变得扁平可读。
- 更好的进行错误捕获:如果使用 promise 的话,通过 reject 方法把 Promise 的状态置为 rejected,这样我们在 then 中就能捕捉到,然后执行“失败”情况的回调。
- 对象的状态不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态
- 一旦状态改变(从
pending变为fulfilled和从pending变为rejected),就不会再变,任何时候都可以得到这个结果
缺点
1、无法取消Promise,一旦新建它就会立即执行,无法中途取消。
2、如果不设置回调函数,promise内部抛出的错误,不会反应到外部。
3、当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
4.this是什么?在不同的情况下this指向的是什么?
this 关键字是函数运行时自动生成的一个内部对象,只能在函数内部使用,总指向调用它的对象。同时,this在函数执行过程中,this一旦被确定了,就不可以再更改。
this的绑定规则
默认绑定
全局环境中定义person函数,内部使用this关键字。
var name = 'Jenny';
function person() {
return this.name;
}
console.log(person()); //Jenny
这里的this指向的是window,当在严格模式下,不能将全局对象用于默认绑定,this会绑定到undefined,只有函数运行在非严格模式下,默认绑定才能绑定到全局对象。
隐式绑定
函数还可以作为某个对象的方法调用,这时this就指这个上级对象。
function test() {
console.log(this.x);
}
var obj = {};
obj.x = 1;
obj.m = test;
obj.m(); // 1
若函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象。
var o = {
a:10,
b:{
fn:function(){
console.log(this.a); //undefined
}
}
}
o.b.fn();
上述代码中,this的上一级对象为b,b内部并没有a变量的定义,所以输出undefined。
这里再举一种特殊情况
var o = {
a:10,
b:{
a:12,
fn:function(){
console.log(this.a); //undefined
console.log(this); //window
}
}
}
var j = o.b.fn;
j();
此时this指向的是window,这里的大家需要记住,this永远指向的是最后调用它的对象,虽然fn是对象b的方法,但是fn赋值给j时候并没有执行,所以最终指向window。
new绑定
通过构建函数new关键字生成一个实例对象,此时this指向这个实例对象。
function test() {
this.x = 1;
}
var obj = new test();
obj.x // 1
若new过程遇到return一个对象,此时this指向为返回的对象。
function fn()
{
this.user = 'xxx';
return {};
}
var a = new fn();
console.log(a.user); //undefined
如果返回一个简单类型或者null的时候,则this指向实例对象。
function fn()
{
this.user = 'xxx';
return 1;
//return null
}
var a = new fn;
console.log(a.user); //xxx
显示修改
apply()、call()、bind()是函数的一个方法,作用是改变函数的调用对象。它的第一个参数就表示改变后的调用这个函数的对象。因此,这时this指的就是这第一个参数。、
箭头函数中的this指向
箭头函数定义位置所在的作用域的this(函数作用域)是谁,箭头函数的this就指向谁
const obj = {
sayThis: () => {
console.log(this);
}
};
obj.sayThis(); // window 因为 JavaScript 没有块作用域,所以在定义 sayThis 的时候,里面的 this 就绑到 window 上去了
const globalSay = obj.sayThis;
globalSay(); // window 浏览器中的 global 对象
5.写一个将json数据转化为json树的函数?
// 转换前:
source = [{
id: 1,
pid: 0,
name: 'body'
}, {
id: 2,
pid: 1,
name: 'title'
}, {
id: 3,
pid: 2,
name: 'div'
}]
// 转换为:
tree = [{
id: 1,
pid: 0,
name: 'body',
children: [{
id: 2,
pid: 1,
name: 'title',
children: [{
id: 3,
pid: 1,
name: 'div'
}]
}]
}]
function jsonToTree2(data) {
// 初始化结果数组,并判断输入数据的格式
let result = []
if (!Array.isArray(data)) {
return result
}
// 使用map,将当前对象的id与当前对象对应存储起来
let map = new Map()
data.forEach((item)=>{
map.set(item.id,item)
})
data.forEach((item)=>{
// 取出当前item的父节点
let parent = map.get(item.pid)
if(parent){
// parent存在,就将当前的item存入children数组中
if(parent.children){
parent.children.push(item)
}else {
parent.children = []
parent.children.push(item)
}
}else {
result.push(item);
}
})
return result;
}