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—>可以叫原型对象;
- 原型继承核心就是让自定义的构造器的prototype对象指向父类构造器生成的对象:
//Person是个构造函数,Per是自定义的构造器
function Per(){}
Per.prototype = new Person('Alice');
const person = new Per()//继承父类实例定义的所有属性以及父类构造器原型上的属性
-
借用函数继承:通过函数对象本身的
call
和apply
来显示的指定函数调用时必备的参数;
function Per(name){
Person.call(this,name)//当成了普通函数来使用
}
const person = new Per('Alice')
//只能调用Person中定义的属性和函数,无法调用Person定义在prototype上的属性和方法
- 组合继承(原型链+借用函数):[为了解决借用函数无法使用函数原型上的属性和方法]
function Per(name){
Person.call(this,name)//当成了普通函数来使用
}
Per.prototype = new Person('Alice')//这样写,会造成Animal实例化两次,没有自己的原型
//或者
Per.prototype = Person.prototype//这样写,就不会造成多次实例化
const person = new Per('Alice')
- 原型式继承:提供一个被继承的对象,把这个对象挂在到某个构造函数的prototype上,再利用new;
function inherit (object) {
function fn () {} // 提供一个函数
fn.prototype = object ; // 设置函数的prototype
return new fn() // 返回这个函数实例化出来的对象
}
const person = Person('Alice');
const me=inherit(person);//可以继承peroson对象上所有的方法和属性
- 寄生式继承
-
寄生组合式继承:利用
Object.create()
方法
3. js的垃圾回收机制?(摘自红宝书)
- 离开作用域的值将被自动标记为可以回收,因此将在垃圾收集期间被删除;
- “ 标记清除 ” 是目前最主流的垃圾收集算法,这种算法的思想是给当前不使用的值加上标记,然后再回收其内存(当变量进入环境时,将变量标记为“进入环境”。当变量离开环境时,将其标记为“离开环境”,标记“离开环境”的就回收内存);
- 另一种垃圾收集算法是 “ 引用计数 ” ,这种算法的思想是跟踪记录所有值被引用的次数。
JavaScript
引擎目前都不再使用这种算法;但在 IE 中访问非原生JavaScript
对象(如DOM
元素)时,这种算法仍然可能会导致问题; - 当代码中存在循环引用现象时,” 引用计数 “算法就会导致问题;
- 解除变量的引用不仅有助于消除循环引用现象,而且对垃圾收集也有好处。为了确保有效的回收内存,应该及时解除不再使用的全局对象、全局对象属性以及循环引用变量的引用。
代码回收规则如下:
1.全局变量不会被回收。
2.局部变量会被回收,也就是函数一旦运行完以后,函数内部的东西都会被销毁。
3.只要被另外一个作用域所引用就不会被回收
4. 闭包是什么?用let怎么实现闭包?有什么优点和缺点?
闭包是指有权访问另一个函数作用域中的变量的函数。
一个闭包就是一个没有释放资源的栈区,栈区内的变量处于激活状态。
闭包:当外部函数返回之后,内部函数可以访问外部函数的属性或者方法。(站友提供的说法)
在ES6中let实际是为js新增了块级作用域。
let声明的变量可以绑定在作用域中。
优点:① 能够读取函数内部的变量;②让这些变量一直存在于内存中,不会在调用结束后被垃圾回收机制回收;
缺点:由于闭包会使用函数中的变量存在在内存中,内存消耗很大,所以不能滥用闭包;解决的办法是退出函数之前,将不使用的局部变量删除;
5. 怎么调程序中出现的代码?
alert('方法一')
console.log('方法二')
Chrome中的开发者工具:断点设置、调试功能
6.this的指向问题?
this的最终指向的是那个调用它的对象。
改变this指向的方法:
- 使用箭头函数;
- 在函数内部使用
_this=this;
- 使用apply、call、bind
- new实例化一个对象;
在非严格模式下,如果如果函数没有用作构造函数,而是仅仅作为普通函数使用的话,那么函数中的this是指向window的。在严格模式下,this的值就是undefined。
7.js的执行机制:Event loop
- 同步和异步任务分别进入不同的执行”场所”,同步的进入主线程,异步的进入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,比如setTimeout
和setInterval
会进入相同的Event Queue。
进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。
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
的方式还可以拥有第三个参数:
- 事件类型,不需要添加上
on
- 事件函数
- 是否捕获(布尔值),默认是
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呢,整合下搜索结果,总共有以下几种方法:
- parseInt(5/2)
- Math.floor(5/2)
- | 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__
有什么区别:
__proto__
指向的是这个对象的构造函数的prototype;A函数的构造函数是Object
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]