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

JS面试题_第1张图片
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。

JS面试题_第2张图片
image

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

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

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

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




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






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

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






使用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

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__有什么区别:

JS面试题_第3张图片
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


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") //所有

元素 $(".intro.demo") //所有 class="intro" 且 class="demo" 的元素 $("p:first") //第一个

元素 $("p:last") //最后一个

元素 $("tr:even") //所有偶数元素 $("tr:odd") //所有奇数元素 $("ul li:eq(3)") //列表中的第四个元素(index从0开始) $("ul li:gt(3)") //列出index大于3的元素 $("ul li:lt(3)") //列出index小于3的元素 $("[href]") //所有带有href属性的元素 $(":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面试题)