前端面试经典问题

1. JS事件的捕获和冒泡

下面代码执行结果的顺序:
html代码如下:

<html>
    <body>
        <div id="test">
            <ul>
                <li>捕获和冒泡</li>
            </ul>
        </div>
    </body>
</html>

javascript代码如下:

var html = document.documentElement;
var body = document.body;
var div = body.querySelector('div');
var ul = body.querySelector('ul');
var li = body.querySelector('li');

ul.addEventListener('click',callback,true);
li.addEventListener('click',callback,true);
div.addEventListener('click',callbackdiv2,true);

body.addEventListener('click',callback,false);
html.addEventListener('click',callback,false);

function callback(event){
    var target = event.currentTarget;
    console.log(target.tagName);
}

function callbackdiv(event){
    //event.stopPropagation();
    console.log("div callback");
}

//输出什么内容
//div callback ->ul->li->body->html

总结就是:先捕获,后冒泡,捕获从上到下,冒泡从下到上(形象点说法:捕获像石头沉入海底,冒泡则像气泡冒出水面)
问:假如去掉注释 event.stopPropagation(); 结果又会输出什么?
结果:事件在div元素上传播被阻止,只有div元素响应事件。(还有就是在点击空白区域的时候html元素依然响应事件)

2. 如何消除一个数组里面重复的元素?

//1. 使用indexOf判断新数组,但ie低版本不支持
function unique1(arr){
    var tmpArr = [];
    for(var i=0; i<arr.length; i++){
      //如果当前数组的第i已经保存进了临时数组,那么跳过,
      //否则把当前项push到临时数组里面
      if(tmpArr.indexOf(arr[i]) == -1){
        tmpArr.push(arr[i]);
      }
    }
    return tmpArr;
}

//2. hash查找
function unique2(arr){
    var tmpArr = [], hash = {};//hash为hash表
    for(var i=0;i<arr.length;i++){
      if(!hash[arr[i]]){//如果hash表中没有当前项
        hash[arr[i]] = true;//存入hash表
        tmpArr.push(arr[i]);//存入临时数组
      }
    }
    return tmpArr;
} 

//3. 先排序后比前后大小
Array.prototype.unique3 = function () {
        var temp = new Array();
        this.sort();
        for(i = 0; i < this.length; i++) {
          if( this[i] == this[i+1]) {
            continue;
        }
          temp[temp.length]=this[i];
        }
        return temp;
} 

//4. 正则匹配
Array.prototype.unique4 = function () {
        return this.sort().join(",,").replace(/(,|^)([^,]+)(,,\2)+(,|$)/g,"$1$2$4").replace(/,,+/g,",").replace(/,$/,"").split(",");
}

3. 在Javascript中什么是伪数组?如何将伪数组转化为标准数组?

//argument参数,还有像调用getElementsByTagName,document.childNodes之类
var toArray = function(s){
    try{
        //s调用Array的原型slice方法,返回一个新数组
        return Array.prototype.slice.call(s);
    } catch(e){
            var arr = [];
            for(var i = 0,len = s.length; i < len; i++){
                //arr.push(s[i]);
                   arr[i] = s[i];  //据说这样比push快
            }
             return arr;
    }
}

4. Javascript跨域方案

  1. JSONP
      JSONP (JSON with Padding)是一个简单高效的跨域方式,HTML中的script标签可以加载并执行其他域的javascript,于是我们可以通过script标记来动态加载其他域的资源。例如我要从域A的页面pageA加载域B的数据,那么在域B的页面pageB中我以JavaScript的形式声明pageA需要的数据,然后在 pageA中用script标签把pageB加载进来,那么pageB中的脚本就会得以执行。JSONP在此基础上加入了回调函数,pageB加载完之后会执行pageA中定义的函数,所需要的数据会以参数的形式传递给该函数。JSONP易于实现,但是也会存在一些安全隐患,如果第三方的脚本随意地执行,那么它就可以篡改页面内容,截获敏感数据。但是在受信任的双方传递数据,JSONP是非常合适的选择。

  2. 通过修改 document.domain 来跨子域
    http://www.example.com/a.htmlhttp://example.com/b.html这两个页面的document.domain都设成相同的域名就可以了。但要注意的是,document.domain的设置是有限制的,我们只能把document.domain设置成自身或更高一级的父域,且主域必须相同。例如:a.b.example.com 中某个文档的document.domain 可以设成a.b.example.com、b.example.com 、example.com中的任意一个,但是不可以设成 c.a.b.example.com,因为这是当前域的子域,也不可以设成baidu.com,因为主域已经不相同了。

  3. 使用window.name来进行跨域(没有浏览器兼容问题)
    window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。
    我们看到在b.html页面上成功获取到了它的上一个页面a.html给window.name设置的值。如果在之后所有载入的页面都没对window.name进行修改的话,那么所有这些页面获取到的window.name的值都是a.html页面设置的那个值。当然,如果有需要,其中的任何一个页面都可以对window.name的值进行修改。注意,window.name的值只能是字符串的形式,这个字符串的大小最大能允许2M左右甚至更大的一个容量,具体取决于不同的浏览器,但一般是够用了。

4、使用HTML5中新引进的window.postMessage方法来跨域传送数据
window.postMessage(message,targetOrigin) 方法是html5新引进的特性,可以使用它来向其它的window对象发送消息,无论这个window对象是属于同源或不同源,目前IE8+、FireFox、Chrome、Opera等浏览器都已经支持window.postMessage方法。
调用postMessage方法的window对象是指要接收消息的那一个window对象,该方法的第一个参数message为要发送的消息,类型只能为字符串;第二个参数targetOrigin用来限定接收消息的那个window对象所在的域,如果不想限定域,可以使用通配符 * 。
需要接收消息的window对象,可是通过监听自身的message事件来获取传过来的消息,消息内容储存在该事件对象的data属性中。示例代码如下:

//a.html
<script>
function onLoad(){
var iframe = document.getElementById('iframe');
var win = iframe.contentWindow;
win.postMessage('的尽快发货上看到', '*');
}
</script>

<iframe id='iframe' src="http://127.0.0.1/postmsg/b.html" onload="onLoad()">
</iframe>

//b.html
<script>
window.onmessage = function(e){
e = e || event;
alert(e.data);
}
</script>

5. 一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么?

分为4个步骤:
(1),当发送一个URL请求时,不管这个URL是Web页面的URL还是Web页面上每个资源的URL,浏览器都会开启一个线程来处理这个请求,同时在远程DNS服务器上启动一个DNS查询。这能使浏览器获得请求对应的IP地址。
(2), 浏览器与远程`Web`服务器通过`TCP`三次握手协商来建立一个`TCP/IP`连接。该握手包括一个同步报文,一个同步-应答报文和一个应答报文,这三个报文在浏览器和服务器之间传递。该握手首先由客户端尝试建立起通信,而后服务器应答并接受客户端的请求,最后由客户端发出该请求已经被接受的报文。
(3),一旦`TCP/IP`连接建立,浏览器会通过该连接向远程服务器发送`HTTP`的`GET`请求。远程服务器找到资源并使用HTTP响应返回该资源,值为200的HTTP响应状态表示一个正确的响应。
(4),此时,`Web`服务器提供资源服务,客户端开始下载资源。

请求返回后,便进入了我们关注的前端模块
简单来说,浏览器会解析HTML生成DOM Tree,其次会根据CSS生成CSS Rule Tree,而javascript又可以根据DOM API操作DOM

6. 浏览器内核工作原理

浏览器的主要结构如下:

  1. 用户界面-包括地址栏、后退/前进按钮、书签菜单等。
  2. 浏览器引擎- 询问和操作渲染引擎的接口
  3. 渲染引擎-负责展现所请求的内容,比如如果请求的是html,它就负责解析html和css并且在屏幕上呈现解析后的内容。
  4. 网络模块-网络请求用,比如http请求。它有跨平台的接口以及对应于每个平台的底层实现。
  5. UI后端-用来绘制基本构件,如下拉框和窗口。UI后端暴露出一个平台无关性的公共接口,底层的它使用操作系统的UI方法。
  6. javascript解释器—用来解析和执行javascript代码。
  7. 数据存储–这是一个可存储的层。浏览器需要在硬盘上保存各种数据,比如cookies。新的html规范(html5)把浏览器中的“web database”定义为一个完完全全的(虽然很轻)数据库。
    浏览器组件

渲染引擎主要流程
渲染引擎开始会从网络模块获取要请求的文档内容,一般会以8k大小的区块获取。
在获取内容之后,是渲染引擎的基本流程:
渲染引擎主要流程

渲染引擎开始解析HTML文档,并且把标签转换成“内容树”上的DOM节点,然后会解析样式,包括外部的css文件和style元素里的数据。这些样式信息将会和HTML中视觉性的属性组合在一起
创建另一个树—渲染树。
渲染树由带有如颜色和大小等视觉属性的矩形区域构成,这些矩形区域按它们将要显示在屏幕上的顺序排列。
渲染树构造完成后,进入到布局阶段,会把每个节点精确地调整到它应该在屏幕上出现的位置上。
下一步是绘制,渲染树将会被遍历,每个节点都会通过UI后端层来绘制。
理解这是一个渐进的过程相当重要。为了有更好的用户体验,渲染引擎将会尽可能早的把内容在屏幕上显示出来,不会等到所有的HTML都被解析完才开始建造和布局渲染树,当进程还在继续解
析源源不断的来自于网络的内容的时候,一部分内容会被解析并且显示出来。
渲染流程

7 requirejs循环依赖问题

一.问题:A依赖B(即A引用B且调用B中的方法),B也依赖A,这即为循环依赖,那么,当B调用A中的方法时,会发现A为undefined,这就是循环依赖导致的问题。
二.解决循环依赖的方法:
1.用scope模式传参方式;
2.用pubsub解耦;
3.用require(“A”)的方式:

define([‘require‘,‘jquery‘,‘a‘],
function(require, $, a){
return {
loading : function(data){
require(‘a‘).addBag(data);
}
}
});

8 数组乱序

关于数组乱序,正确的解法应该是 Fisher–Yates Shuffle,复杂度 O(n)。

其实它的思想非常的简单,遍历数组元素,将其与之前的任意元素交换。因为遍历有从前向后和从后往前两种方式,所以该算法大致也有两个版本的实现。

从后往前的版本:

function shuffle(array) {
  var _array = array.concat();

  for (var i = _array.length; i--; ) {
    var j = Math.floor(Math.random() * (i + 1));
    var temp = _array[i];
    _array[i] = _array[j];
    _array[j] = temp;
  }

  return _array;
}

数组乱序

9 js模板引擎原理

10 js深拷贝