This website requires JavaScript.

一些js的面试题

2018.04.11 17:55字数 6067阅读 325喜欢 0评论 0

题目我也只是挑一些自己觉得比较经典的,分享给大家!

1、写一个函数fn(10),获取0-100以内的随机数传入一个数组,数组内不能重复出现想同的数字,数组长度为10(下面写的代码进行了一部分改造原题);

function getRandomNum(num,[min,max]){
    let arr = [];
    if(typeof num === "number" && typeof min === "number" && typeof max === "number"){
        // console.log(num,min,max);
        while(true){
            let isExit = false;
            let randomNum = parseInt(min + (max-min) * Math.random());
            //方法一:
            // for(let i = 0;i<arr.length;i++){
            //     if(arr[i]===randomNum){
            //         console.log(arr[i]);
            //         isExit = true;
            //         break;
            //     }
            // }

            //方法二:
            let set = new Set(arr)
            if(!set.has(randomNum)){
                arr.push(randomNum);
            }


            if(arr.length === num) break;
        }
    }else{
        throw Error("请传入Number类型!")
    }
    return arr;
}

let randomArr = getRandomNum(10,[10,100]);
console.log(randomArr);

2、给定这么一个数据:

let data = [{
    id:1,
    name:"北京"
},{
    id:1,
    name:"天津",
    children:[{
        id:1,
        name:"西青"
    },{
        id:1,
        name:"和平",
        children:[{
            id:1,
            name:"和平一"
        },{
            id:1,
            name:"和平二"
        }]
    }]
}]

写一个函数将数据内的key值name和children相应的替换成lable和city:

function dealData(arr){
   if(arr instanceof Array){
       let dealedArr = JSON.parse(JSON.stringify(arr));
       let new_arr = changeKey(dealedArr);
       return new_arr;
   }else{
       throw Error("请传入数组!")
   }
}

function changeKey(arr){
   let new_arr = arr.map((item,index)=>{
       if(item.hasOwnProperty("name")){
           item["lable"]=item["name"];
           delete item.name;
       }
       if(item.children && item.children.length > 0){
           item['city'] = changeKey(item.children);
           delete item.children;
       }
       return item;
   })
   return new_arr;
}
let arr = dealData(data);
console.log(arr);

3、给一个数组如下
const tree = [ '1', ['2', '3'], [4, ["5",6]]];
需要改造成
let new_tree = ["1", "2", "3", 4, "5", 6];
这道题面试的时候,一眼看过去真的会懵逼,反正我是懵逼了。不过只要懂一些iterator的知识,其实并不难;

const tree = [ '1', ['2', '3'], [4, ["5",6]]];

function* iteratorTree(tree){
    if(typeof tree === "string" || typeof tree === "number"){
        yield tree;
    }else if(tree instanceof Array){
        for (const i of tree) {
            yield* iteratorTree(i)
        }
    }
}


// for(const i of iteratorTree(tree)){
//     console.log(i);
// }
console.log([...iteratorTree(tree)]);

1、说一说promise的实现原理
so,我也是比较懵逼的,只是会用而已,恶补了下这篇 文章

2、js类型检测的各种方法及其对比
转移现场,看看这篇 文章

3、vue双向绑定的基本原理
这个题我估计只要考到vue,基本都会考到吧!

vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的settergetter,在数据变动时发布消息给订阅者,触发相应的监听回调。

具体步骤:
第一步:需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter和getter,这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化

第二步:compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图

第三步:Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:
1、在自身实例化时往属性订阅器(dep)里面添加自己
2、自身必须有一个update()方法
3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。

第四步:MVVM作为数据绑定的入口,整合ObserverCompileWatcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。

这种题一般考到之后,先问你了解发布者订阅者模式(观察者模式)的实现,再问你是否了解其他的js的设计模式;

其实,只要我们曾经在DOM节点上绑定过事件函数,那我们曾经就使用过发布-订阅模式;比如click事件,给某个dom节点添加click事件,其实就是订阅了这个dom节点的click事件,当这个dom被点击的时候,就会像订阅者click事件发布这个消息。

发布订阅者模式我用得也很少,用得比较多的也就是单例模式和代理模式,遇到这种情况的了解哪一种说哪一种吧,别瞎比比就行。毕竟面试要求你什么都会,而实际工作需要用什么。js设计模式建议大家还是多看看,毕竟前人总结出来的好东西,不用一用,你还好意思说自己是站在巨人的肩膀上么?

3、vue生命周期钩子的理解(vue2.0+)
beforeCreate:在实例初始化之后,数据观测(data observer)和event/watcher事件配置之前被调用。

created :实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算,watch/event事件回调。然而,挂载阶段还没开始,$el属性目前不可见。

beforeMount:在挂载开始之前被调用:相关的 render 函数首次被调用,也就是说,render函数此时会生成虚拟的dom节点,即vm.$el;

mounted:el被新创建的vm.$el替换,并挂载到实例上去之后调用该钩子。如果root实例挂载了一个文档内元素,当mounted被调用时vm.$el也在文档内。

beforeUpdate:数据更新时调用,发生在虚拟DOM重新渲染和打补丁之前。 你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。

updated:由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件DOM已经更新,所以你现在可以执行依赖于DOM的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。

beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。

destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。该钩子在服务器端渲染期间不被调用。

4、http状态码有哪些?
大概说说就行,这种状态码,其实工作中遇到了直接就给后台说一声就行,反正我从来不记,面试的时候,大致看看记了一下

100-199 用于指定客户端应相应的某些动作。  
200-299 用于表示请求成功。  
300-399 用于已经移动的文件并且常被包含在定位头信息中指定新的地址信息。  
400-499 用于指出客户端的错误。400 1、语义有误,当前请求无法被服务器理解。  
401 当前请求需要用户验证 403 服务器已经理解请求,但是拒绝执行它。  
500-599 用于支持服务器错误。 503 – 服务不可用  

5、谈一谈let
与var的不同在于,用let声明的变量只在 { } 内有效。这使得我们可以很方便的实现块级作用域,不再使用立即实行函数。

{  
  let a=1;  
  var b=2;  
}  
console.log(a); //undefined  console.log(b); //2

let不会变量提升。也就是说,如果你使用var ,可以先使用变量再定义变量(注意:变量提升只提升声明不提升赋值操作),但是如果你使用let定义变量则必须先定义后使用,否则会报错。

console.log(a);//报错:Uncaught ReferenceError: a is not defined  console.log(b); //undefined  let a=1;  
var b=2;

暂时性死区(Temporal Dead Zone),只要块级作用域内有let,let 声明的变量不受全局同名变量的影响,如果想要在块级作用域内使用let 声明的变量,只能为其赋值。

var a=1;  
if(true){  
  a=2;  
  let a;
   //Uncaught ReferenceError: a is not defined
  }

不允许在一个块级作用域内重复声明一个变量

不管是var与let重复声明,还是let与let重复声明,都会报错。

为了方便大家看,把原文复制了一遍给大家看,懒得去自己敲栗子了!

6、promise的一个题

console.log('1');
    setTimeout(function() {
      console.log('2');
    }, 0);
    Promise.resolve().then(function() {
      console.log('3');
    }).then(function() {
      console.log('4');
    });
    console.log('5');  

// 顺序是:15342  

console.log(1)

setTimeout(() => {
    console.log(2)
}, 0)

new Promise(resolve => {
    console.log(3)
    resolve()
}).then(() => {
    console.log(4)
})

console.log(5)
// 顺序是:13542

这个题也比较简单,注意一个是有new关键字的,另一个是直接在then方法中输出的,小心这个陷阱就OK了!