面试之JavaScript篇
原文链接:segmentfault.com
此文主旨是记录面试中遇到的面试题,包括js中常见,易错,重要知识点
window.onload和document.ready的区别
window.onload是在页面中包含图片在内的素有元素全部加载完成; document.ready是文档结构加载完成,但不包含图片,其他媒体文件; 在jQuery中会看到$(function(){})和$(document).ready(function(){}),是在DOM树加载完成之后执行; window.onload是在DOM树加载完以及所有文件加载完成才执行,因此慢于document.ready。
数组去重
var arr = ['a','bb','22','a','yuci','haha','22'];
es6的set()方法
var unique = new Set(arr); console.log(Array.from(unique));
使用push()
var arr2 = []; for(var i = 0; i < arr.length; i++) { (function(i) { if(arr2.indexOf(arr[i]) == -1) { //不包含某个值则返回-1 arr2.push(arr[i]); } }(i)) } console.log(arr2);
//如果当前数组的第i项在当前数组中第一次出现的位置不是i,那么表示第i项是重复的,忽略掉。否则存入结果数组 var arr3 = [arr[0]]; for(var i = 1; i < arr.length; i++) { (function(i) { if(arr.indexOf(arr[i]) == i) { arr3.push(arr[i]); } }(i)) } console.log(arr3);
排序去除相邻重复元素
var arrSort = arr.sort(); var arr4 = []; for(let i = 0; i< arrSort.length; i++) { if(arrSort[i] != arrSort[i+1]) { arr4.push(arrSort[i]); } } console.log(arr4);
使用splice()
var len = arr.length; for(let i = 0; i < len; i++) { for(let j = i + 1; j < len; j++) { if(arr[i] === arr[j]) { arr.splice(i,1); len--; j--; } } } console.log(arr);
事件委托
得益于事件冒泡,当多个元素有相同的事件,将事件绑定在父元素
var oUl = document.getElementById('oul'); oUl.addEventListener('click', function(e) { var e = e||window.event; var tar = e.target; if(tar.nodeName === 'LI') { alert(tar.innerHTML); } })
更详细请看:事件委托
判断变量类型
typeof()用于判断简单数据;
判断一个变量是对象还是数组使用instanceof,constructor或Object.prototype.toString.call();
更详细请看:判断数据类型
同步和异步(简要阐述)
同步:由于js单线程,同步任务都在主线程上排队执行,前面任务没执行完成,后面的任务会一直等待; 异步:不进入主线程,进入任务队列,等待主线程任务执行完成,开始执行。最基础的异步操作setTimeout和setInterval,等待主线程任务执行完,在开始执行里面的函数;
更详细请看:js运行机制
返回false的几种情况
false,null,0,“”,undefined,NaN
js类型值的区别
存储地: 简单数据类型:存储在栈中; 引用数据类型:存储在堆中,在栈中存储了指针,指向存储在堆中的地址,解释器会先检索在栈中的地址,从堆中获得实体; 大小: 简单数据类型:大小固定,占用空间小,频繁使用,所以存储在栈中; 引用数据类型:大小不固定,占用空间大;
闭包
何为闭包:有权访问另一个作用域中变量的函数 闭包特性:可实现函数外访问函数内变量,外层变量可以不被垃圾回收机制回收 为什么?怎么解决?
for(var i = 0; i < 10; i++) { setTimeout(function() { console.log(i); }, 1000); }
输出结果都为10,因为for()循环过程中每次传值,匿名函数并没有执行,相当于执行10次function(){console.log(i);},循环结束i变为10,所以输出全部为10;
使用闭包,自执行匿名函数包裹:
for(var i = 0; i < 10; i++) { (function(j) { setTimeout(function() { console.log(j); }, 1000); })(i); }
外部匿名函数立即执行,把 i 作为参数,赋值给 j ,因为是立即执行,所以每次循环输出不同值。
引用外层变量不被回收,会相比其他函数占用更高内存,使用不当容易造成内存泄漏。
this的指向
全局范围:指向window(严格模式下不存在全局变量,指向undefined); 普通函数调用:指向window; 对象方法调用:指向最后调用它的对象; 构造函数调用:指向new出来的对象; 显示设置this:call,apply方法显示将this指向第一个参数指明的对象
new具体做了些什么
创建一个新对象foo; 并将它的__proto__指向其构造函数的prototype,foo.__proto__ = Foo.prototype; 动态将this指向新对象,Foo.apply(foo,arguments); 执行函数体中的代码; 放回新对象foo;
原型和原型链
创建一个函数就会为其创建一个prototype属性,指向这个函数的原型对象,原型对象会自动获得constructor属性,指向prototype属性所在函数。
Function.prototype.a = "a"; Object.prototype.b = "b"; function Person(){} console.log(Person); //function Person() let p = new Person(); console.log(p); //Person {} 对象 console.log(p.a); //undefined console.log(p.b); //b
p.__proto__ === Person.prototype;Person.prototype.constructor === Person
当调用某种方法或查找某种属性时,首先会在自身调用和查找,如果自身并没有该属性或方法,则会去它的__proto__属性中调用查找,也就是它构造函数的prototype中调用查找,如果构造函数中也没有该属性方法,则会去构造函数的隐式原型中查找,一直到null,就这样形成原型链。
更多有关原型请看:原型和原型链
继承方式
原型链继承:
Child()的原型作为Parent()的实例来继承Parent()的方法属性
因为所有实例都继承原型方法属性,其中一个实例对原型属性值更改后,所有实例调用该属性的值全部更改
function Parent() {} Parent.prototype.parentSay = function() { return 'i am parent'; } function Child() {} Child.prototype.childSay = function() { return 'i am child'; } Child.prototype = new Parent(); var par = new Parent(); var kid = new Child(); console.log(kid.parentSay()); //i am parent
构造函数继承:
在子类的构造函数内部通过call或apply来调用父类构造函数
无法实现函数的复用
function People() { this.name = ['zhangsan','lisi','wangwu']; } function Person() { People.call(this); } var per1 = new Person(); per1.name.push('zhanliu'); console.log(per1.name); //["zhangsan", "lisi", "wangwu", "zhanliu"] var per2 = new Person(); console.log(per2.name); //["zhangsan", "lisi", "wangwu"]
组合继承:
将原型链继承和构造函数继承结合,最常用的继承模式
原型链继承共享的属性和方法,构造函数继承实例属性
function People(num) { this.num = num; this.name = ['zhangsan','lisi','wangwu']; } People.prototype.numCount = function() { console.log(this.num); } function Person(num) { People.call(this, num); } //继承方式 Person.prototype = new People(); Person.prototype.constructor = Person; var per1 = new Person(10); per1.name.push('zhaoliu'); console.log(per1.name); //["zhangsan", "lisi", "wangwu", "zhanliu"] per1.numCount(); //10 var per2 = new Person(20); console.log(per2.name); //["zhangsan", "lisi", "wangwu"] per2.numCount(); //20
更多继承方式请看:继承方式
数组常用方法
改变原数组:
尾部删除pop(),尾部添加push(),头部删除shift(),头部添加unshift(),排序sort(),颠倒数组元素reverse(),删除或插入元素splice();
不会改变元素组:
合并数组concat(),拼接数组元素join(),截取元素slice(),indexOf(),lastIndexOf(),toString()
更详细数组方法总结请看:Array数组方法总结
数据存储
Cookie:用于客户端与服务端通信,也具有本地存储的功能 localStorage,sessionStorage:专门用于存储 区别: 大小:Cookie容量为4K,因为用于客户端与服务端通信,所有http都携带,如果太大会降低效率; localStorage,sessionStorage大小为5M。 失效时间:Cookie会在浏览器关闭时删除,除非主动设置删除时间;localStorage一直都在直到用户主动删除或清除浏览器缓存;sessionStorage在浏览器关闭时删除。
结束语:
如有错误,欢迎指正
热门评论
我觉得很棒了,怎么没有赞。超级实用啊啊啊。