1. js数据类型分为什么?null和undefined的区别?
基本数据类型和引用数据类型;
基本数据类型:number,string,Boolean,null,undefined; 引用数据类型:Array,Object,Date,Function,RegExp;
null: typeof null 返回的是object,即一个空的对象引用;
undefined: 使用var声明变量并未对其进行初始化时,这个变量的值就是undefined;
2. JavaScript 中 typeof 和 instanceof
typeof 一般只能返回如下结果:number , string , Boolean , object , Function , undefined; 对于array,null,对象等 typeof(x)均为 object,这也是typeof的局限性;
instanceof 用来判断一个对象在其原型链中是否存在一个构造函数prototype属性;
如:var a = new Array();
console.log(a instanceof Array) //true
console.log(a instanceof Object ) // true (因为Array是object的子类)
3. JS常见的dom操作api
1.节点查找:
document.getElementById :根据ID查找元素,大小写敏感,如果有多个结果,只返回第一个;
document.getElementsByClassName:根据类名查找元素,多个类名用空格分隔,返回一个 HTMLCollection 。注意兼容性为IE9+(含)。
document.getElementsByTagName:根据标签查找元素,返回一个 HTMLCollection。
document.getElementsByName :根据元素的name属性查找,返回一个 NodeList 。
document.querySelector:返回单个Node,IE8+(含),如果匹配到多个结果,只返回第一个。
document.querySelectorAll:返回一个 NodeList ,IE8+(含)。
document.forms:返回当前页面所有form,返回一个 HTMLCollection
2.节点创建API
* createElement创建元素:
var ele = document.createElement('div');
ele.innerHTML = 'sss';
document.body.appendChild(ele);
注:通过 createElement 创建的元素并不属于 document 对象,它只是创建出来,并未添加到html文档中,要调用 appendChild 或 insertBefore 等方法将其添加到HTML文档中。
* createTextNode:创建文本节点:
var node = document.createTextNode("我是文本节点");
document.body.appendChild(node);
* cloneNode 克隆一个节点:它接收一个bool参数,用来表示是否复制子元素
var form = document.getElementById("test");
var clone = form.cloneNode(true);
clone.id = 'test2';
document.body.appendChild(clone);
3.节点修改API
appendChild:parent.appendChild(child);
insertBefore:parentNode.insertBefore(newNode, refNode);
removeChild:var deletedChild = parent.removeChild(node);
replaceChild:parent.replaceChild(newChild, oldChild);
4.节点关系API
父关系API:parentNode,parentElement
子关系API:children,childNodes,firstChild,lastChild
兄弟关系型API:
previousSibling:节点的前一个节点,如果不存在则返回null。注意有可能拿到的节点是文本节点或注释节点,与预期的不符,要进行处理一下。
nextSibling :节点的后一个节点,如果不存在则返回null。注意有可能拿到的节点是文本节点,与预期的不符,要进行处理一下。
previousElementSibling :返回前一个元素节点,前一个节点必须是Element,注意IE9以下浏览器不支持
nextElementSibling :返回后一个元素节点,后一个节点必须是Element,注意IE9以下浏览器不支持。
5.元素属性型API
setAttribute给元素设置属性:element.setAttribute(name, value);
getAttribute:var value = element.getAttribute("id");
hasAttribute,dataset
6.样式相关API
直接修改元素的样式:
elem.style.color = 'red';
elem.style.setProperty('font-size', '14px');
elem.style.removeProperty('color');
动态添加样式规则:
var style = document.createElement('style');
style.innerHTML = 'body{color:red} #top:hover{color: white;}';
document.head.appendChild(style);
等等。。。(以上dom操作不全,仅参考)
4.事件冒泡,事件捕获和事件委托
事件冒泡是从当前事件目标一级一级的往上传递,依次触发直到document为止。
事件捕获是从document开始触发,一级一级的往下传递,依次触发,直至目标元素。
document.getElementById('box3').addEventListener('click', sayBox3, false);
addEventListener:第三个参数默认是false,false为冒泡,true为捕获;
事件委托又称之为事件代理,通过监听一个父元素,来给不同的子元素绑定事件,减少监听次数,从而提升速度。
如:ul里又有100个li,li每个都添加点击事件;通过冒泡事件,点击li时触发ul的点击事件;
document.getElementById('Ul').addEventListener('click', function(event) {
// 每一个函数内都有一个event事件对象,它有一个target属性,指向事件源
var src = event.target;
// 我们判断如果target事件源的节点名字是li,那就执行这个函数
// target里面的属性是非常多的,id名、class名、节点名等等都可以取到
if(src.nodeName.toLowerCase() == 'li') {
clickLi() ;
}
});
function clickLi() {
alert('你点击了li');
}
阻止事件冒泡:
window.event? window.event.cancelBubble = true : e.stopPropagation();<br/>
阻止浏览器的默认行为:
if(e.preventDefault){
e.preventDefault();
}else{
window.event.returnValue == false;
}
5.对闭包的理解?什么时候构成闭包?闭包的实现方法?闭包的优缺点?
定义:能够访问其他函数内部变量的函数
function a() {
var i=0;
function b() {
alert(i);
}
return b;//return b是返回一个函数,外部通过调用的这个返回函数就能获取到局部标量的值
}
var c = a();
c();
特性:1:函数套函数;
2:内部函数可以直接使用外部函数的局部变量或参数;
3:变量或参数不会被垃圾回收机制回收;
优点:1:变量长期驻扎在内存中;
2:避免全局变量的污染;
3:私有成员的存在 ;
缺点: 造成内存泄露
6.深拷贝和浅拷贝,如何实现深拷贝?
浅拷贝:如基本数据类型的赋值,以及将原对象或原数组的引用直接赋值给新对象/新数组,新对象/数组只是原对象/数组的引用。即改变新对象/数组,原对象数组也会改变。
深拷贝:创建新数组/对象,将原数组/对象的‘值’拷贝,而不是拷贝引用。即对新数组/对象修改,原数组/对象无改变。
深拷贝分为两种:1.拷贝第一层级的对象属性或数组元素
2.递归拷贝所有层级的对象属性和数组元素
1)只拷贝第一层级的数组元素 深拷贝数组
var arr = [1,2,3,4];
function copyFun (arr){
var newArr = [];
for(var i = 0;i<arr.length;i++){
newArr.push(arr[i]);
}
return newArr;
}
var copyArr = copyFun(arr);
copyArr[0] = 7;
console.log(copyArr);//[7,2,3,4];
console.log(arr);//[1,2,3,4]
2) 拷贝所有层级的对象属性和数组
* JSON.parse(JSON.stringify(XXXX))
* 递归方式实现:
var arr = [
{number:1},
{number:2},
{number:3}
];
function copyFun (obj){
var newObj = obj.constructor === Array ? []:{};
if(typeof obj !== 'object'){
return;
}
for(var i in obj){
newObj[i] = typeof obj[i] === 'object'? copyFun(obj[i]): obj[i];
}
return newObj;
}
var copyArray = copyFun(arr);
copyArray[0].number = 100;
console.log(copyArray);//[{number:100},{number:2},{number:3}]
console.log(arr);//[{number:1},{number:2},{number:3}]
7.数组去重
1. function unique(arr){
for(var i = 0;i<arr.length;i++){
for(var j = i+1;j<arr.length;j++){
if(arr[i] == arr[j]){
arr.splice(j,1);
j--;
}
}
}
return arr;
}
var arr = [1,1,2,5,6,3,5,5,6,8,9,8];
console.log(unique(arr));//[1, 2, 5, 6, 3, 8, 9]
2. function unique(arr){
var newArr = [];
for(var i = 0;i<arr.length;i++){
//如果临时数组里没有当前数组的当前值,则把当前值push到新数组里面
if(arr.indexof(arr[i]) == -1){
newArr.push(arr[i]);
}
}
return newArr;
}
3.es6
function unique(arr){
return Array.from(new Set(arr));
}
8.数组排序
1.冒泡排序
var arr = [4,1,3,7,9,0];
function sortFun (arr){
var temp;
for(var i = 0;i<arr.length-1;i++){
for(var j = 0; j<arr.length-1;j++){
if(arr[j]>arr[j+1]){
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
return arr;
}
2.快速排序
function quickSort(arr){
if(arr.length <=1){
return arr;
}
var left = [];
var right = [];
var middleIndex = parseInt(arr.length/2);
var middle = arr[middleIndex];
for(var i = 0;i<arr.length;i++){
if(i == middleIndex) continue;//必须
if(arr[i]<middle){
left.push(arr[i]);
}else{
right.push(arr[i]);
}
}
return quickSort(left).concat([middle],quickSort(right));
}
console.log(quickSort([9,2,1,5,3,0]));
3.sort排序
var arr=[1,5,7,9,16,2,4];
arr.sort(function(a,b){
return b-a; //降序排列,return a-b; —>升序排列
})
9. map和foreach的区别?
关于map(),创建一个新的数组,在callback函数中需要return
原数组不变!!
map接收一个回调(里面参数value,index,array),也可接收第二个参数thisValue;
var array = [10,34,57,43,76];
var res = array.map(function (item,index,input) {
return item*10;
})
console.log(res);
console.log(array);不变
关于forEach()
原数组改变!!
arr[].forEach(function(value,index,array){
xxxxx
});
var array = [10,34,57,43,76];
var res = array.forEach(function (item,index,input) {
input[index] = item*10;
})
console.log(res);//--> undefined;
console.log(array);//--> 通过数组索引改变了原数组;
//[100,340,570,430,760]
10. for...of 与for…in的区别
1)for of是es6新增的语法,修复了for…in的不足
2)for in循环的是key,for of循环的是value
11.var a=[0,1,2,3,4,5,6,7,8,9]要求从第0个位置开始,每隔三个插入一个“a”,使用es6实现代码。
['a','b'].entries()是对键值对的遍历=> [0,'a'],[1,'b']
例:
1):
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);//0,'a' // 1,'b'
}
2):
let letter = ['a', 'b', 'c'];
let entries = letter.entries();
console.log(entries.next().value); // [0, 'a']
console.log(entries.next().value); // [1, 'b']
function inserts(arr=[0,1,2,3,4,5,6,7,8,9]){
const res=[]
for(const [i,v] of arr.entries()){
res.push(v)
if( (i+1)%3 === 0 ){
res.push(“a”);
}
}
return res;
}
12.请分析以下代码执行结果 Console.log( ['1', '2', '3'].map(parseInt))
答案: [1, NaN, NaN]
解析:1. map函数
将数组的每个元素传递给指定的函数处理,并返回处理后的数组,所以 ['1','2','3'].map(parseInt) 就是将字符串1,2,3作为元素;0,1,2作为下标分别调用 parseInt 函数。即分别求出 parseInt('1',0), parseInt('2',1), parseInt('3',2)的结果。
2. parseInt函数(重点)
概念:以第二个参数为基数来解析第一个参数字符串,通常用来做十进制的向上取整(省略小数)如:parseInt(2.7) //结果为2
特点:接收两个参数parseInt(string,radix)
string:字母(大小写均可)、数组、特殊字符(不可放在开头,特殊字符及特殊字符后面的内容不做解析)的任意字符串,如 '2'、'2w'、'2!'
radix:解析字符串的基数,基数规则如下:
1) 区间范围介于2~36之间;
2 ) 当参数为 0,parseInt() 会根据十进制来解析;
3 ) 如果忽略该参数,默认的基数规则:
如果 string 以 "0x" 开头,parseInt() 会把 string 的其余部分解析为十六进制的整数;parseInt("0xf") //15
如果 string 以 0 开头,其后的字符解析为八进制或十六进制的数字;parseInt("08") //8
如果 string 以 1 ~ 9 的数字开头,parseInt() 将把它解析为十进制的整数;parseInt("88.99f") //88
只有字符串中的第一个数字会被返回。parseInt("10.33") //返回10;
开头和结尾的空格是允许的。parseInt(" 69 10 ") //返回69
如果字符串的第一个字符不能被转换为数字,返回 NaN。parseInt("f") //返回NaN 而parseInt("f",16) //返回15
三、再来分析一下结果
['1','2','3'].map(parseInt)即
parseInt('1',0);radix 为 0,parseInt() 会根据十进制来解析,所以结果为 1;
parseInt('2',1);radix 为 1,超出区间范围,所以结果为 NaN;
parseInt('3',2);radix 为 2,用2进制来解析,应以 0 和 1 开头,所以结果为 NaN。
例: ['1', '3', '10'].map(parseInt); ---- [1,NAN,2],
parseInt('1',0), parseInt('3',1), parseInt('10',2),其中parseInt('10',2)按二进制转换十进制,00000010转成二进制0*2^0+1*2^1=2
13.请分析以下代码执行结果
var number = 10;
function fn() {
console.log(this.number);
}
var obj = {
number: 2,
show: function(fn) {
this.number = 3;
fn();
arguments[0]();
}
};
obj.show(fn);//10
14.请写出 inner 的实际高度。
<style>
.outer {
width: 200px;
height: 100px;
}
.inner {
width: 60px;
height: 60px;
padding-top: 20%;
}
</style>
<div class="outer"><div class="inner"></div></div>
//100px,解析padding-top按照父元素的width来计算,也就是默认书写方向,若.outer增加 writing-mode: vertical-lr;则按照父元素的高度来计算
15.如何垂直居中一个img
#img /**<img>的容器设置如下**/
{
display:table-cell;
text-align:center;
vertical-align:middle;
}
16.重绘(Repaint)和回流(Reflow)
回流必将引起重绘,重绘不一定会引起回流
会导致回流的操作:
页面首次渲染
浏览器窗口大小发生改变
元素尺寸或位置发生改变
元素内容变化(文字数量或图片大小等等)
元素字体大小变化
添加或者删除可见的DOM元素
激活CSS伪类(例如::hover)
查询某些属性或调用某些方法
一些常用且会导致回流的属性和方法:
clientWidth、clientHeight、clientTop、clientLeft
offsetWidth、offsetHeight、offsetTop、offsetLeft
scrollWidth、scrollHeight、scrollTop、scrollLeft
scrollIntoView()、scrollIntoViewIfNeeded()
getComputedStyle()
getBoundingClientRect()
scrollTo()
重绘:
当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘
17.什么是事件代理?(同第四步)
事件代理(Event Delegation)又称事件委托。是JavaScript常用绑定事件的常用方式。‘事件代理’是把原本需要绑定的事件委托给父元素,让父元素担任监听的职务。事件代理的原理是DOM元素的事件冒泡机制。使用事件代理的好处是提高性能。
`可以大量节省内存占用,减少事件注册,比如在table上代理所有td的click事件就非常棒`
`可以实现当新增子对象时无需再次对其绑定`
on、delegate、bind、live
18.继承
1.构造继承
2.原型继承
3.实例继承
4.拷贝继承
19.new操作符做了什么?
创建了一个空对象,并将this引用指向该对象,同时还继承了该构造函数的原型。
* 构造函数的属性和方法加入到this引用的该实例对象中。
* 新创建的对象由this引用,最后隐式的返回this。
20.常见浏览器兼容问题
- 常见浏览器之间的padding和margin是不同的。解决方案设置*{padding:0;margin:0}
- 24位的png图片,ie6中不兼容透明底儿
解决方式:使用png透明图片呗,但是需要注意的是24位的PNG图片在IE6是不支持的
解决方案有两种:1.使用8位的PNG图片;2.为IE6准备一套特殊的图片
- ie8以下无法使用e.stopPropagation()阻止事件冒泡,解决方案: e.cancelBubble = false;
// 以下为兼容写法
event.stopPropagation ? event.stopPropagation() : (event.cancelBubble = false);
- ie8以下无法使用e.preventDefault()阻止浏览器默认事件,解决方案: e.returnValue = false;
// 以下为兼容写法
event.preventDefault ? event.preventDefault() : (event.returnValue = false);
- 事件监听的兼容:IE下不支持addEventListener,解决方案:attachEvent
- event.target的兼容,引发事件的DOM元素。
ie6789不支持event.target,解决方案:event.srcElement
// 以下为兼容写法
target = event.target || event.srcElement;
21.快速的让一个数组乱序
var arr = [1,2,3,4,5,6,7,8,9,10];
arr.sort(function(){
return Math.random()-0.5;
})
console.log(arr)
22.如何实现图片滚动懒加载
原理:浏览器在加载html时遇到src会请求src里面的内容,若需加载10000张图片,等待这些图片加载完成耗时久,并且加载图片是同步的,会阻塞浏览器继续向下执行。用户体验是很差的。
//设置图片的dataSrc属性
<div @scroll="lazyLoad" ref="lazy">
<img v-for="(src, index) in imgs" src="##" :dataSrc="src" :key="index">
<!--more img-->
</div>
改变图片src
监听最外层div的滚动事件,触发滚动时遍历图片检测图片位置,若在可视区内则显示
loadImg() {
var img = this.$refs.lazy.getElementsByClassName("lazyImg");
// 已滚动高度+可视区高度
var top = this.$refs.lazy.scrollTop + this.$refs.lazy.clientHeight;
for(var i = 0; i < img.length; i++) {
if(img[i].offsetTop <= top) { // 在可视区内则显示图片
img[i].src = img[i].getAttribute("datasrc");
}
}
},
lazyLoad() {
this.loadImg();
}
这样实现了图片懒加载,但是滚动过程中会不断触发lazyLoad对图片做一个遍历并判断,那么就会做无数次for循环,更可怕的是,修改一次src会发送一个请求,在滚动的时候我们的for循环每次都从头判断并修改src请求图片,那么请求次数可想而知
函数防抖
如果在滚动过程中不断触发遍历并判断图片是否在可视区的监听事件,会耗费很大的性能,这里采用函数防抖:当用户停止滚动时统一遍历判断图片位置。
debounce(fn) {
// 函数防抖:用户停止操作之后触发
clearTimeout(this.timer);
this.timer = setTimeout(() => {
fn();
}, 1000);
}
lazyLoad() {
this.debounce(this.loadImg);
}
详情参考[ www.cnblogs.com/wind-lanyan…]
23.节流和防抖
函数节流和防抖都是防止某一时间频繁触发。
节流是按时间间隔执行(持续触发事件时,一段时间内只调用一次事件处理函数);
节流应用场景:抢购(频繁点击)
html:
<button id="btn">点击抢购</button>
javascript如下:
var btn = document.getElementById('btn');
btn.onclick = throttle(ajaxFun,1000);
//节流函数
function throttle(fn,wait){
console.log('throttle')
var lastTime = 0;
return function(){
var nowTime = new Date().getTime();
//在这个时间内我才触发事件处理函数,是不是起到节流的目的呢。
if (nowTime - lastTime > wait) {
fn.apply(this, arguments)
lastTime = nowTime
}
}
}
function ajaxFun(){
console.log('事件处理函数')
}
防抖是某一时间段内只执行一次(即:持续触发时,一段时间内没有触发该事件,则会执行处理函数,如果设定的时间来到之前再次触发则重新开始延时)。
防抖应用场景:1.输入框持续输入,类似百度联想词,监听keyup事件,监听文字输入并调用接口进行模糊匹配。2.通过监听scroll事件,检测滚动事件,根据滚动位置显示返回顶部按钮。3.通过监听 resize 事件,对某些自适应页面调整DOM的渲染等
html:
<input type="text" id="input" placeholder="联想关键词">
javascript:
var input = document.getElementById('input');
input.oninput= debounce(ajaxFun,800);
function debounce (fn, delay){
var timer = null;
return function(){
clearTimeout(timer);
timer = setTimeout(function(){
fn.apply(this,arguments);
},delay);
}
}
24.隐式转换和显式转换
显式转换:toString(),Number(),Boolean()可以将数据转换成字符串,数字,布尔值,这些都属于显式转化;
隐式转换:
-
运算相关的操作符包括 +、-、+=、++、* 、/、%、<<、& 等。
-
数据比较相关的操作符包括 >、<、== 、<=、>=、===。
-
逻辑判断相关的操作符包括 &&、!、||、三目运算符。
25.为什么使用promise
大家都知道做前端开发的时候最让人头痛的就是处理异步请求的情况,在请求到的成功回调函数里继续写函数,长此以往形成了回调地狱。 如下:
function load() {
$.ajax({
url: 'xxx.com',
data: 'jsonp',
success: function(res) {
init(res, function(res) {
render(res, function(res) {
// 一层一层又一层
});
});
}
}
}
load();
这样的代码看层级少了当然还是可以凑合看的,但是多起来的话,那就难以维护了
先介绍一下Promise的三种状态:
- 1 Pending 创建Promise对象时的初始状态
- 2 Fulfilled 成功时的状态
- 3 Rejected 失败时的状态
// then方法是用的最多的了
// 按照then来执行成功和失败的回调函数
function load() {
return new Promise((resolve, reject) => {
$.ajax({
url: 'xxx.com',
data: 'jsonp',
success: function(res) {
resolve(res);
},
error: function(err) {
reject(err);
}
});
});
}
//使用
load().then(data => {
console.log(data); // 请求到的数据
console.log('请求数据成功');
}, err => {
console.log('请求失败');
});
26.Pomise.all的使用
Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值
let p1 = new Promise((resolve, reject) => {
resolve('成功了')
})
let p2 = new Promise((resolve, reject) => {
resolve('success')
})
let p3 = Promse.reject('失败')
Promise.all([p1, p2]).then((result) => {
console.log(result) //['成功了', 'success']
}).catch((error) => {
console.log(error)
})
Promise.all([p1,p3,p2]).then((result) => {
console.log(result)
}).catch((error) => {
console.log(error) // 失败了,打出 '失败'
})
Promse.all在处理多个异步处理时非常有用,比如说一个页面上需要等两个或多个ajax的数据回来以后才正常显示,在此之前只显示loading图标
例如:
let wake = (time) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`${time / 1000}秒后醒来`)
}, time)
})
}
let p1 = wake(3000)
let p2 = wake(2000)
Promise.all([p1, p2]).then((result) => {
console.log(result) // [ '3秒后醒来', '2秒后醒来' ]
}).catch((error) => {
console.log(error)
})
需要特别注意的是,Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的,即p1的结果在前,即便p1的结果获取的比p2要晚。
27.Promise.race的使用
顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
},1000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('failed')
}, 500)
})
Promise.race([p1, p2]).then((result) => {
console.log(result)
}).catch((error) => {
console.log(error) // 打开的是 'failed'
})
28. 在浏览器地址栏输入 www.baidu.com,敲下回车,会发生什么??
- DNS域名解析
- 寻找IP对应的服务器
- 发起TCP的三次握手
- 向服务器发送请求并接收数据,请求入口文件index.html
- 浏览器开始渲染DOM 》》 渲染结束