美文网首页
js面试题

js面试题

作者: 刘松阳 | 来源:发表于2020-03-20 18:43 被阅读0次

    1. get请求传参长度的误区

    误区:我们经常说get请求参数的大小存在限制,而post请求的参数大小是无限制的。

    实际上HTTP 协议从未规定 GET/POST 的请求长度限制是多少。对get请求参数的限制是来源与浏览器或web服务器,浏览器或web服务器限制了url的长度。为了明确这个概念,我们必须再次强调下面几点:

    • HTTP 协议 未规定 GET 和POST的长度限制
    • GET的最大长度显示是因为 浏览器和 web服务器限制了 URI的长度
    • 不同的浏览器和WEB服务器,限制的最大长度不一样
    • 要支持IE,则最大长度为2083byte,若只支持Chrome,则最大长度 8182byte

    2. 补充get和post请求在缓存方面的区别

    post/get的请求区别,具体不再赘述。

    补充补充一个get和post在缓存方面的区别:

    • get请求类似于查找的过程,用户获取数据,可以不用每次都与数据库连接,所以可以使用缓存。
    • post不同,post做的一般是修改和删除的工作,所以必须与数据库交互,所以不能使用缓存。因此get请求适合于请求缓存。

    3. 闭包

    一句话可以概括:闭包就是能够读取其他函数内部变量的函数,或者子函数在外调用,子函数所在的父函数的作用域不会被释放。

    4. 类的创建和继承

    (1)类的创建(es5):new一个function,在这个function的prototype里面增加属性和方法。

    下面来创建一个Animal类:

    // 定义一个动物类
    function Animal (name) {
      // 属性
      this.name = name || 'Animal';
      // 实例方法
      this.sleep = function(){
        console.log(this.name + '正在睡觉!');
      }
    }
    // 原型方法
    Animal.prototype.eat = function(food) {
      console.log(this.name + '正在吃:' + food);
    };
    
    

    这样就生成了一个Animal类,实力化生成对象后,有方法和属性。

    (2)类的继承——原型链继承

    --原型链继承
    function Cat(){ }
    Cat.prototype = new Animal();
    Cat.prototype.name = 'cat';
    // Test Code
    var cat = new Cat();
    console.log(cat.name);
    console.log(cat.eat('fish'));
    console.log(cat.sleep());
    console.log(cat instanceof Animal); //true 
    console.log(cat instanceof Cat); //true
    
    
    • 介绍:在这里我们可以看到new了一个空对象,这个空对象指向Animal并且Cat.prototype指向了这个空对象,这种就是基于原型链的继承。
    • 特点:基于原型链,既是父类的实例,也是子类的实例
    • 缺点:无法实现多继承

    (3)构造继承:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

    function Cat(name){
      Animal.call(this);
      this.name = name || 'Tom';
    }
    // Test Code
    var cat = new Cat();
    console.log(cat.name);
    console.log(cat.sleep());
    console.log(cat instanceof Animal); // false
    console.log(cat instanceof Cat); // true
    
    
    • 特点:可以实现多继承
    • 缺点:只能继承父类实例的属性和方法,不能继承原型上的属性和方法。

    (4)实例继承和拷贝继承

    实例继承:为父类实例添加新特性,作为子类实例返回

    拷贝继承:拷贝父类元素上的属性和方法

    上述两个实用性不强,不一一举例。

    (5)组合继承:相当于构造继承和原型链继承的组合体。通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

    function Cat(name){
      Animal.call(this);
      this.name = name || 'Tom';
    }
    Cat.prototype = new Animal();
    Cat.prototype.constructor = Cat;
    // Test Code
    var cat = new Cat();
    console.log(cat.name);
    console.log(cat.sleep());
    console.log(cat instanceof Animal); // true
    console.log(cat instanceof Cat); // true
    
    
    • 特点:可以继承实例属性/方法,也可以继承原型属性/方法
    • 缺点:调用了两次父类构造函数,生成了两份实例

    (6)寄生组合继承:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性

    function Cat(name){
      Animal.call(this);
      this.name = name || 'Tom';
    }
    (function(){
      // 创建一个没有实例方法的类
      var Super = function(){};
      Super.prototype = Animal.prototype;
      //将实例作为子类的原型
      Cat.prototype = new Super();
    })();
    // Test Code
    var cat = new Cat();
    console.log(cat.name);
    console.log(cat.sleep());
    console.log(cat instanceof Animal); // true
    console.log(cat instanceof Cat); //true
    
    
    • 较为推荐

    5. 如何解决异步回调地狱

    promise、generator、async/await

    6. 说说前端中的事件流

    HTML中与javascript交互是通过事件驱动来实现的,例如鼠标点击事件onclick、页面的滚动事件onscroll等等,可以向文档或者文档中的元素添加事件侦听器来预订事件。想要知道这些事件是在什么时候进行调用的,就需要了解一下“事件流”的概念。

    什么是事件流:事件流描述的是从页面中接收事件的顺序,DOM2级事件流包括下面几个阶段。

    • 事件捕获阶段
    • 处于目标阶段
    • 事件冒泡阶段

    addEventListeneraddEventListener 是DOM2 级事件新增的指定事件处理程序的操作,这个方法接收3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。

    IE只支持事件冒泡

    7. 如何让事件先冒泡后捕获

    在DOM标准事件模型中,是先捕获后冒泡。但是如果要实现先冒泡后捕获的效果,对于同一个事件,监听捕获和冒泡,分别对应相应的处理函数,监听到捕获事件,先暂缓执行,直到冒泡事件被捕获后再执行捕获之间。

    8. 事件委托

    • 简介:事件委托指的是,不在事件的发生地(直接dom)上设置监听函数,而是在其父元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上事件的触发,通过判断事件发生元素DOM的类型,来做出不同的响应。
    • 举例:最经典的就是ul和li标签的事件监听,比如我们在添加事件时候,采用事件委托机制,不会在li标签上直接添加,而是在ul父元素上添加。
    • 好处:比较合适动态元素的绑定,新添加的子元素也会有监听函数,也可以有事件触发机制。

    9. 图片的懒加载和预加载

    • 预加载:提前加载图片,当用户需要查看时可直接从本地缓存中渲染。
    • 懒加载:懒加载的主要目的是作为服务器前端的优化,减少请求数或延迟请求数。

    两种技术的本质:两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。
    懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。

    10. mouseover和mouseenter的区别

    • mouseover:当鼠标移入元素或其子元素都会触发事件,所以有一个重复触发,冒泡的过程。对应的移除事件是mouseout
    • mouseenter:当鼠标移除元素本身(不包含元素的子元素)会触发事件,也就是不会冒泡,对应的移除事件是mouseleave

    11. js的new操作符做了哪些事情

    new 操作符新建了一个空对象,这个对象原型指向构造函数的prototype,执行构造函数后返回这个对象。

    12.改变函数内部this指针的指向函数(bind,apply,call的区别)

    • 通过apply和call改变函数的this指向,他们两个函数的第一个参数都是一样的表示要改变指向的那个对象,第二个参数,apply是数组,而call则是arg1,arg2...这种形式。
    • 通过bind改变this作用域会返回一个新的函数,这个函数不会马上执行。

    13. js的各种位置,比如clientHeight,scrollHeight,offsetHeight ,以及scrollTop, offsetTop,clientTop的区别?

    • clientHeight:表示的是可视区域的高度,不包含border和滚动条
    • offsetHeight:表示可视区域的高度,包含了border和滚动条
    • scrollHeight:表示了所有区域的高度,包含了因为滚动被隐藏的部分。
    • clientTop:表示边框border的厚度,在未指定的情况下一般为0
    • scrollTop:滚动后被隐藏的高度,获取对象相对于由offsetParent属性指定的父坐标(css定位的元素或body元素)距离顶端的高度。

    14. js拖拽功能的实现

    • 首先是三个事件,分别是mousedown,mousemove,mouseup

    当鼠标点击按下的时候,需要一个tag标识此时已经按下,可以执行mousemove里面的具体方法。

    • clientX,clientY标识的是鼠标的坐标,分别标识横坐标和纵坐标,并且我们用offsetX和offsetY来表示元素的元素的初始坐标,移动的举例应该是:

      鼠标移动时候的坐标-鼠标按下去时候的坐标。

      也就是说定位信息为:

      鼠标移动时候的坐标-鼠标按下去时候的坐标+元素初始情况下的offetLeft.

    • 还有一点也是原理性的东西,也就是拖拽的同时是绝对定位,我们改变的是绝对定位条件下的left
      以及top等等值。

    补充:也可以通过html5的拖放(Drag 和 drop)来实现

    二、进阶javascript篇

    1.自己实现一个bind函数

    原理:通过apply或者call方法来实现。

    (1)初始版本

    Function.prototype.bind=function(obj,arg){
      var arg=Array.prototype.slice.call(arguments,1);
      var context=this;
      return function(newArg){
        arg=arg.concat(Array.prototype.slice.call(newArg));
        return context.apply(obj,arg);
      }
    }
    

    (2) 考虑到原型链

    为什么要考虑?因为在new 一个bind过生成的新函数的时候,必须的条件是要继承原函数的原型

    Function.prototype.bind=function(obj,arg){
      var arg=Array.prototype.slice.call(arguments,1);
      var context=this;
      var bound=function(newArg){
        arg=arg.concat(Array.prototype.slice.call(newArg));
        return context.apply(obj,arg);
      }
      var F=function(){}
      //这里需要一个寄生组合继承
      F.prototype=context.prototype;
      bound.prototype=new F();
      return bound;
    }
    
    

    2.用setTimeout来实现setInterval

    (1)用setTimeout()方法来模拟setInterval()与setInterval()之间的什么区别?

    首先来看setInterval的缺陷,使用setInterval()创建的定时器确保了定时器代码规则地插入队列中。这个问题在于:如果定时器代码在代码再次添加到队列之前还没完成执行,结果就会导致定时器代码连续运行好几次。而之间没有间隔。不过幸运的是:javascript引擎足够聪明,能够避免这个问题。当且仅当没有该定时器的如何代码实例时,才会将定时器代码添加到队列中。这确保了定时器代码加入队列中最小的时间间隔为指定时间。

    这种重复定时器的规则有两个问题:1.某些间隔会被跳过 2.多个定时器的代码执行时间可能会比预期小。

    下面举例子说明:

    假设,某个onclick事件处理程序使用啦setInterval()来设置了一个200ms的重复定时器。如果事件处理程序花了300ms多一点的时间完成。

    <img width="626" alt="2018-07-10 11 36 43" src="https://user-gold-cdn.xitu.io...;h=498&f=png&s=326047">

    这个例子中的第一个定时器是在205ms处添加到队列中,但是要过300ms才能执行。在405ms又添加了一个副本。在一个间隔,605ms处,第一个定时器代码还在执行中,而且队列中已经有了一个定时器实例,结果是605ms的定时器代码不会添加到队列中。结果是在5ms处添加的定时器代码执行结束后,405处的代码立即执行。

    function say(){
      //something
      setTimeout(say,200);
    }
    setTimeout(say,200)
    
    

    或者

    setTimeout(function(){
       //do something
       setTimeout(arguments.callee,200);
    },200);
    
    

    3.js怎么控制一次加载一张图片,加载完后再加载下一张

    (1)方法1

    <script type="text/javascript">
    var obj=new Image();
    obj.src="http://www.phpernote.com/uploadfiles/editor/201107240502201179.jpg";
    obj.onload=function(){
    alert('图片的宽度为:'+obj.width+';图片的高度为:'+obj.height);
    document.getElementById("mypic").innnerHTML="<img src='"+this.src+"' />";
    }
    </script>
    <div id="mypic">onloading……</div>
    
    

    (2)方法2

    <script type="text/javascript">
    var obj=new Image();
    obj.src="http://www.phpernote.com/uploadfiles/editor/201107240502201179.jpg";
    obj.onreadystatechange=function(){
    if(this.readyState=="complete"){
    alert('图片的宽度为:'+obj.width+';图片的高度为:'+obj.height);
    document.getElementById("mypic").innnerHTML="<img src='"+this.src+"' />";
    }
    }
    </script>
    <div id="mypic">onloading……</div>
    
    

    3.代码的执行顺序

    setTimeout(function(){console.log(1)},0);
    new Promise(function(resolve,reject){
       console.log(2);
       resolve();
    }).then(function(){console.log(3)
    }).then(function(){console.log(4)});
    
    process.nextTick(function(){console.log(5)});
    
    console.log(6);
    //输出2,6,5,3,4,1
    
    

    为什么呢?具体请参考我的文章:
    从promise、process.nextTick、setTimeout出发,谈谈Event Loop中的Job queue

    4.如何实现sleep的效果(es5或者es6)

    (1)while循环的方式

    function sleep(ms){
       var start=Date.now(),expire=start+ms;
       while(Date.now()<expire);
       console.log('1111');
       return;
    }
    
    

    执行sleep(1000)之后,休眠了1000ms之后输出了1111。上述循环的方式缺点很明显,容易造成死循环。

    (2)通过promise来实现

    function sleep(ms){
      var temple=new Promise(
      (resolve)=>{
      console.log(111);setTimeout(resolve,ms)
      });
      return temple
    }
    sleep(500).then(function(){
       //console.log(222)
    })
    //先输出了111,延迟500ms后输出222
    
    

    (3)通过async封装

    function sleep(ms){
      return new Promise((resolve)=>setTimeout(resolve,ms));
    }
    async function test(){
      var temple=await sleep(1000);
      console.log(1111)
      return temple
    }
    test();
    //延迟1000ms输出了1111
    
    

    (4).通过generate来实现

    function* sleep(ms){
       yield new Promise(function(resolve,reject){
                 console.log(111);
                 setTimeout(resolve,ms);
            })  
    }
    sleep(500).next().value.then(function(){console.log(2222)})
    
    

    5.简单的实现一个promise

    首先明确什么是promiseA+规范,参考规范的地址:

    primiseA+规范

    如何实现一个promise,参考我的文章:

    实现一个完美符合Promise/A+规范的Promise

    一般不会问的很详细,只要能写出上述文章中的v1.0版本的简单promise即可。

    6.Function.proto(getPrototypeOf)是什么?

    获取一个对象的原型,在chrome中可以通过proto的形式,或者在ES6中可以通过Object.getPrototypeOf的形式。

    那么Function.proto是什么么?也就是说Function由什么对象继承而来,我们来做如下判别。

    Function.__proto__==Object.prototype //false
    Function.__proto__==Function.prototype//true
    
    

    我们发现Function的原型也是Function。

    我们用图可以来明确这个关系:

    <img width="646" alt="2018-07-10 2 38 27" src="https://user-gold-cdn.xitu.io...;h=1028&f=png&s=183106">

    7.实现js中所有对象的深度克隆(包装对象,Date对象,正则对象)

    通过递归可以简单实现对象的深度克隆,但是这种方法不管是ES6还是ES5实现,都有同样的缺陷,就是只能实现特定的object的深度复制(比如数组和函数),不能实现包装对象Number,String , Boolean,以及Date对象,RegExp对象的复制。

    (1)前文的方法

    function deepClone(obj){
        var newObj= obj instanceof Array?[]:{};
        for(var i in obj){
           newObj[i]=typeof obj[i]=='object'?  
           deepClone(obj[i]):obj[i];    
        }
        return newObj;
    }
    
    

    这种方法可以实现一般对象和数组对象的克隆,比如:

    var arr=[1,2,3];
    var newArr=deepClone(arr);
    // newArr->[1,2,3]
    
    var obj={
       x:1,
       y:2
    }
    var newObj=deepClone(obj);
    // newObj={x:1,y:2}
    
    

    但是不能实现例如包装对象Number,String,Boolean,以及正则对象RegExp和Date对象的克隆,比如:

    //Number包装对象
    var num=new Number(1);
    typeof num // "object"
    
    var newNum=deepClone(num);
    //newNum ->  {} 空对象
    
    //String包装对象
    var str=new String("hello");
    typeof str //"object"
    
    var newStr=deepClone(str);
    //newStr->  {0:'h',1:'e',2:'l',3:'l',4:'o'};
    
    //Boolean包装对象
    var bol=new Boolean(true);
    typeof bol //"object"
    
    var newBol=deepClone(bol);
    // newBol ->{} 空对象
    
    ....
    
    

    (2)valueof()函数

    所有对象都有valueOf方法,valueOf方法对于:如果存在任意原始值,它就默认将对象转换为表示它的原始值。对象是复合值,而且大多数对象无法真正表示为一个原始值,因此默认的valueOf()方法简单地返回对象本身,而不是返回一个原始值。数组、函数和正则表达式简单地继承了这个默认方法,调用这些类型的实例的valueOf()方法只是简单返回这个对象本身。

    对于原始值或者包装类:

    function baseClone(base){
     return base.valueOf();
    }
    
    //Number
    var num=new Number(1);
    var newNum=baseClone(num);
    //newNum->1
    
    //String
    var str=new String('hello');
    var newStr=baseClone(str);
    // newStr->"hello"
    
    //Boolean
    var bol=new Boolean(true);
    var newBol=baseClone(bol);
    //newBol-> true
    
    

    其实对于包装类,完全可以用=号来进行克隆,其实没有深度克隆一说,

    这里用valueOf实现,语法上比较符合规范。

    对于Date类型:

    因为valueOf方法,日期类定义的valueOf()方法会返回它的一个内部表示:1970年1月1日以来的毫秒数.因此我们可以在Date的原型上定义克隆的方法:

    Date.prototype.clone=function(){
      return new Date(this.valueOf());
    }
    
    var date=new Date('2010');
    var newDate=date.clone();
    // newDate->  Fri Jan 01 2010 08:00:00 GMT+0800 
    
    

    对于正则对象RegExp:

    RegExp.prototype.clone = function() {
    var pattern = this.valueOf();
    var flags = '';
    flags += pattern.global ? 'g' : '';
    flags += pattern.ignoreCase ? 'i' : '';
    flags += pattern.multiline ? 'm' : '';
    return new RegExp(pattern.source, flags);
    };
    
    var reg=new RegExp('/111/');
    var newReg=reg.clone();
    //newReg->  /\/111\//
    
    

    8.简单实现Node的Events模块

    简介:观察者模式或者说订阅模式,它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知。

    node中的Events模块就是通过观察者模式来实现的:

    var events=require('events');
    var eventEmitter=new events.EventEmitter();
    eventEmitter.on('say',function(name){
        console.log('Hello',name);
    })
    eventEmitter.emit('say','Jony yu');
    
    

    这样,eventEmitter发出say事件,通过On接收,并且输出结果,这就是一个订阅模式的实现,下面我们来简单的实现一个Events模块的EventEmitter。

    (1)实现简单的Event模块的emit和on方法

    function Events(){
    this.on=function(eventName,callBack){
      if(!this.handles){
        this.handles={};
      }
      if(!this.handles[eventName]){
        this.handles[eventName]=[];
      }
      this.handles[eventName].push(callBack);
    }
    this.emit=function(eventName,obj){
       if(this.handles[eventName]){
         for(var i=0;o<this.handles[eventName].length;i++){
           this.handles[eventName][i](obj);
         }
       }
    }
    return this;
    }
    
    

    这样我们就定义了Events,现在我们可以开始来调用:

     var events=new Events();
     events.on('say',function(name){
        console.log('Hello',nama)
     });
     events.emit('say','Jony yu');
     //结果就是通过emit调用之后,输出了Jony yu
    
    

    (2)每个对象是独立的

    因为是通过new的方式,每次生成的对象都是不相同的,因此:

    var event1=new Events();
    var event2=new Events();
    event1.on('say',function(){
        console.log('Jony event1');
    });
    event2.on('say',function(){
        console.log('Jony event2');
    })
    event1.emit('say');
    event2.emit('say');
    //event1、event2之间的事件监听互相不影响
    //输出结果为'Jony event1' 'Jony event2'
    
    

    9.箭头函数中this指向举例

    var a=11;
    function test2(){
      this.a=22;
      let b=()=>{console.log(this.a)}
      b();
    }
    var x=new test2();
    //输出22
    
    

    定义时绑定。

    相关文章

      网友评论

          本文标题:js面试题

          本文链接:https://www.haomeiwen.com/subject/gohsyhtx.html