JS面试题

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]
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,473评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,229评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,570评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,427评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,335评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,737评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,392评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,693评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,730评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,512评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,349评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,750评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,017评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,290评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,706评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,904评论 2 335

推荐阅读更多精彩内容

  • 面试题按类型来分,主要涉及到“技术”与“非技术”两大类,今天我们主要讨论的是“技术类”,在这个大类别下涉及到的子类...
    simple_50a1阅读 2,432评论 1 56
  • 1.介绍js的基本数据类型。Undefined、Null、Boolean、Number、String2.介绍js有...
    lucky婧阅读 681评论 0 5
  • 1.JavaScript概述(js是什么?) JavaScript是一种基于对象的,事件驱动的,跨平台的,客户端脚...
    jocode阅读 434评论 0 0
  • 1.JavaScript 的 typeof 返回哪些数据类型? 基础类型包括:Number、String、Bool...
    流泪手心_521阅读 367评论 0 0
  • 禁止鼠标右键contextmenu 是当浏览者按下鼠标右键出现菜单时或者通过键盘的按键触发页面菜单时触发的事件...
    Shaw007阅读 395评论 0 0