前端面试题集锦——JavaScript
Posted 合众汇才
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端面试题集锦——JavaScript相关的知识,希望对你有一定的参考价值。
javascript
栈和队列的区别?
栈的插入和删除操作都是在一端进行的,而队列的操作却是在两端进行的。
队列先进先出,栈先进后出。
栈只允许在表尾一端进行插入和删除,而队列只允许在表尾一端进行插入,在表头一端进行删除
栈和堆的区别?
栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。
堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。
堆(数据结构):堆可以被看成是一棵树,如:堆排序;
栈(数据结构):一种先进后出的数据结构。
浏览器端的js包括哪几个部分?
核心( ECMAScript) , 文档对象模型(DOM), 浏览器对象模型(BOM)
介绍js有哪些内置对象?
Object 是 JavaScript 中所有对象的父对象
数据封装类对象:Object、Array、Boolean、Number 和 String
其他对象:Function、Arguments、Math、Date、RegExp、Error
基本类型与引用类型有什么区别?
基本数据类型有: Undefined、Null、Boolean、Number、String
引用类型则有: Object, Array, Date, RegExp, Function
区别:
存储
基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中
引用类型的值是对象, 保存在堆内存中. 包含引用类型的变量实际上包含的并不是对象本身, 而是一个指向改对象的指针
复制
从一个变量向另一个变量复制基本类型的值, 会创建这个值的一个副本
从一个变量向另一个变量复制引用类型的值, 复制的其实是指针, 因此两个变量最终都指向同一个对象
检测类型
确定一个值是哪种基本类型可以用
typeof
操作符确定一个值是哪种引用类型可以使用
instanceof
操作符
JavaScript有几种类型的值?,你能画一下他们的内存图吗?
栈:原始数据类型(Undefined,Null,Boolean,Number、String)
堆:引用数据类型(对象、数组和函数)
两种类型的区别是:存储位置不同;
原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
null,undefined 的区别?
null
表示一个对象被定义了,值为“空值”;
undefined
表示不存在这个值。
typeof undefined //"undefined"
undefined :是一个表示"无"的原始值或者说表示"缺少值",就是此处应该有一个值,但是还没有定义。当尝试读取时会返回 undefined;
例如变量被声明了,但没有赋值时,就等于undefined
typeof null //"object"
null : 是一个对象(空对象, 没有任何属性和方法);
例如作为函数的参数,表示该函数的参数不是对象;
注意:在验证null时,一定要使用 ===
,因为 ==
无法分别 null 和 undefined
再来一个例子:
null
Q:有张三这个人么?
A:有!
Q:张三有房子么?
A:没有!
undefined
Q:有张三这个人么?
A:没有!
参考阅读:undefined与null的区别
说几条写JavaScript的基本规范?
不要在同一行声明多个变量。
请使用
===
/!==
来比较true/false或者数值使用对象字面量替代new Array这种形式
不要使用全局函数。
Switch语句必须带有default分支
函数不应该有时候有返回值,有时候没有返回值。
for循环必须使用大括号
if语句必须使用大括号
for-in循环中的变量 应该使用var关键字明确限定作用域,从而避免作用域污染。
eval是做什么的?
它的功能是把对应的字符串解析成JS代码并运行;
应该避免使用eval,不安全,非常耗性能(2次,一次解析成js语句,一次执行)。
由JSON字符串转换为JSON对象的时候可以用eval,var obj =eval('('+ str +')');
javascript 代码中的"use strict";
是什么意思 ? 使用它区别是什么?
use strict
是一种ECMAscript 5 添加的(严格)运行模式,这种模式使得 Javascript 在更严格的条件下运行。
使用它区别是:
使JS编码更加规范化的模式,消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为。
默认支持的糟糕特性都会被禁用,比如不能用with,也不能在意外的情况下给全局变量赋值;
全局变量的显示声明,函数必须声明在顶层,不允许在非函数代码块内声明函数,
arguments.callee
也不允许使用;消除代码运行的一些不安全之处,保证代码运行的安全,限制函数中的arguments修改,严格模式下的eval函数的行为和非严格模式的也不相同;
提高编译器效率,增加运行速度;
为未来新版本的Javascript标准化做铺垫。
["1", "2", "3"].map(parseInt)
答案是多少?
[1, NaN, NaN]
因为 parseInt
需要两个参数 (val, radix)
,其中 radix 表示解析时用的基数。
map 传了 3 个 (element, index, array),对应的 radix 不合法导致解析失败。
parseInt方法将会通过以下方式被调用
parseInt("1", 0)
parseInt("2", 1)
parseInt("3", 2)
parseInt的第二个参数radix为0时,ECMAScript5将string作为十进制数字的字符串解析;
parseInt的第二个参数radix为1时,解析结果为NaN;
parseInt的第二个参数radix在2—36之间时,如果string参数的第一个字符(除空白以外),不属于radix指定进制下的字符,解析结果为NaN。parseInt("3", 2)
执行时,由于"3"不属于二进制字符,解析结果为NaN。
快速 排序的思想并实现一个快排?
“快速排序”的思想很简单,整个排序过程只需要三步:
(1)在数据集之中,找一个基准点
(2)建立两个数组,分别存储左边和右边的数组
(3)利用递归进行下次比较
<script type="text/javascript">
function quickSort(arr){
if(arr.length<=1){
return arr;//如果数组只有一个数,就直接返回;
}
var num = Math.floor(arr.length/2);//找到中间数的索引值,如果是浮点数,则向下取整
var numValue = arr.splice(num,1);//找到中间数的值
var left = [];
var right = [];
for(var i=0;i<arr.length;i++){
if(arr[i]<numValue){
left.push(arr[i]);//基准点的左边的数传到左边数组
}
else{
right.push(arr[i]);//基准点的右边的数传到右边数组
}
}
return quickSort(left).concat([numValue],quickSort(right));//递归不断重复比较
}
alert(quickSort([32,45,37,16,2,87]));//弹出“2,16,32,37,45,87”
</script>
Javascript作用链域?
全局函数无法查看局部函数的内部细节,但局部函数可以查看其上层的函数细节,直至全局细节。
当需要从局部函数查找某一属性或方法时,如果当前作用域没有找到,就会上溯到上层作用域查找,直至全局函数,这种组织形式就是作用域链。
作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window对象即被终止,作用域链向下访问变量是不被允许的。
什么是闭包(closure),为什么要用它?
闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域,将函数内部的变量和方法传递到外部。
使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。在js中,函数即闭包,只有函数才会产生作用域的概念
闭包的特性:
函数内再嵌套函数
内部函数可以引用外层的参数和变量
参数和变量不会被垃圾回收机制回收
//li节点的onclick事件都能正确的弹出当前被点击的li索引
<ul id="testUL">
<li> index = 0</li>
<li> index = 1</li>
<li> index = 2</li>
<li> index = 3</li>
</ul>
<script type="text/javascript">
var nodes = document.getElementsByTagName("li");
for(i = 0;i<nodes.length;i+= 1){
nodes[i].onclick = function(){
console.log(i+1);//不用闭包的话,值每次都是4
}(i);
}
</script>
执行say667()后,say667()闭包内部变量会存在,而闭包内部函数的内部变量不会存在
使得Javascript的垃圾回收机制GC不会收回say667()所占用的资源
因为say667()的内部函数的执行需要依赖say667()中的变量
这是对闭包作用的非常直白的描述
unction say667() {
// Local variable that ends up within closure
var num = 666;
var sayAlert = function() {
alert(num);
}
num++;
return sayAlert;
}
var sayAlert = say667();
sayAlert()//执行结果应该弹出的667
什么是伪数组
伪数组是能通过Array.prototype.slice
转换为真正的数组的带有length属性的对象
比如arguments对象,还有像调用getElementsByTagName,document.childNodes之类的,它们都返回NodeList对象都属于伪数组
我们可以通过Array.prototype.slice.call(fakeArray)
将伪数组转变为真正的Array对象: 返回新数组而不会修改原数组
javascript创建对象的几种方式?
javascript创建对象简单的说,无非就是使用内置对象或各种自定义对象,当然还可以用JSON;但写法有很多种,也能混合使用。
1、对象字面量的方式
person={firstname:"Mark",lastname:"Yun",age:25,eyecolor:"black"};
2、用function来模拟无参的构造函数
function Person(){}
var person=new Person();//定义一个function,如果使用new"实例化",该function可以看作是一个Class
person.name="Mark";
person.age="25";
person.work=function(){
alert(person.name+" hello...");
}
person.work();
3、用function来模拟参构造函数来实现(用this关键字定义构造的上下文属性)
function Pet(name,age,hobby){
this.name=name;//this作用域:当前对象
this.age=age;
this.hobby=hobby;
this.eat=function(){
alert("我叫"+this.name+",我喜欢"+this.hobby+",是个程序员");
}
}
var maidou =new Pet("麦兜",25,"coding");//实例化、创建对象
maidou.eat();//调用eat方法
4、用工厂方式来创建(内置对象)
var wcDog =new Object();
wcDog.name="旺财";
wcDog.age=3;
wcDog.work=function(){
alert("我是"+wcDog.name+",汪汪汪......");
}
wcDog.work();
5、用原型方式来创建
function Dog(){
}
Dog.prototype.name="旺财";
Dog.prototype.eat=function(){
alert(this.name+"是个吃货");
}
var wangcai =new Dog();
wangcai.eat();
6、用混合方式来创建
function Car(name,price){
this.name=name;
this.price=price;
}
Car.prototype.sell=function(){
alert("我是"+this.name+",我现在卖"+this.price+"万元");
}
var camry =new Car("凯美瑞",27);
camry.sell();
谈谈This对象的理解。
this总是指向函数的直接调用者(而非间接调用者);
如果有new关键字,this指向new出来的那个对象;
在事件中,this指向触发这个事件的对象,特殊的是,IE中的attachEvent中的this总是指向全局对象Window;
JavaScript原型,原型链? 有什么特点?
每个对象都会在其内部初始化一个属性,就是prototype(原型),当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去prototype里找这个属性,这个prototype又会有自己的prototype,于是就这样一直找下去,也就是我们平时所说的原型链的概念。
关系:instance.constructor.prototype = instance.__proto__
特点:
JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。
当我们需要一个属性的时,Javascript引擎会先看当前对象中是否有这个属性, 如果没有的话,就会查找他的Prototype对象是否有这个属性,如此递推下去,一直检索到 Object 内建对象。
```
function Func(){}
Func.prototype.name = "Sean";
Func.prototype.getInfo = function() {
return this.name;
}
var person = new Func();//现在可以参考var person = Object.create(oldObject);
console.log(person.getInfo());//它拥有了Func的属性和方法
//"Sean"
console.log(Func.prototype);
// Func { name="Sean", getInfo=function()}
```
javascript中call()和apply()方法的区别?
ECMAScript规范给所有函数都定义了这两个方法call()和apply()。
call()和apply()都能继承另一个对象的方法和属性,区别在于参数列表不一样
Function.call(obj, arg1, arg2,...)
和Function.apply(obj, args)
的第一个参数都是要调用的函数的对象,call()的剩余参数是传递给要调用的函数的值,而apply()只有两个参数,第一个是对象,第二个是数组,这个数组就是该函数的参数。
function add(a,b)
{
alert(a+b);
}
function sub(a,b)
{
alert(a-b);
}
add.call(sub,3,1);
//alert(4)
Javascript如何实现继承?
构造继承
原型继承
实例继承
拷贝继承
参考:构造函数的继承,非构造函数的继承;
如何判断一个对象是否属于某个类?
使用instanceof
if(a instanceof Person){
alert('yes');
}
new操作符具体干了什么呢?
创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。
属性和方法被加入到 this 引用的对象中。
新创建的对象由 this 所引用,并且最后隐式的返回 this 。
var obj = {};
obj.__proto__ = Base.prototype;
Base.call(obj);
Javascript中,有一个函数,执行时对象查找时,永远不会去查找原型,这个函数是?
hasOwnProperty
javaScript中hasOwnProperty函数方法是返回一个布尔值,指出一个对象是否具有指定名称的属性。此方法无法检查该对象的原型链中是否具有该属性;该属性必须是对象本身的一个成员。
使用方法:object.hasOwnProperty(proName)
其中参数object是必选项。一个对象的实例。
proName是必选项。一个属性名称的字符串值。
如果 object 具有指定名称的属性,那么JavaScript中hasOwnProperty函数方法返回 true,反之则返回 false。
JS 怎么实现一个类?怎么实例化这个类?
//混合的构造函数/原型方式
//创建对象
function Card(sID,ourName){
this.ID = sID;
this.OurName = ourName;
this.Balance = 0;
}
Card.prototype.SaveMoney = function(money){
this.Balance += money;
};
Card.prototype.ShowBalance = function(){
alert(this.Balance);
};
//使用对象
var cardAA = new Card(1000,'james');
var cardBB = new Card(1001,'sun');
cardAA.SaveMoney(30);
cardBB.SaveMoney(80);
cardAA.ShowBalance();
cardBB.ShowBalance();
事件是?IE与火狐的事件机制有什么区别? 如何阻止冒泡?
我们在网页中的某个操作(有的操作对应多个事件)。例如:当我们点击一个按钮就会产生一个事件。是可以被 JavaScript 侦测到的行为。
事件处理机制:IE是事件冒泡、Firefox同时支持两种事件模型,也就是:捕获型事件和冒泡型事件;
ev.stopPropagation();
(旧ie的方法ev.cancelBubble = true;
)
写一个通用的事件侦听器函数。
// event(事件)工具集,来源:github.com/markyun
markyun.Event = {
// 页面加载完成后
readyEvent : function(fn) {
if (fn==null) {
fn=document;
}
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = fn;
} else {
window.onload = function() {
oldonload();
fn();
};
}
},
// 视能力分别使用dom0||dom2||IE方式 来绑定事件
// 参数: 操作的元素,事件名称 ,事件处理程序
addEvent : function(element, type, handler) {
if (element.addEventListener) {
//事件类型、需要执行的函数、是否捕捉
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent('on' + type, function() {
handler.call(element);
});
} else {
element['on' + type] = handler;
}
},
// 移除事件
removeEvent : function(element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false);
} else if (element.datachEvent) {
element.detachEvent('on' + type, handler);
} else {
element['on' + type] = null;
}
},
// 阻止事件 (主要是事件冒泡,因为IE不支持事件捕获)
stopPropagation : function(ev) {
if (ev.stopPropagation) {
ev.stopPropagation();
} else {
ev.cancelBubble = true;
}
},
// 取消事件的默认行为
preventDefault : function(event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
// 获取事件目标
getTarget : function(event) {
return event.target || event.srcElement;
},
// 获取event对象的引用,取到事件的所有信息,确保随时能使用event;
getEvent : function(e) {
var ev = e || window.event;
if (!ev) {
var c = this.getEvent.caller;
while (c) {
ev = c.arguments[0];
if (ev && Event == ev.constructor) {
break;
}
c = c.caller;
}
}
return ev;
}
};
事件委托(事件代理)是什么
事件委托利用了事件冒泡, 只指定一个事件处理程序, 就可以管理某一类型的所有事件.
例:
html部分: 要点击li弹出其id
<ul id="list">
<li id="li-1">Li 2</li>
<li id="li-2">Li 3</li>
<li id="li-3">Li 4</li>
<li id="li-4">Li 5</li>
<li id="li-5">Li 6</li>
<li id="li-6">Li 7</li>
</ul>
//js部分
document.getElementById("list").addHandler("click", function(e){
var e = e || window.event;
var target = e.target || e.srcElement;
if(target.nodeName.toUpperCase == "LI"){
console.log("List item", e,target.id, "was clicked!");
}
});
我们给一个dom同时绑定两个点击事件,一个用捕获,一个用冒泡。会执行几次事件,会先执行冒泡还是捕获?
绑定在目标元素上的事件是按照绑定的顺序执行的!
即: 绑定在被点击元素的事件是按照代码顺序发生,其他元素通过冒泡或者捕获“感知”的事件,按照W3C的标准,先发生捕获事件,后发生冒泡事件。所有事件的顺序是:其他元素捕获阶段事件 -> 本元素代码顺序事件 -> 其他元素冒泡阶段事件 。
JSON 的了解?
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。
它是基于JavaScript的一个子集。数据格式简单, 易于读写, 占用带宽小
如:{"age":"12", "name":"back"}
//JSON字符串转换为JSON对象:
var obj =eval('('+ str +')');
var obj = str.parseJSON();
var obj = JSON.parse(str);
//JSON对象转换为JSON字符串:
var last=obj.toJSONString();
var last=JSON.stringify(obj);
XML和JSON的区别?
(1) 数据体积方面。
JSON相对于XML来讲,数据的体积小,传递的速度更快些。
(2) 数据交互方面。
JSON与JavaScript的交互更加方便,更容易解析处理,更好的数据交互。
(3) 数据描述方面。
JSON对数据的描述性比XML较差。
(4) 传输速度方面。
JSON的速度要远远快于XML。
同步和异步的区别?
同步的概念应该是来自于OS中关于同步的概念:不同进程为协同完成某项工作而在先后次序上调整(通过阻塞,唤醒等方式).同步强调的是顺序性.谁先谁后.异步则不存在这种顺序性.
同步:浏览器访问服务器请求,用户看得到页面刷新,重新发请求,等请求完,页面刷新,新内容出现,用户看到新内容,进行下一步操作。
异步:浏览器访问服务器请求,用户正常操作,浏览器后端进行请求。等请求完,页面不刷新,新内容也会出现,用户看到新内容。
异步加载JS的方式有哪些?
(1) defer,只支持IE,并行加载js文件,会按照页面上script标签的顺序执行
(2) async,并行加载js文件,下载完成立即执行,不会按照页面上script标签的顺序执行
(3) 创建script,插入到DOM中,加载完毕后callBack
(4) 按需异步载入js
Ajax 是什么? 如何创建一个Ajax?
ajax的全称:Asynchronous Javascript And XML。
异步传输+js+xml。
所谓异步,在这里简单地解释就是:向服务器发送请求的时候,我们不必等待结果,而是可以同时做其他的事情,等到有了结果它自己会根据设定进行后续操作,与此同时,页面是不会发生整页刷新的,提高了用户体验。
创建ajax过程:
创建XMLHttpRequest对象,也就是创建一个异步调用对象
创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息
设置响应HTTP请求状态变化的函数
发送HTTP请求
获取异步调用返回的数据
使用JavaScript和DOM实现局部刷新
Ajax可以实现动态不刷新(局部刷新)
readyState属性 状态 有5个可取值: 0=未初始化 ,1=启动 2=发送,3=接收,4=完成
优点:
可以使得页面不重载全部内容的情况下加载局部内容,降低数据传输量
避免用户不断刷新或者跳转页面,提高用户体验
缺点:
对搜索引擎不友好
ajax不支持浏览器back按钮
可能造成请求数的增加
跨域问题限制
安全问题,AJAX暴露了与服务器交互的细节
Ajax 解决浏览器缓存问题?
在ajax发送请求前加上
anyAjaxObj.setRequestHeader("If-Modified-Since","0")
。在ajax发送请求前加上
anyAjaxObj.setRequestHeader("Cache-Control","no-cache")
。在URL后面加上一个随机数:
"fresh=" + Math.random();
。在URL后面加上时间搓:
"nowtime=" + new Date().getTime();
。如果是使用jQuery,直接这样就可以了
$.ajaxSetup({cache:false})
。这样页面的所有ajax都会执行这条语句就是不需要保存缓存记录。
ajax请求时,如何解释json数据
使用eval、parse。鉴于安全性考虑 使用parse更靠谱
javascript的本地对象,内置对象和宿主对象
本地对象为array obj regexp等可以new实例化
内置对象为gload Math 等不可以实例化的
宿主为浏览器自带的document,window 等
什么是window对象? 什么是document对象?
Window – 代表浏览器中一个打开的窗口
document对象 – 代表整个HTML 文档,可用来访问页面中的所有元素。
documen.write和 innerHTML的区别
document.write只能重绘整个页面
innerHTML可以重绘页面的一部分
DOM操作——怎样添加、移除、移动、复制、创建和查找节点?
(1)创建新节点
createDocumentFragment() //创建一个DOM片段
createElement() //创建一个具体的元素
createTextNode() //创建一个文本节点
(2)添加、移除、替换、插入
appendChild()
removeChild()
replaceChild()
insertBefore() //在已有的子节点前插入一个新的子节点
(3)查找
getElementsByTagName() //通过标签名称
getElementsByName() //通过元素的Name属性的值(IE容错能力较强,会得到一个数组,其中包括id等于name值的)
getElementById() //通过元素Id,唯一性
document load 和document ready的区别
页面加载完成有两种事件:
ready,表示文档结构已经加载完成(不包含图片等非文字媒体文件)
onload,指示页面包含图片等文件在内的所有元素都加载完成。
Document.onload 是在结构和样式加载完才执行js
Document.ready原生中没有这个方法,jquery中有 $().ready(function)
一般情况下,一个页面响应加载的顺序是,域名解析-加载html-加载js和css-加载图片等其他信息。
[].forEach.call($$("*"),function(a){a.style.outline="1px solid #"+(~~(Math.random()*(1<<24))).toString(16)})
能解释一下这段代码的意思吗?
[].forEach.call()
–调用引用数组的forEach方法
$$("")
–等价于document.querySelectortAll("*")
~~a
–等价于parseInt(a)
1<<24
–对二进数1小数点右移24位
写一个获取非行间样式的函数
function getStyle(obj,attr,value)
{
if(!value)
{
if(obj.currentStyle)
{
return obj.currentStyle(attr)
}
else
{
obj.getComputedStyle(attr,false)
}
}
else
{
obj.style[attr]=value
}
}
写一个方法获取url中?号后面的参数,并将参数对象化?
function getSearch (url) {
var reg_url = /^[^\?]+\?([\w\W]+)$/,
reg_params =/([^&=]+)=([\w\W]*?)(&|$|#)/g,
arr_url = reg_url.exec(url),
ret = {};
if(arr_url && arr_url[1]) {
var str_params = arr_url[1], result;
while((result = reg_ params.exec(str_ params)) != null) {
ret[result[1]]= result[2];
}
}
return ret;
}
哪些地方会出现css阻塞,哪些地方会出现js阻塞?
js的阻塞特性:所有浏览器在下载JS的时候,会阻止一切其他活动,比如其他资源的下载,内容的呈现等等。直到JS下载、解析、执行完毕后才开始继续并行下载其他资源并呈现内容。为了提高用户体验,新一代浏览器都支持并行下载JS,但是JS下载仍然会阻塞其它资源的下载(例如.图片,css文件等)。
由于浏览器为了防止出现JS修改DOM树,需要重新构建DOM树的情况,所以就会阻塞其他的下载和呈现。
嵌入JS会阻塞所有内容的呈现,而外部JS只会阻塞其后内容的显示,2种方式都会阻塞其后资源的下载。也就是说外部样式不会阻塞外部脚本的加载,但会阻塞外部脚本的执行。
CSS怎么会阻塞加载了?CSS本来是可以并行下载的,在什么情况下会出现阻塞加载了(在测试观察中,IE6下CSS都是阻塞加载)
当CSS后面跟着嵌入的JS的时候,该CSS就会出现阻塞后面资源下载的情况。而当把嵌入JS放到CSS前面,就不会出现阻塞的情况了。
根本原因:因为浏览器会维持html中css和js的顺序,样式表必须在嵌入的JS执行前先加载、解析完。而嵌入的JS会阻塞后面的资源加载,所以就会出现上面CSS阻塞下载的情况。
嵌入JS应该放在什么位置?
放在底部,虽然放在底部照样会阻塞所有呈现,但不会阻塞资源下载。
如果嵌入JS放在head中,请把嵌入JS放在CSS头部。
使用defer(只支持IE)
不要在嵌入的JS中调用运行时间较长的函数,如果一定要用,可以用
setTimeout
来调用
Javascript无阻塞加载具体方式
将脚本放在底部。
<link>
还是放在head中,用以保证在js加载前,能加载出正常显示的页面。<script>
标签放在</body>
前。成组脚本:由于每个
<script>
标签下载时阻塞页面解析过程,所以限制页面的<script>
总数也可以改善性能。适用于内联脚本和外部脚本。非阻塞脚本:等页面完成加载后,再加载js代码。也就是,在
window.onload
事件发出后开始下载代码。
(1)defer属性:支持IE4和fierfox3.5更高版本浏览器
(2)动态脚本元素:文档对象模型(DOM)允许你使用js动态创建HTML的几乎全部文档内容。代码如下:
```
<script>
var script=document.createElement("script");
script.type="text/javascript";
script.src="file.js";
document.getElementsByTagName("head")[0].appendChild(script);
</script>
```
此技术的重点在于:无论在何处启动下载,文件的下载和运行都不会阻塞其他页面处理过程。即使在head里(除了用于下载文件的http链接)。
把 Script 标签 放在页面的最底部的body封闭之前 和封闭之后有什么区别?浏览器会如何解析它们?
谈谈你对模块化的理解?
什么是模块化?
模块化就是为了减少系统耦合度,提高高内聚,减少资源循环依赖,增强系统框架设计。让开发者便于维护,同时也让逻辑相同的部分可复用。
模块化开发:针对js、css,以功能或业务为单元组织代码。js方面解决独立作用域、依赖管理、api暴露、按需加载与执行、安全合并等问题,css方面解决依赖管理、组件内部样式管理等问题。
模块化的过程
拆分。将整个系统按功能,格式,加载顺序,继承关系分割为一个一个单独的部分。
注意::拆分的粒度问题、可复用问题、效率问题,如果这些问题处理的不好,就有可能出现不想要的后果。
将功能或特征相似的部分组合在一起,组成一个资源块。
将每个资源块按照需求,功能场景以及目录约束放到固定的地方以供调用。
模块的历程
模块化的发展也是从草根一步一步走过来的。从最开始到现在成熟方案:
namespace
sass,less
AMD&CMD
html模版
grunt,gulp,webpack
FIS,YUI,KISSY
模块化开发怎么做?
立即执行函数,不暴露私有成员
var module1 = (function(){
var _count = 0;
var m1 = function(){
//...
};
var m2 = function(){
//...
};
return {
m1 : m1,
m2 : m2
};
})();
AMD和CMD
Asynchronous Module Definition,异步模块定义,所有的模块将被异步加载,模块加载不影响后面语句运行。所有依赖某些模块的语句均放置在回调函数中。
AMD(Asynchronous Modules Definition)是 RequireJS 在推广过程中对模块定义的规范化产出。
CMD(Common Module Definition)是 SeaJS 在推广过程中对模块定义的规范化产出。
区别:
对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible.
CMD 推崇依赖就近,AMD 推崇依赖前置。看代码:
// CMD
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
// 此处略去 100 行
var b = require('./b') // 依赖可以就近书写
b.doSomething()
// ...
})
// AMD 默认推荐
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
a.doSomething()
// 此处略去 100 行
b.doSomething()
// ...
})
模块加载器
规范提出来了,但是在浏览器怎么实现?业内大神造出了轮子,用的多的就是require.js和sea.js。
RequireJS 和 SeaJS 是模块加载器
利用模块加载器,我们只有一个入口文件,然后根据依赖关系去加载对应的js文件,依赖关系在入口文件写好,(只有一个入口文件,但是解析依赖关系的时候会去加载对应的依赖模块,加载的js文件就不止一个了)
两者的区别:
两者都是异步加载js,只不过一个写法遵循amd,一个写法遵循cmd,其实都是让浏览器支持模块化写法的库。
requirejs是无论模块需不需要都去加载完全部的依赖文件,seajs是某个模块需要用到才去加载,所以说AMD体验好,因为依赖模块一开始全都加载好了,cmd性能好,因为需要才去加载对应的模块
这样我们就可以在浏览器端实现模块化开发了,解决了全局变量的问题,也解决了依赖关系的问题,但是却也带来了新的问题,页面依赖的文件多(浏览器解析的时候就会去加载对应的依赖模块,一个模块就是一个文件),发起的http请求也多,随之而来的就是加载性能的影响(HTTP1,并行的http请求有限制个数).这个时候模块打包器就应运而生了.
模块打包器
在模块化加载器处理的基础上,为了减少解析时加载依赖模块而增加的http请求,我们可以把入口文件打包,在打包的过程中,让它去加载对应的依赖模块,最终生成的那份文件就是包含依赖模块的文件,那样就可以减少http请求,这样的打包操作我们交给构建工具或者说打包工具去实现,比如webpack/Browserify/rollup等等,这样,我们可以只专注怎么写出模块化的,可维护的,高聚合,低耦合的代码
随着es6的出现,js原生也出现了模块开发定义,也有对应的规范,用export导出,用import导入,让我们可以不用使用requirejs和seajs就能进行模块化开发,不过目前浏览器兼容性有限,不过我们可以用webpack来实现兼容,webpack不只可以帮我们把相关依赖的文件打成一个包,也能帮我们打成一个能够兼容的包(借助一些loader).
规避javascript多人开发函数重名问题
命名空间
封闭空间
js模块化mvc(数据层、表现层、控制层)
seajs
变量转换成对象的属性
对象化
requireJS的核心原理是什么?(如何动态加载的?如何避免多次加载的?如何 缓存的?)
概念
requireJS是基于AMD模块加载规范,使用回调函数来解决模块加载的问题。
原理
requireJS是使用创建script元素,通过指定script元素的src属性来实现加载模块的。
特点
实现js文件的异步加载,避免网页失去响应
管理模块之间的依赖,便于代码的编写和维护
项目优化
r.js 是基于requirejs模块化的基础上进一步的压缩和打包成一个js,请求数大大减少,便于优化
核心是js的加载模块,通过正则匹配模块以及模块的依赖关系,保证文件加载的先后顺序,根据文件的路径对加载过的文件做了缓存。
JS模块加载器的轮子怎么造,也就是如何实现一个模块加载器?
参考资料:JS模块化编程之加载器原理
javascript的同源策略
一段脚本只能读取来自于同一来源的窗口和文档的属性,这里的同一来源指的是主机名、协议和端口号的组合。
同源策略规定跨域之间的脚本是隔离的,一个域的脚本不能访问和操作另外一个域的绝大部分属性和方法。
同源策略还应该对一些特殊情况做处理,比如限制file协议下脚本的访问权限。本地的HTML文件在浏览器中是通过file协议打开的,如果脚本能通过file协议访问到硬盘上其它任意文件,就会出现安全隐患,目前IE8还有这样的隐患。
为什么要有同源限制?
我们举例说明:比如一个黑客程序,他利用Iframe把真正的银行登录页面嵌到他的页面上,当你使用真实的用户名,密码登录时,他的页面就可以通过Javascript读取到你的表单中input中的内容,这样用户名,密码就轻松到手了。
请解释JSONP的工作原理,以及它为什么不是真正的AJAX。
JSONP (JSON with Padding)是一个简单高效的跨域方式,HTML中的script标签可以加载并执行其他域的javascript,于是我们可以通过script标记来动态加载其他域的资源。例如我要从域A的页面pageA加载域B的数据,那么在域B的页面pageB中我以JavaScript的形式声明pageA需要的数据,然后在 pageA中用script标签把pageB加载进来,那么pageB中的脚本就会得以执行。JSONP在此基础上加入了回调函数,pageB加载完之后会执行pageA中定义的函数,所需要的数据会以参数的形式传递给该函数。JSONP易于实现,但是也会存在一些安全隐患,如果第三方的脚本随意地执行,那么它就可以篡改页面内容,截获敏感数据。但是在受信任的双方传递数据,JSONP是非常合适的选择。
AJAX是不跨域的,而JSONP是一个是跨域的,还有就是二者接收参数形式不一样!
如何解决跨域问题
网络协议不同,如http协议访问https协议。
端口不同,如80端口访问8080端口。
域名不同,如qianduanblog.com访问baidu.com。
子域名不同,如abc.qianduanblog.com访问def.qianduanblog.com。
域名和域名对应ip,如www.a.com访问20.205.28.90.
跨域请求资源的方法:jsonp、 iframe、window.name、window.postMessage、flash、服务器上设置代理页面
1. JSONP
JSONP:json+padding(内填充),顾名思义,就是把JSON填充到一个盒子里。
原理是:动态插入script标签,通过script标签引入一个js文件,这个js文件载入成功后会执行我们在url参数中指定的函数,并且会把我们需要的json数据作为参数传入。
由于同源策略的限制,XmlHttpRequest只允许请求当前源(域名、协议、端口)的资源,为了实现跨域请求,可以通过script标签实现跨域请求,然后在服务端输出JSON数据并执行回调函数,从而解决了跨域的数据请求。
浏览器对script的资源引用没有同源限制,同时资源加载到页面后会立即执行(没有阻塞的情况下)。
优点是兼容性好,简单易用,支持浏览器与服务器双向通信。
缺点是只支持GET请求。另外要确定jsonp的请求是否失败并不容易,大多数框架的实现都是结合超时时间来判定。
<script>
function createJs(sUrl){
var oScript = document.createElement('script');
oScript.type = 'text/javascript';
oScript.src = sUrl;
document.getElementsByTagName('head')[0].appendChild(oScript);
}
createJs('jsonp.js');
box({
'name': 'test'
});
function box(json){
alert(json.name);
}
</script>
2. CORS【Cross-Origin Resource Sharing】
定义和用法:是现代浏览器支持跨域资源请求的一种最常用的方式。
使用方法:一般需要后端人员在处理请求数据的时候,添加允许跨域的相关操作。如下:
res.writeHead(200, {
"Content-Type": "text/html; charset=UTF-8",
"Access-Control-Allow-Origin":'http://localhost',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'X-Requested-With, Content-Type'
});
服务器端对于CORS的支持,主要就是通过设置Access-Control-Allow-Origin
来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。
3. 通过修改document.domain
来跨子域
将子域和主域的document.domain
设为同一个主域。前提条件:这两个域名必须属于同一个基础域名!而且所用的协议,端口都要一致,否则无法利用document.domain
进行跨域
主域相同的使用document.domain
4. 使用window.name
来进行跨域
window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的
5. 使用HTML5中新引进的window.postMessage
方法来跨域传送数据
6. porxy代理
定义和用法:proxy代理用于将请求发送给后台服务器,通过服务器来发送请求,然后将请求的结果传递给前端。
实现方法:通过nginx代理;
注意点:如果你代理的是https协议的请求,那么你的proxy首先需要信任该证书(尤其是自定义证书)或者忽略证书检查,否则你的请求无法成功。
页面编码和被请求的资源编码如果不一致如何处理?
对于ajax请求传递的参数,如果是get请求方式,参数如果传递中文,在有些浏览器会乱码,不同的浏览器对参数编码的处理方式不同,所以对于get请求的参数需要使用encodeURIComponent函数对参数进行编码处理,后台开发语言都有相应的解码api。对于post请求不需要进行编码。
若请求的资源编码,如外引js文件编码与页面编码不同。可根据外引资源编码方式定义为 charset=“utf-8"或"gbk”。
比如:http://www.yyy.com/a.html
中嵌入了一个http://www.xxx.com/test.js
。a.html 的编码是gbk或gb2312的。 而引入的js编码为utf-8的 ,那就需要在引入的时候<script utf-8"></script>
用过哪些设计模式?知道什么是singleton, factory, strategy, decrator么?
工厂模式:
主要好处就是可以消除对象间的耦合,通过使用工程方法而不是new关键字。将所有实例化的代码集中在一个位置防止代码重复。
工厂模式解决了重复实例化的问题 ,但还有一个问题,那就是识别问题,因为根本无法 搞清楚他们到底是哪个对象的实例。
function createObject(name,age,profession){//集中实例化的函数var obj = new Object();
obj.name = name;
obj.age = age;
obj.profession = profession;
obj.move = function () {
return this.name + ' at ' + this.age + ' engaged in ' + this.profession;
};
return obj;
}
var test1 = createObject('trigkit4',22,'programmer');//第一个实例var test2 = createObject('mike',25,'engineer');//第二个实例
构造函数模式
使用构造函数的方法 ,即解决了重复实例化的问题 ,又解决了对象识别的问题,该模式与工厂模式的不同之处在于:
构造函数方法没有显示的创建对象 (
new Object()
);直接将属性和方法赋值给 this 对象;
没有 renturn 语句。
IE和DOM事件流的区别
执行顺序不一样
参数不一样
事件加不加on
this指向问题
IE和标准下有哪些兼容性的写法
var ev = ev || window.event
document.documentElement.clientWidth || document.body.clientWidth
var target = ev.srcElement||ev.target
JS的垃圾回收机机制
什么是垃圾回收机制(GC)?
早期的计算机语言,比如C和C++,需要开发者手动的来跟踪内存,这种机制的优点是内存分配和释放的效率很高。但是它也有着它的缺点,程序员很容易不小心忘记释放内存,从而造成内存的泄露。
新的编程语言,比如JAVA, C#, javascript, 都提供了所谓“垃圾回收的机制”,运行时自身会运行相应的垃圾回收机制。程序员只需要申请内存,而不需要关注内存的释放。垃圾回收器(GC)会在适当的时候将已经终止生命周期的变量的内存给释放掉。GC的优点就在于它大大简化了应用层开发的复杂度,降低了内存泄露的风险。
内存管理
1、什么时候触发垃圾回收?
垃圾回收器周期性运行,如果分配的内存非常多,那么回收工作也会很艰巨,确定垃圾回收时间间隔就变成了一个值得思考的问题。
IE6的垃圾回收是根据内存分配量运行的,当环境中的变量,对象,字符串达到一定数量时触发垃圾回收。垃圾回收器一直处于工作状态,严重影响浏览器性能。
IE7中,垃圾回收器会根据内存分配量与程序占用内存的比例进行动态调整,开始回收工作。
2、合理的GC方案:(1)、遍历所有可访问的对象; (2)、回收已不可访问的对象。
3、GC缺陷:停止响应其他操作;
4、GC优化策略:(1)、分代回收(Generation GC);(2)、增量GC
Javascript垃圾回收方法
标记清除(mark and sweep)
这是JavaScript最常见的垃圾回收方式,当变量进入执行环境的时候,比如函数中声明一个变量,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”。
垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了
引用计数(reference counting)
在低版本IE中经常会出现内存泄露,很多时候就是因为其采用引用计数方式进行垃圾回收。引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个 变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1,如果该变量的值变成了另外一个,则这个值得引用次数减1,当这个值的引用次数变为0的时 候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间。
在IE中虽然JavaScript对象通过标记清除的方式进行垃圾回收,但BOM与DOM对象却是通过引用计数回收垃圾的, 也就是说只要涉及BOM及DOM就会出现循环引用问题。
哪些操作会造成内存泄漏?
内存泄漏指任何对象在您不再拥有或需要它之后仍然存在。
垃圾回收器定期扫描对象,并计算引用了每个对象的其他对象的数量。如果一个对象的引用数量为 0(没有其他对象引用过该对象),或对该对象的惟一引用是循环的,那么该对象的内存即可回收。
1、setTimeout 的第一个参数使用字符串而非函数的话,会引发内存泄漏。
2、闭包、控制台日志、循环(在两个对象彼此引用且彼此保留时,就会产生一个循环)
3、当页面中元素被移除或替换时,若元素绑定的事件仍没被移除,在IE中不会作出恰当处理,此时要先手工移除事件,不然会存在内存泄露。
实例如下:
<div id="myDiv">
<input type="button" value="Click me" id="myBtn">
</div>
<script type="text/javascript">
var btn = document.getElementById("myBtn");
btn.onclick = function(){
document.getElementById("myDiv").innerHTML = "Processing...";
}
</script>
解决方法如下:
<div id="myDiv">
<input type="button" value="Click me" id="myBtn">
</div>
<script type="text/javascript">
var btn = document.getElementById("myBtn");
btn.onclick = function(){
btn.onclick = null;
document.getElementById("myDiv").innerHTML = "Processing...";
}
</script>
4、由于是函数内定义函数,并且内部函数–事件回调的引用外暴了,形成了闭包。闭包可以维持函数内局部变量,使其得不到释放。
实例如下:
function bindEvent(){
var obj=document.createElement("XXX");
obj.onclick=function(){
//Even if it's a empty function
}
}
解决方法如下:
function bindEvent(){
var obj=document.createElement("XXX");
obj.onclick=function(){
//Even if it's a empty function
}
obj=null;
}
图片预加载的实现
使用jQuery图片预加载插件Lazy Load
图片预先加载距离:threshold,通过设置这个值,在图片未出现在可视区域的顶部距离这个值时加载。
事件绑定加载的方式:event
图片限定在某个容器内:container
加载jQuery, 与jquery.lazyload.js
设置图片的占位符为data-original, 给图片一个特别的标签,比如
class=".lazy"
然后延迟加载:
$('img.lazy').lazyload();
这个函数可以选择一些参数:使用js实现图片加载: 就是new一个图片对象, 绑定onload函数, 赋值url
用CSS实现图片的预加载
写一个CSS样式设置一批背景图片,然后将其隐藏改进: 使用js来推迟预加载时间, 防止与页面其他内容一起加载
如何编写高性能的Javascript?
用hash-table来优化查找
少用全局变量
用innerHTML代替DOM操作,减少DOM操作次数,优化javascript性能
用setTimeout来避免页面失去响应
缓存DOM节点查找的结果
避免使用with(with会创建自己的作用域,会增加作用域链长度)
多个变量声明合并
需求:实现一个页面操作不会整页刷新的网站,并且能在浏览器前进、后退时正确响应。给出你的技术实现方案?
如何判断当前脚本运行在浏览器还是node环境中?(阿里)
this === window ? 'browser' : 'node';
通过判断Global对象是否为window,如果不为window,当前脚本没有运行在浏览器中
检测浏览器版本版本有哪些方式?
功能检测、userAgent特征检测
比如:
navigator.userAgent
//"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36"
移动端最小触控区域是多大?
移动端的点击事件的有延迟,时间是多久,为什么会有? 怎么解决这个延时?
click 有 300ms 延迟,为了实现safari的双击事件的设计,浏览器要知道你是不是要双击操作。
用js实现千位分隔符?(来源:前端农民工,提示:正则+replace)
function commafy(num) {
num = num + '';
var reg = /(-?d+)(d{3})/;
if(reg.test(num)){
num = num.replace(reg, '$1,$2');
}
return num;
}
What is a Polyfill?
polyfill 是“在旧版浏览器上复制标准 API 的 JavaScript 补充”,可以动态地加载 JavaScript 代码或库,在不支持这些标准 API 的浏览器中模拟它们。
例如,geolocation(地理位置)polyfill 可以在 navigator 对象上添加全局的 geolocation 对象,还能添加 getCurrentPosition 函数以及“坐标”回调对象,所有这些都是 W3C 地理位置 API 定义的对象和函数。因为 polyfill 模拟标准 API,所以能够以一种面向所有浏览器未来的方式针对这些 API 进行开发,一旦对这些 API 的支持变成绝对大多数,则可以方便地去掉 polyfill,无需做任何额外工作。
做的项目中,有没有用过或自己实现一些 polyfill 方案(兼容性处理方案)?
比如: html5shiv、Geolocation、Placeholder
Flash、Ajax各自的优缺点,在使用中如何取舍?
Ajax的优势
(1) 可搜索型
(2) 开放性
(3) 费用
(4) 易用性
(5) 易于开发
Flash的优势
(1) 多媒体处理
(2) 兼容性
(3) 矢量图形 比SVG,Canvas优势大很多
(4) 客户端资源调度,比如麦克风,摄像头
Flash适合处理多媒体、矢量图形、访问机器;对CSS、处理文本上不足,不容易被搜索。
Ajax对CSS、文本支持很好,支持搜索;多媒体、矢量图形、机器访问不足。
共同点:与服务器的无刷新传递消息、用户离线和在线状态、操作DOM
ECMAScript6
Object.is()
与原来的比较操作符"="、""的区别?
两等号判等,会在比较时进行类型转换;
三等号判等(判断严格),比较时不进行隐式类型转换,(类型不同则会返回false);
Object.is
在三等号判等的基础上特别处理了 NaN
、-0
和 +0
,保证 -0
和 +0
不再相同,但 Object.is(NaN, NaN)
会返回 true.
Object.is
应被认为有其特殊的用途,而不能用它认为它比其它的相等对比更宽松或严格。
谈一谈你对ECMAScript6的了解?
新增模板字符串(为JavaScript提供了简单的字符串插值功能)、箭头函数(操作符左边为输入的参数,而右边则是进行的操作以及返回的值Inputs=>outputs。)、for-of(用来遍历数据—例如数组中的值。)arguments对象可被不定参数和默认参数完美代替。ES6将promise对象纳入规范,提供了原生的Promise对象。增加了let和const命令,用来声明变量。增加了块级作用域。let命令实际上就增加了块级作用域。ES6规定,var命令和function命令声明的全局变量,属于全局对象的属性;let命令、const命令、class命令声明的全局变量,不属于全局对象的属性。还有就是引入module模块的概念。
以上是关于前端面试题集锦——JavaScript的主要内容,如果未能解决你的问题,请参考以下文章