美文网首页
JS面试题

JS面试题

作者: 逸軒 | 来源:发表于2020-01-30 23:51 被阅读0次

    1. setTimeout输出值的时候,如何实现i按序输出?

    for(var i=0;i<10;i++){
        setTimeout(function ten(){
                console.log(i);
            },10);
    }//输出结果是10个10
    
    

    问:为什么输出的是10个10?

    答:JS是一个单线程的解释器,setTimeout本质是间隔一定时间将任务添加到任务队列中。输出的时候for循环作为主线程已经执行完毕,此时作用域中的i=5;按序执行10次输出i,就会输出10个10;

    问:如何输出成按序输出?

    答:方法一:

    生成一个立即执行的函数,将i作为参数输入(闭包)

    for(var i=0;i<10;i++){
        (function(i){
            setTimeout(function ten(){
                console.log(i);
            },10);
        })(i)
    }
    
    

    方法二:用es6中的let来声明变量,相当于let在每个块级作用域里面都声明了一个变量i

    for(let i=0;i<10;i++){
        setTimeout(function ten(){
                console.log(i);
            },10);
    }
    
    

    方法三:使用setTimeout第三个参数

    for(var i=0;i<10;i++){
        setTimeout(function ten(){
                console.log(i);
            },10,i);
    }
    
    

    2. 继承prototype大概是怎么实现的?

    实例对象per的构造器constructor是指构造函数Person;

    console.log(per.constructor==Person);//true
    
    

    实例对象的_proto_和构造函数中的prototype相等;

    console.log(per._proto_.constructor==Person.prototype.constructor);//true
    
    

    原型链:实例对象使用的属性或者方法,先在实例中查找,找到了则直接使用,找不到则去实例对象的__proto__指向的原型对象prototype中找,找到了则使用,找不到则报错。

    实例对象中有_proto_这个属性,叫原型,也是一个对象,这个属性是给浏览器使用,

    不是标准的属性—–>proto—–>可以叫原型对象;

    构造函数中有prototype这个属性,叫原型,也是一个对象,这个属性是给程序员使用,

    是标准的属性——>prototype—>可以叫原型对象;

    1. 原型继承核心就是让自定义的构造器的prototype对象指向父类构造器生成的对象:
    //Person是个构造函数,Per是自定义的构造器
    function Per(){}
    Per.prototype = new Person('Alice');
    const person = new Per()//继承父类实例定义的所有属性以及父类构造器原型上的属性
    
    
    1. 借用函数继承:通过函数对象本身的 callapply 来显示的指定函数调用时必备的参数;
    function Per(name){
        Person.call(this,name)//当成了普通函数来使用
    }
    const person = new Per('Alice')
    //只能调用Person中定义的属性和函数,无法调用Person定义在prototype上的属性和方法
    
    
    1. 组合继承(原型链+借用函数):[为了解决借用函数无法使用函数原型上的属性和方法]
    function Per(name){
        Person.call(this,name)//当成了普通函数来使用
    }
    Per.prototype = new Person('Alice')//这样写,会造成Animal实例化两次,没有自己的原型
    //或者
    Per.prototype = Person.prototype//这样写,就不会造成多次实例化
    
    const person = new Per('Alice')
    
    
    1. 原型式继承:提供一个被继承的对象,把这个对象挂在到某个构造函数的prototype上,再利用new;
    function inherit (object) {
      function fn () {}         // 提供一个函数
      fn.prototype = object ;   // 设置函数的prototype
      return new fn()           // 返回这个函数实例化出来的对象
    }
    const person = Person('Alice');
    const me=inherit(person);//可以继承peroson对象上所有的方法和属性
    
    
    1. 寄生式继承
    2. 寄生组合式继承:利用 Object.create() 方法

    3. js的垃圾回收机制?(摘自红宝书)

    1. 离开作用域的值将被自动标记为可以回收,因此将在垃圾收集期间被删除;
    2. “ 标记清除 ” 是目前最主流的垃圾收集算法,这种算法的思想是给当前不使用的值加上标记,然后再回收其内存(当变量进入环境时,将变量标记为“进入环境”。当变量离开环境时,将其标记为“离开环境”,标记“离开环境”的就回收内存);
    3. 另一种垃圾收集算法是 “ 引用计数 ” ,这种算法的思想是跟踪记录所有值被引用的次数。JavaScript引擎目前都不再使用这种算法;但在 IE 中访问非原生JavaScript对象(如DOM元素)时,这种算法仍然可能会导致问题;
    4. 当代码中存在循环引用现象时,” 引用计数 “算法就会导致问题;
    5. 解除变量的引用不仅有助于消除循环引用现象,而且对垃圾收集也有好处。为了确保有效的回收内存,应该及时解除不再使用的全局对象、全局对象属性以及循环引用变量的引用。

    代码回收规则如下:

    1.全局变量不会被回收。

    2.局部变量会被回收,也就是函数一旦运行完以后,函数内部的东西都会被销毁。

    3.只要被另外一个作用域所引用就不会被回收

    4. 闭包是什么?用let怎么实现闭包?有什么优点和缺点?

    闭包是指有权访问另一个函数作用域中的变量的函数。

    一个闭包就是一个没有释放资源的栈区,栈区内的变量处于激活状态。

    闭包:当外部函数返回之后,内部函数可以访问外部函数的属性或者方法。(站友提供的说法)

    在ES6中let实际是为js新增了块级作用域。

    let声明的变量可以绑定在作用域中。

    优点:① 能够读取函数内部的变量;②让这些变量一直存在于内存中,不会在调用结束后被垃圾回收机制回收;

    缺点:由于闭包会使用函数中的变量存在在内存中,内存消耗很大,所以不能滥用闭包;解决的办法是退出函数之前,将不使用的局部变量删除

    5. 怎么调程序中出现的代码?

    alert('方法一')
    console.log('方法二') 
    Chrome中的开发者工具:断点设置、调试功能
    
    

    6.this的指向问题?

    this的最终指向的是那个调用它的对象。

    改变this指向的方法:

    1. 使用箭头函数
    2. 在函数内部使用_this=this;
    3. 使用apply、call、bind
    4. new实例化一个对象

    在非严格模式下,如果如果函数没有用作构造函数,而是仅仅作为普通函数使用的话,那么函数中的this是指向window的。在严格模式下,this的值就是undefined。

    7.js的执行机制:Event loop

    image
    • 同步和异步任务分别进入不同的执行”场所”,同步的进入主线程,异步的进入Event Table并注册函数。
    • 当指定的事情完成时,Event Table会将这个函数移入Event Queue。
    • 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
    • 上述过程会不断重复,也就是常说的Event Loop(事件循环)。

    setTimeout这个函数,是经过指定时间后,把要执行的任务加入到Event Queue中,又因为是单线程任务要一个一个执行,如果前面的任务需要的时间太久,那么只能等着,导致真正的延迟时间远远大于设置的时间长度。

    setInterval会每隔指定的时间将注册的函数置入Event Queue,如果前面的任务耗时太久,那么同样需要等待。唯一需要注意的一点是,对于setInterval(fn,ms)来说,我们已经知道不是每过ms秒会执行一次fn,而是每过ms秒,会有fn进入Event Queue。一旦setInterval的回调函数fn执行时间超过了延迟时间ms,那么就完全看不出来有时间间隔了

    除了广义的同步任务和异步任务,我们对任务有更精细的定义:

    • macro-task(宏任务):包括整体代码script,setTimeout,setInterval
    • micro-task(微任务):Promise,process.nextTick

    不同类型的任务会进入对应的Event Queue,比如setTimeoutsetInterval会进入相同的Event Queue。

    image

    进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。

    8.给DOM元素绑定事件有哪几种方法?

    1.使用内联;2.使用.onclick的方式;3.使用事件监听addEventListener的方式;(IE使用attachEvent)

    1、2两种方式都是存在一个弊端的,就是一个元素只能添加一个事件。

    <!--1.内联-->
    <input type="button" value="btn" onclick="alert('内联');">
    
    

    ​ 这种方式就是在一个元素上面直接绑定了一个点击onclick事件。同时,这个事件的优先级是最高的。

    <!--2\. .onclick的方式-->
    <input type="button" value="btn">
    
    <script>
        var btn = document.getElementsBytagname("input")[0];
        btn.onclick = function(){
            alert('.onclick')
        }
    </script>
    
    

    使用这种形式也是可以给一个DOM元素添加上一个事件。

    3.给一个元素(DOM对象)添加两个甚至是多个事件

    <!--3.事件监听addEventListener-->
    <input type="button" value="按钮">
    
    <script>
        var btn = document.getElementsBytagname("input")[0];
        btn.addEventListener("click", function(){
            alert(1)
        })
        btn.addEventListener("click", function(){
            alert(2)
        })
    </script>
    
    

    使用addEventListener的方式还可以拥有第三个参数:

    1. 事件类型,不需要添加上on
    2. 事件函数
    3. 是否捕获(布尔值),默认是false,即不捕获,那就是冒泡。

    attachEvent绑定的事件,如果需要注销,应该使用detachEvent

    9.三大事件冒泡、捕获是怎么执行的?

    先捕获再冒泡

    事件冒泡的概念下在p元素上发生click事件的顺序应该是p -> div -> body -> html -> document

    事件捕获的概念下在p元素上发生click事件的顺序应该是document -> html -> body -> div -> p

    接下来是摘自《红宝书》的总结:

    事件流描述的是从页面中接收事件的顺序。P348

    IE的事件流叫做事件冒泡:就是指事件开始时由最具体的元素(文档中嵌套层次最深的的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。

    div–>body–>html–>document

    事件捕获:不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。(用意在于在事件到达预定目标之前捕获它)

    document–>html–>body–>div

    10.js处理异步的几种方式

    一、回调函数(callback)

    概念:回调是一个函数被作为一个参数传递到另一个函数里,在那个函数执行完后再执行

    优点:回调函数是异步编程最基本的方法,简单、容易理解和部署

    缺点:不利于代码的阅读和维护,各个部分之间高度耦合,流程会很混乱,而且每个任务只能指定一个回调函数。

    注意 区分:回调函数和异步 回调并不一定就是异步。他们自己并没有直接关系。

    二、事件监听

    采用事件驱动模式。任务的执行不取决代码的顺序,而取决于某一个事件是否发生

    监听函数有:on,bind,listen,addEventListener,observe

    //on
    f1.on('done',f2);
    //或者
    function f1(){
        settimeout(function(){
           //...f1的任务代码
           f1.trigger('done');  //执行完成后,立即触发done事件,从而开始执行f2.
        },1000);
    }
    
    

    优点:容易理解,可以绑定多个事件,每一个事件可以指定多个回调函数,而且可以去耦合,有利于实现模块化。

    缺点:整个程序都要变成事件驱动型,运行流程会变得不清晰。

    element.onclick=function(){
       //处理函数
    }
    
    

    优点:写法兼容到主流浏览器

    缺点:当同一个element元素绑定多个事件时,只有最后一个事件会被添加,相当于一次只能添加一个事件

    //IE:attachEvent;三个方法执行顺序:3-2-1;
    elment.attachEvent("onclick",handler1);
    elment.attachEvent("onclick",handler2);
    elment.attachEvent("onclick",handler3);
    
    
    //标准addEventListener;执行顺序:1-2-3
    elment.addEvenListener("click",handler1,false);
    elment.addEvenListener("click",handler2,false);
    elment.addEvenListener("click",handler3,false);
    
    

    注意:该方法的第三个参数是冒泡获取,是一个布尔值:当为false时表示由里向外(冒泡),true表示由外向里(捕获)。

    三、发布/订阅(又称观察者模式)

    //jQuery的一个插件 Ben Alman的Tiny Pub/Sub
    jQuery.subscribe("done", f2);//f2向"信号中心"jQuery订阅"done"信号。
    jQuery.unsubscribe("done", f2);//取消订阅(unsubscribe)
    //或者
    function f1(){
      setTimeout(function () {
        // ...f1的任务代码
        jQuery.publish("done");//f1执行完成后,向"信号中心"jQuery发布"done"信号,从而引发f2的执行
      }, 1000);
    }
    
    

    四、promise对象(promise 模式)

    promise是一种模式,promise可以帮忙管理异步方式返回的代码。将代码进行封装并添加一个类似于事件处理的管理层。我们可以使用promise来注册代码,这些代码会在在promise成功或者失败后运行

    我们可以注册任意数量的函数再成功或者失败后运行,也可以在任何时候注册事件处理程序。

    promise有两种状态:1、等待(pending);2、完成(settled)。promise会一直处于等待状态,直到它所包装的异步调用返回/超时/结束。这时候promise状态变成完成。完成状态分成两类:解决(resolved);拒绝(rejected)。

    resolve()函数告诉promise用户promise已解决;reject()函数告诉promise用户promise未能顺利完成。注意then和catch用法,可以将他们想象成onsucess和onfailure事件的处理程序。

    f1().then(f2).then(f3);
    f1().then(f2).fail(f3);
    
    

    优点:回调函数写成了链式写法,程序的流程可以看得很清楚,而且有一整套的配套方法,可以实现很多强大的功能。如果一个任务已经完成,再添加回调函数,该回调函数会立即执行

    缺点:编写和理解都相对比较难。

    五、优雅的async/await

    async 函数书写的方式跟我们普通的函数书写方式一样,只不过是前面多了一个 async 关键字,并且函数返回的是一个 Promise 对象,所接收的值就是函数 return 的值。

    async 函数内部可以使用 await 命令,表示等待一个异步函数的返回。await 后面跟着的是一个 Promise 对象,如果不是的话,系统会调用 Promise.resolve() 方法,将其转为一个 resolve 的 Promise 的对象。

    let bar = async function(){  
        try{    
            await Promise.reject('error')
        }catch(e){    
            console.log(e)
        }
    }
    
    

    11.JS设置CSS样式的几种方式

    1. 直接设置style的属性 某些情况用这个设置 !important值无效

    element.style.height = '100px';
    /*      如果属性有'-'号,就写成驼峰的形式(如textAlign)  
    如果想保留 - 号,就中括号的形式  element.style['text-align'] = '100px';*/
    
    

    2. 直接设置属性(只能用于某些属性,相关样式会自动识别)

    element.setAttribute('height', 100);
    element.setAttribute('height', '100px');
    
    

    3. 设置style的属性

    element.setAttribute('style', 'height: 100px !important');
    
    

    4. 使用setProperty 如果要设置!important,推荐用这种方法设置第三个参数

    element.style.setProperty('height', '300px', 'important');
    
    

    5. 改变class 比如JQ的更改class相关方法

    element.className = 'blue';
    element.className += 'blue fb';
    /*因JS获取不到css的伪元素,所以可以通过改变伪元素父级的class来动态更改伪元素的样式*/
    
    

    6. 设置cssText

    element.style.cssText = 'height: 100px !important';
    element.style.cssText += 'height: 100px !important';
    
    

    7. 创建引入新的css样式文件

        function addNewStyle(newStyle) {
                var styleElement = document.getElementById('styles_js');
                if (!styleElement) {
                    styleElement = document.createElement('style');
                    styleElement.type = 'text/css';
                    styleElement.id = 'styles_js';
                    document.getElementsByTagName('head')[0].appendChild(styleElement);
                }  
                styleElement.appendChild(document.createTextNode(newStyle));
         }
        addNewStyle('.box {height: 100px !important;}');
    
    

    8. 使用addRule、insertRule

            // 在原有样式操作
            document.styleSheets[0].addRule('.box', 'height: 100px');
            document.styleSheets[0].insertRule('.box {height: 100px}', 0);
    
            // 或者插入新样式时操作
            var styleEl = document.createElement('style'),
                styleSheet = styleEl.sheet;
    
            styleSheet.addRule('.box', 'height: 100px');
            styleSheet.insertRule('.box {height: 100px}', 0);
    
            document.head.appendChild(styleEl);  
    
    

    12.点击网页任意空白区域隐藏div

    $('.phone_cell').on('click',function(event){
        //取消事件冒泡  
        event.stopPropagation();
        $('.ulfix').slideToggle();
    });
    //点击空白处隐藏弹出层,下面为消失效果
    $(window).click(function(){
        if ($('.ulfix').css('display')!='none') {
            $('.ulfix').slideToggle();
        }    
    });
    
    

    需要注意的是如果单层里面本身还有点击事件,如果点击自身也会出现消失的情况 ,解决办法给点击事件增加

    13.js的基本数据类型及其判断方法

    值类型(6个基本类型):字符串(String)、数字(Number)、布尔(Boolean)、空(Null)、未定义(Undefined)、Symbol(ES6引入,表示独一无二的值)。

    引用数据类型(又称Object类型):对象(Object)、数组(Array)、函数(Function)、Date 类型、RegExp 类型(正则)。

    注意:基本数据类型值不可变,基本类型的比较是值的比较;引用类型值可变,引用类型的比较是引用的比较;

    检测类型常用的有typeof和instanceof:

    ①typeof:经常用来检测一个变量是不是最基本的数据类型

    var a;typeof a;     // undefined,声明未赋值
    a = true;typeof a;    // boolean
    a = 666;typeof a;    // number 
    a = "hello";typeof a;    // string
    a = Symbol();typeof a;    // symbol
    a = [];typeof a;    // object
    a = {};typeof a;    // object
    a = /aaa/g;typeof a;    // object
    
    a = null;typeof a;    // object,注意null判断出来的类型是object
    a = function(){};typeof a;    // function,注意function判断出来的类型是function而不是object
    
    

    ②instanceof:判断一个引用类型的变量具体是不是某种类型的对象

    ({}) instanceof Object              // true
    ([]) instanceof Array               // true
    (/aa/g) instanceof RegExp           // true
    (function(){}) instanceof Function  // true
    
    

    14.怎么判断数组类型?5种方法

    typeof运算符,constructor法,instanceof运算符,Object.prototype.toString方法以及Array.isArray法

    ①typeof:javascript原生提供的判断数据类型的运算符,它会返回一个表示参数的数据类型的字符串;但是数组,对象,正则,null经过这个判断得到的都是object,并不能识别出数组

    const s = [];
    console.log(typeof(s))//object
    
    

    ②instanceof运算符:判断某个构造函数的prototype属性所指向的對象是否存在于另外一个要检测对象的原型链上。如果这个Object的原型链上能够找到Array构造函数的话,那么这个Object应该及就是一个数组,如果这个Object的原型链上只能找到Object构造函数的话,那么它就不是一个数组。

    ({}) instanceof Object              // true
    ([]) instanceof Array               // true
    
    

    ③constructor:实例化的数组拥有一个constructor属性,这个属性指向生成这个数组的方法。但是constructor属性可以改写,判断的结果不靠谱

    const a = [];console.log(a.constructor);//function Array(){ [native code] }
    console.log(a.constructor==Array);//true
    const o = {};console.log(o.constructor);//function Object(){ [native code] }
    const r = /^[0-9]$/;console.log(r.constructor);//function RegExp() { [native code] }
    const n = null;console.log(n.constructor);//报错
    
    

    ④Object.prototype.toString:每一个继承自Object的对象都拥有toString的方法,toString方法将会返回”[object type]”,其中的type代表的是对象的类型,根据type的值,我们就可以判断这个疑似数组的对象到底是不是数组了。但是对象的toString方法也可以重写,可能会判断出错

    注意:不能直接使用数组的。toString方法,会返回字符串,如下:

    const a = ['Hello','world'];a.toString();//"Hello,world"
    const b = {0:'Hello',1:'world'};b.toString();//"[object Object]"
    const c = 'Hello world';c.toString();//"Hello,world"
    
    

    所以要判断除了对象之外的数据的数据类型,我们需要“借用”对象的toString方法,使用call或者apply方法来改变toString方法的执行上下文

    const a = ['Hello','world'];Object.prototype.toString.call(a);//"[object Array]"
                                Object.prototype.toString.apply(a);//"[object Array]"
    const c = 'Hello world';Object.prototype.toString.call(c);//"[object String]"
                            Object.prototype.toString.apply(c);//"[object String]"
    
    

    ⑤isArray方法:最靠谱的判断数组的方法

    const a = [];Array.isArray(a);//true
    const b = {};Array.isArray(b);//false
    
    

    经过验证,即使重写了Object.prototype.toString方法、修改了constructor对象是不影响判断的结果的。

    15.new Date()获取的时间是什么时间

    运行环境的时间:如果是浏览器那就是电脑时间或者手机时间;如果是node.js那就是服务器时间

    前台获取的时间都用处不大、用户可以改、不安全;通常时间用于从后端传入、服务器的时间一定是统一的、解析时间戳;

    js中单独调用new Date(),例如document.write(new Date());显示的结果是:Mar 31 10:10:43 UTC+0800 2012 这种格式的时间; 但是用new Date() 参与计算会自动转换为从1970.1.1开始的毫秒数。

    JS获取当前时间戳
    //方法1
    var timestamp =Date.parse(new Date());
    
    

    注意:Date.parse()得到的结果将后三位(毫秒)转换成了000显示,使用时可能会出现问题。例如动态添加页面元素id的时候,不建议使用。

    //方法2
    var timestamp =(new Date()).valueOf();
    
    
    //方法3
    var timestamp=new Date().getTime();
    
    
    //方法4
    var timestamp=+new Date();
    
    

    16.深拷贝和浅拷贝的区别?怎么实现深拷贝?

    是否指向原对象 第一层数据为基本数据类型 原数据中包含引用数据类型
    赋值 改变会使原数据一同改变 改变会使原数据一同改变
    浅拷贝 改变会使原数据一同改变 改变会使原数据一同改变
    深拷贝 改变会使原数据一同改变 改变会使原数据一同改变

    赋值是赋的是该对象的在栈中的地址,而不是堆中的数据,也就是两个对象指向的是同一个存储空间,是联动的;

    浅拷贝只复制一层对象的属性,如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,并不包括对象里面的为引用类型的数据;

    浅拷贝的实现

    1.Object.assign():把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象,拷贝的是对象的属性的引用,而不是对象本身。

    var obj = { a: {a: "copy", b: 1} };
    var newlObj = Object.assign({}, obj);//-----------
    initalObj.a.a = "swallow";
    console.log(obj.a.a); //swallow
    
    

    2.Array.prototype.concat():修改新对象会改到原对象

    let arr = [1, 2, {    username: 'copy'    }];
    let arr2=arr.concat();//----------------    
    arr2[2].username = 'swallow';
    console.log(arr);
    
    

    3.Array.prototype.slice():同样修改新对象会改到原对象

    let arr = [1, 2, {    username: 'copy'    }];
    let arr2=arr.slice();//----------------    
    arr2[2].username = 'swallow';
    console.log(arr);
    
    

    注:Array的slice和concat方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。

    深拷贝是对对象以及对象的所有子对象进行拷贝。

    深拷贝的实现

    1.JSON.parse(JSON.stringify()):用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,可以实现数组或对象深拷贝,但不能处理函数

    因为JSON.stringify() 方法是将一个JavaScript值(对象或者数组)转换为一个 JSON字符串,不能接受函数

    let arr = [1, 3, {    username: ' copy'}];
    let arr2 = JSON.parse(JSON.stringify(arr));
    arr2[2].username = 'shen'; 
    console.log(arr, arr4)
    
    

    2.递归:递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝

        //定义检测数据类型的功能函数
        function checkedType(target) {
          return Object.prototype.toString.call(target).slice(8, -1)
        }
        //实现深度克隆---对象/数组
        function clone(target) {
          //判断拷贝的数据类型
          //初始化变量result 成为最终克隆的数据
          let result, targetType = checkedType(target)
          if (targetType === 'Object') {
            result = {}
          } else if (targetType === 'Array') {
            result = []
          } else {
            return target
          }
          //遍历目标数据
          for (let i in target) {
            //获取遍历数据结构的每一项值。
            let value = target[i]
            //判断目标结构里的每一值是否存在对象/数组
            if (checkedType(value) === 'Object' ||
              checkedType(value) === 'Array') { //对象/数组里嵌套了对象/数组
              //继续遍历获取到value值
              result[i] = clone(value)
            } else { //获取到value值是基本的数据类型或者是函数。
              result[i] = value;
            }
          }
          return result
        }
    
    
        // 内部方法:用户合并一个或多个对象到第一个对象
        // 参数:
        // target 目标对象  对象都合并到target里
        // source 合并对象
        // deep 是否执行深度合并
        function extend(target, source, deep) {
            for (key in source)
                if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
    // source[key]是对象,而target[key]不是对象,则target[key]={}初始化一下,否则递归会出错的
                    if (isPlainObject(source[key]) && !isPlainObject(target[key]))
                        target[key] = {}
    
    // source[key] 是数组,而 target[key]不是数组,则 target[key] = []初始化一下,否则递归会出错的
                    if (isArray(source[key]) && !isArray(target[key]))
                        target[key] = []
                    // 执行递归
                    extend(target[key], source[key], deep)
                }
                // 不满足以上条件,说明 source[key] 是一般的值类型,直接赋值给 target 就是了
                else if (source[key] !== undefined) target[key] = source[key]
        }
    
        // Copy all but undefined properties from one or more
        // objects to the `target` object.
        $.extend = function(target){
            var deep, args = slice.call(arguments, 1);
    
            //第一个参数为boolean值时,表示是否深度合并
            if (typeof target == 'boolean') {
                deep = target;
                //target取第二个参数
                target = args.shift()
            }
            // 遍历后面的参数,都合并到target上
            args.forEach(function(arg){ extend(target, arg, deep) })
            return target
        }
    
    

    17.数组常用的遍历方法有哪几种?(for…in一般用于对象)

    • 1.普通for循环

    • 性能:使用频率最高,性能不弱,但仍有优化空间

      for(j = 0; j < arr.length; j++) {}
      
      
    • 2.优化版for循环

    • 性能:使用临时变量,将长度缓存起来,避免重复获取数组长度,当数组较大时优化效果才会比较明显。

      基本上是所有循环遍历方法中性能最高的一种。

      for(j = 0,len=arr.length; j < len; j++) {}
      
      
    • 3.foreach循环,没有返回值,纯粹用来遍历数组

    • 性能:数组自带的foreach循环,性能比普通for循环弱

      let numbers = [1, 2, 3, 4];    
      numbers.forEach((item, index, array)=>{        
        // 执行某些操作    
      })
      
      
    • 4.for…in循环

    • 性能:效率最低,不推荐用于数组遍历,一般用于对象循环

      for(j in arr) {} 
      
      
    • 5.for…of循环

    • 性能:性能好于for…in,但仍低于普通for循环

      不仅可以遍历数组,还可以遍历Map、Set这两种ES6新推出的数据结构。

    let numbers = [1, 2, 3, 4];
    for(let item of numbers){    
        console.log(item);
    }
    //输出
    // 1
    // 2
    // 3
    // 4
    
    
    • map遍历

    • 注意: map() 不会对空数组进行检测;map() 不会改变原始数组。

      对数组的每一项运行给定函数,返回每次函数调用的返回值组成的数组。

    let numbers = [1, 2, 3, 4];    
    let mapResult = numbers.map((item, index, array)=>{        
        return item * 2;    
    })    
    console.log(mapResult); // 输出 [2, 4, 6, 8]
    
    

    性能:比较优雅,低于普通for循环,还比不上foreach

    • 大致比较如下:for循环 > forEach > for…of > for…in > map

    还有几个用的比较少的es5的循环方法:

    every( ):对数组中的每一项运行给定函数,如果该函数对每一项都返回true,则every( )返回truelet

     numbers = [1, 2, 3, 4];    
    let everyResult = numbers.every((item, index, array)=>{        
        return item > 1;    
    })    
    console.log(everyResult);  // 输出 false
    
    

    filter( ):对数组中的每一项运行给定函数,然后返回该函数会返回true的项。

    let numbers = [1, 2, 3, 4];    
    let filterResult = numbers.filter((item, index, array)=>{        
        return item > 1;    
    })    
    console.log(filterResult);  // 输出 [2, 3, 4]
    
    

    | some( ):对数组的每一项运行给定函数,如果该函数对任一项返回true,则some( )就返回true。some和every就像 | | 和 &&,some是只要有一项满足,就返回true。 |

    let numbers = [1, 2, 3, 4];    
    let someResult = numbers.some((item, index, array)=>{        
        return item > 3;    
    })    
    console.log(someResult);  // 输出 true
    
    

    注意:ES5的这几个迭代方法,如果是只对item(当前遍历的数组项)进行操作,是不会改变原数组的,但是也可以通过给定函数中接收的index参数来改变原数组使用选择的时候,以现在的硬件水平,这里的差异其实可以忽略,考虑语义和功能需求来选择

    如果你需要将数组按照给定规则转换返回该结果数组,就使用map。

    如果你需要进行简单的遍历,用 forEach 或者 for of,但是 forEach 不能通过return和break语句来终止循环,所以

    如果需要中途终止循环,就使用 for…of或者 for循环。

    如果是在遍历数组的同时,需要改变原数组中的对应项,就用for循环。

    for…in会把数组所拥有可枚举的属性都遍历一次,所以可能会有意想不到的结果,不推荐用来遍历数组。

    另外的三个,every( )、filter( )、some( )按功能需要来使用即可。

    18.JS获取url参数

    function getQueryVariable(variable)
    {
           var query = window.location.search.substring(1);
           var vars = query.split("&");
           for (var i=0;i<vars.length;i++) {
                   var pair = vars[i].split("=");
                   if(pair[0] == variable){return pair[1];}
           }
           return(false);
    }
    
    

    19.在js的浏览器对象模型中,window对象的(status)属性是用来指定浏览器状态栏里面的临时消息

    status:状态栏信息属性

    screen:屏幕对象(也叫显示屏对象)

    history:网页历史对象

    document:文本对象

    20.隐式转换(三种)掘金有篇博客:你所忽略的js隐式转换

    +运算符即可数字相加,也可以字符串相加。所以转换时很麻烦。

    ==不同于===,故也存在隐式转换。

    - * / 这些运算符只会针对number类型,故转换的结果只能是转换成number类型。

    隐式转换中主要涉及到三种转换:

    ​ 1、将值转为原始值,ToPrimitive(input, PreferredType?)。

    valueOf(),toString()

    PreferredType没有设置时,Date类型的对象,PreferredType默认设置为String,其他类型对象PreferredType默认设置为Number。

    ​ 2、将值转为数字,ToNumber()。.

    参数 结果
    undefined NaN
    null +0
    布尔值 true转换1,false转换为+0
    数字 无须转换
    字符串 有字符串解析为数字,例如:‘324’转换为324,‘qwer’转换为NaN
    对象(obj) 先进行 ToPrimitive(obj, Number)转换得到原始值,在进行ToNumber转换为数字

    ​ 3、将值转为字符串,ToString()。

    参数 结果
    undefined ‘undefined’
    null ‘null’
    布尔值 转换为’true’ 或 ‘false’
    数字 数字转换字符串,比如:1.765转为’1.765’
    字符串 无须转换
    对象(obj) 先进行 ToPrimitive(obj, String)转换得到原始值,在进行ToString转换为字符串

    举例

    ({} + {}) = ?
    两个对象的值进行+运算符,肯定要先进行隐式转换为原始类型才能进行计算。
    1、进行ToPrimitive转换,由于没有指定PreferredType类型,{}会使默认值为Number,进行ToPrimitive(input, Number)运算。
    2、所以会执行valueOf方法,({}).valueOf(),返回的还是{}对象,不是原始值。
    3、继续执行toString方法,({}).toString(),返回"[object Object]",是原始值。
    故得到最终的结果,"[object Object]" + "[object Object]" = "[object Object][object Object]"
    
    

    ==进行转换时,

    1.类型相同时,没有类型转换,主要注意NaN不与任何值相等,包括它自己,即NaN !== NaN

    2.类型不相同时,

    x,y 为null、undefined两者中一个 // 返回true

    x、y为Number和String类型时,则转换为Number类型比较

    有Boolean类型时,Boolean转化为Number类型比较

    一个Object类型,一个String或Number类型,将Object类型进行原始转换后,按上面流程进行原始值比较。

    21.使用setTimeout模拟setInterval

    var i=0;
    setInterval(function(){
        i=i++;
        console.log(i)
    },1000)
    
    
    //使用setTimeout也可以产生setInterval的效果
    var i=0;
    function intv(){
        setTimeout(function(){
            consolr.log(i++);
            intv()
        },1000)
    }
    intv()
    
    

    但是setInterval和intv()还是有些差别

    /intv()函数表示在函数执行完成之后的一秒再执行下一个函数
    函数A(执行时间)-->(1s)-->函数B(执行时间)-->(1S)-->函数C(执行时间)-->(1S);
    /setInterval表示函数执行的间隔为1秒钟,可以这样认为
    (函数A(执行时间)+(间隔时间))=(1S)-->(函数B(执行时间)+(间隔时间))=(1S)-->(函数C(执行时间)+(间隔时间))=(1S)
    
    

    22.计算数字各位相加之和

    // 初始化 sum(和)为 0
    var a = 283, sum = 0;            
    for(var i = 0; i < a.toString().length; i++) {
        alert(a.toString()[i]);    
        // 每一位相加
        // 第一次 0 + 2,第二次 2 + 8,第三次 10 + 3
        sum += parseInt(a.toString()[i]);
    }            
    alert(sum);//13
    
    

    23.数据结构中”遍历”是什么意思?

    所谓遍历,是指沿着某条搜索路线,依次对树中每个结点均做一次且仅做一次访问。

    24.在js中,两个整数进行除(/)运算,结果也为整数吗?不是,是小数

    在js中计算5/2,不会像在java中得到2,结果会是2.5,那如何得到整数2呢,整合下搜索结果,总共有以下几种方法:

    1. parseInt(5/2)
    2. Math.floor(5/2)
    3. | 5/2 | 0 |

    | 第三种特别说明下,’ | ‘是位运算符,js中位运算之前会转为整数,与0位运算结果还是本身,所以也能达到取整数的目的。 |

    24.form标签对之间,可以出现p、ul等非表单域元素吗?可以

    25.bind、apply、call三者区别?用apply实现bind

    [图片上传失败...(image-4b343b-1580399293051)]

    26.画一下原型链,prototype__proto__有什么区别

    [图片上传失败...(image-997059-1580399293051)]

    prototype__proto__有什么区别:

    1575295251658

    __proto__指向的是这个对象的构造函数的prototype;A函数的构造函数是Object

    1575295456546

    27.删除表格的第一行有哪些方法

    方法一:removeChild

    var tds = table.getElementsByTagName("td");
    var tr = tds[0].parentNode;
    tr.parentNode.removeChild(tr);
    
    

    方法二:deleteRow

    var trs = document.getElementsByTagName("tr");
    var table = document.getElementById("table");
    table.deleteRow(0);//删除第一行
    
    

    方法三:jQuery:remove、detach、empty

    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    function delRow(row_id){ 
      $(row_id).remove(); //方法1,删移除元素及它的数据和事件
      $(row_id).detach(); //方法2,移除被选元素,包括所有的文本和子节点。然后它会保留数据和事件。
      $(del).empty(); //方法3,从被选元素移除内容
    } 
    
    

    方法四:样式

    var tds = table.getElementsByTagName("td");
    var tr = tds[0].parentNode;
    tr.style.display='none';//方法1,不保留原有位置
    tr.style.opacity=0;//方法2,保留原有位置
    tr.style.visibility='hidden';//方法3,保留原有位置
    
    

    28.jQuery有哪些方法可以选择一个表格中的第一行

    $("#id") //id=“id”的元素
    $(".class") //class=“class”的元素
    $("p") //所有<p>元素
    $(".intro.demo") //所有 class="intro" 且 class="demo" 的元素
    $("p:first") //第一个<p>元素
    $("p:last") //最后一个<p>元素
    $("tr:even") //所有偶数<tr>元素
    $("tr:odd") //所有奇数<tr>元素
    $("ul li:eq(3)") //列表中的第四个元素(index从0开始)
    $("ul li:gt(3)") //列出index大于3的元素
    $("ul li:lt(3)") //列出index小于3的元素
    $("[href]") //所有带有href属性的元素
    $(":input") //所有<input>元素
    $(":checked") //所有被选中的input元素
    
    

    29.扁平化一个嵌套的数组

    let arrays = [1, [2, [3, [4, 5]]], 6];
    
    

    需求:多维数组=>一维数组

    第一种方法:直接调用arr的flat方法

    arr = arrays.flat(Infinity);//[1, 2, 3, 4, 5, 6]
    
    

    第二种方法:正则表达式

    let str = JSON.stringify(arrays);
    arr = str.replace(/(\]|\[)/g, '').split(',');//["1", "2", "3", "4", "5", "6"]
    
    

    第三种:正则表达式

    str = str.replace(/(\[|\])/g, '');
    str = '[' + str + ']';
    ary = JSON.parse(str);//[1, 2, 3, 4, 5, 6]
    
    

    第四种:递归

    let result = [];
    function fn(array) {
        for (let i = 0; i < array.length; i++) {
            let item = array[i];
            if (Array.isArray(array[i])) {
                fn(item);
            } else {
                result.push(item);
            }
        }
    }
    fn(arrays)
    
    

    第五种:reduce

    const flatten = (arr) => {
      return arr.reduce((prev, next) => {
        return prev.concat(Object.prototype.toString.call(next) === '[object Array]' ? flatten(next): next)
      }, [])
    }
    
    

    第六种:…扩展运算符

    while (arrays.some(Array.isArray)) {
        arrays = [].concat(...arrays);
    }//[1, 2, 3, 4, 5, 6]
    

    相关文章

      网友评论

          本文标题:JS面试题

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