前端面试Web前端之路JavaScript 进阶营

JavaScript面试笔试题

2017-08-02  本文已影响1146人  yozosann

JavaScript前端面试

系列文章:

HTML及HTTP面试笔试题
CSS面试笔试题

JS一些算法题:

FE-interview-questions,满意点个star。(待更新)

0. 序

本文没有太多基础性的js内容,推荐一本书《JavaScript高级程序设计》,基础不好的同学建议先学习该本书。重点内容:事件,闭包,作用域练,原型,继承。

1.情简述一下javascript的执行环境(执行上下文)以及所涉及到到一些概念?(该文的整体笔记丢失,详细内容建议查看引用链接)

每当控制器转到可执行环境时,就会进入一个执行环境

而执行环境大致分为三类:

执行环境是靠栈来存储,每当开始执行js代码的时候,就进入到了全局执行环境,于是乎,就把全局执行环境压到了这个栈(我们暂时称它为执行环境栈)中。

而在全局代码中,会定义很多函数,函数中又会定义很多函数,而每当控制器执行到函数时,则就进入到了这个函数的执行环境中。

http://www.jianshu.com/p/cd3fee40ef59

2.sessionStorage,localStorage,cookie区别

  1. cookie在同源的http请求中携带,浏览器与服务器之前回传。cookie有路径的概念;cookie大小不超过4k。
  2. sessionStorage(浏览器关闭窗口前有效)和localStorage(一直有效)达到5M或者更大。
  3. cookie在设置的有效期前有效
  4. session不在不同的浏览器窗口中共享,loacl共享,cookie也共享。
  5. sessionStorage和localStorage支持事件机制

3.localStorage应该如何进行存储?

// 将数据存在loaclStorage中

export function saveToLocal(id, key, value) {
  let seller = window.localStorage.__seller__; 
  if (!seller) {
    seller = {};
    seller[id] = {};
  } else {
    seller = JSON.parse(seller);
    if (!seller[id]) {
      seller[id] = {};
    }
  }
  seller[id][key] = value;
  window.localStorage.__seller__ = JSON.stringify(seller);
};

export function loadFromLocal(id, key, def) {
  let seller = window.localStorage.__seller__;
  if (!seller) {
    return def;
  }
  seller = JSON.parse(seller)[id];
  if (!seller) {
    return def;
  }
  let ret = seller[key];
  return ret || def;
};

4.GET与POST的区别?

5.同源策略指的是什么?

6.如何才能跨域,跨域方式有哪些?

Cookie是服务器写入浏览器的一小段信息,只有同源的网页才能共享。但是,两个网页的一级域名相同,只是二级域名不相同,浏览器允许通过设置document.domain共享Cookie。

www.a.com/a.html

document.domain = 'a.com';
var ifr = document.createElement('iframe');
ifr.src = 'http://www.script.a.com/b.html';
ifr.display = none;
document.body.appendChild(ifr);
ifr.onload = function(){
    var doc = ifr.contentDocument || ifr.contentWindow.document;
    // 这里就能b.html的cookie了
    ifr.cooike = doc.cookie;
};

在 script.a.com/b.html 中

document.cookie = "test1=hello";
document.domain = 'a.com';

这样不仅能访问b的cookie,还可以访问b的dom,但是无法访问到LocalStorage 和 IndexDB,而且主要限制是a,b必须一级域名必须相同

浏览器窗口都有window.name这个属性,这个属性最大的特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页就可以读取它。

在b.com/data.html中有这样的数据

<script>
    window.name = '我是你们想要的数据。';
</script>  

现在a.com/index.html想获取这个数据应该怎么办?通过iframe充当中间人

<script>
    function getDate(url){
        var iframe = document.createElement('iframe');
        iframe.style.display='none';
        var state = 0;
        
        iframe.onload = function(){
            if(state == 1){
                var dataPage = iframe.contentWindow;
                var data = dataPage.name;
                console.log(data);
                dataPage.document.write('');
                dataPage.close();
                document.body.remove(iframe);
            }else
            {
                state = 1;
                iframe.contentWindow.location = url;
            }
        };
        
        // 代理源,创建好且设置了document.name
        iframe.src = 'a.com/b.html';
    }
    
    getData('b.com/data.html');
</script>  

些人可能会问为什么不直接把iframe的src设置为目的源(b.com/data.html)来获取数据,而是在设置为目的源之后,还要把src设置为同域名下的其他源(a.com/b.html)才获取数据?

如果直接设置src,那么iframe和本网页(a.com/index.html)会因为同源策略限制不能访问。而把iframe先设置为目的源,再设置为同域名下的其他源,那么同域名下的其他源就和目的源共享了一个窗口,故拥有同样window.name,并且由于是同域名下的源,并且设置了domain,故可以访问目标源的window.name。

state用来干什么

每次设置src,都会刷新,state是标志位,让获取了数据就销毁掉。

上面的方法都是破解,H5提供了官方的API:window.postMessage。允许跨窗口通信,不论这两个窗口是否同源。

// 举例来说,父窗口http://aaa.com向子窗口http://bbb.com发消息,调用postMessage方法就可以了。
var popup = window.open('http://bbb.com', 'title');
popup.postMessage('Hello World!', 'http://bbb.com');

postMessage方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即"协议 + 域名 + 端口"。也可以设为*,表示不限制域名,向所有窗口发送。

// 子窗口向父窗口发送消息的写法类似。
window.opener.postMessage('Nice to see you', 'http://aaa.com');

// 父窗口和子窗口都可以通过message事件,监听对方的消息。
window.addEventListener('message', function(e) {
  console.log(e.data);
},false);

// event.source:发送消息的窗口
// event.origin: 消息发向的网址
// event.data: 消息内容

下面的例子是,子窗口通过event.source属性引用父窗口,然后发送消息。

window.addEventListener('message', receiveMessage);
function receiveMessage(event) {
  event.source.postMessage('Nice to see you!', '*');
}

利用jsonp

// 首先,网页动态插入<script>元素,由它向跨源网址发出请求。

function addScriptTag(src) {
  var script = document.createElement('script');
  script.setAttribute("type","text/javascript");
  script.src = src;
  document.body.appendChild(script);
}

window.onload = function () {
  addScriptTag('http://example.com/ip?callback=foo');
}

function foo(data) {
  console.log('Your public IP address is: ' + data.ip);
};

阮一峰老师:浏览器同源政策及其规避方法

7. AJAX模型是什么?

// 创建xmlHttpRequest对象
function createXmlHttp(){
    var xmlHttp;
    if(window.XMLHttpRequest){
        xmlHttp = new XMLHttpRequest();
    }else{
        xmlHttp = new ActiveXObject('Microsoft.XMLHTTP');
    }
    return xmlHttp;
}

var xhr = createXmlHttp();

// 发送请求GET或者POST
// 第三个参数代表是否异步
xhr.open('GET',url+'?date='+ new Date().getTime(),true);

//GET发送的数据在url上 POST发送的数据在send()中
xhr.send();

// 回调函数响应
xhr.onreadystatechange=function(){
    if(xhr.readyState == 4 && xhr.status == 200){
        document.getElementById("myDiv").innerHTML=xhr.responseText;
    }
}
    
// xhr.readyState存有 XMLHttpRequest 的状态。从 0 到 4 发生变化。
// 0: 请求未初始化
// 1: 服务器连接已建立
// 2: 请求已接收
// 3: 请求处理中
// 4: 请求已完成,且响应已就绪

// status:状态码

// 如果是同步,直接调用xhr.responseText即可

8.为什么说jsonp不是真正的ajax?

因为他们目的一样,都是请求一个url,然后把服务器返回的数据进行处理。故jquery把jsonp封装为ajax的一种形式。

ajax和jsonp其实本质上是不同的东西,ajax的核心通过XmlHttpRequest获取非本页面内容,而jsnop的核心是动态加载script来调用服务器提供的js脚本

ajax 和jsonp 不是一码事 细读详解

9.property 和 attribute有什么区别?

<input id='ipt' value='123' other='1111' />

console.log(document.getElementById('ipt'));

可以发现有attributes,value,id,以及很多我们并没有赋值的属性,但是并没有other属性。value,id这样的就叫做DOM的基础属性。

点击开这个你会发现有

0: id
1: value
2: other
length: 3
__proto__: NamedNodeMap

这个属性包含了显示定义在标签上的属性。

console.log(ipt.attributes.other); // other="1111";

并且

console.log(typeof ipt.attributes.other); // object

返回的是一个对象。

in1.value = 'new value of prop';
console.log(in1.value);             // 'new value of prop'
console.log(in1.attributes.value);  // 'value="1"'

in1.attributes.value.value = 'new value of attr';
console.log(in1.value);             // 'new value of attr'
console.log(in1.attributes.value);  // 'new value of attr'

10. 关于==的强制类型转换

image
  1. 重点:

    1. Number与String比较,String转Number。
    2. Number与Object比较,Object转String。
    3. 当比较中出现Boolean,无论另一个数据是什么类型,都先把Boolean转为Number。
    4. undefined与null,undefined与undefined,
      null与null都返回true。
    5. Object 与 Object只有引用相同返回true。
  2. 数据类型转化为Boolean的规则:

    1. String的 “” 转换为Boolean为false其他都为true
    2. Number的0和NaN转换为Boolean为false其他都为true
    3. Object的null转换为Boolean为false其他都为true
    4. Undefined转换为false 不能转换为true
[] == ![] //true

// 1.由于!的优先级比较高,先执行![],[]是空数组,数组是对象,由2.3(需要了解的知识文字序号),则[]转换为boolean结果为true,![]结果就为false,表达式变为判断 []==false
// 2.根据1.3,将false转为Number,结果为0,表达式变为判断 [] == 0
// 3.根据1.2,将[]变为String,结果为"",表达式变为判断 "" == 0
// 4.根据1.1,将""变为Number,结果为0,表达式变为判断 0 == 0
返回结果 true

console.log(1 == true); // 文字1.3,Number(true) = 1  ->  1 == 1 true
console.log(2 == true); // 文字1.3,Number(true) = 2  ->  2 == 1 false
console.log(0 == false);//文字 1.3,Number(false)= 0  ->  0 == 0 true
console.log(NaN == NaN);// 图片1.c.i/1.c.ii false
console.log({} == {});// 图片1.f {}是对象,比较引用指向的空间,因为是两个不同的空对象,地址也不一样 false
console.log([] == []);// 同理
console.log(null == 0);// 文字1.2 null是对象,String(null) == "null"  -> "null" == 0 ,文字1.1 Number("null") == NaN -> NaN == 0 false
console.log(undefined == 0);// 这里将执行String(undefined),之后执行步骤同上

总结:
undefined == null,结果是true。且它俩与所有其他值比较的结果都是false。

String == Boolean,需要两个操作数同时转为Number。

String/Boolean == Number,需要String/Boolean转为Number。

Object == Primitive,需要Object转为Primitive(具体通过valueOf和toString方法)。

11.支持正则表达式的方法:

// 1. complie() 用于编译正则表达式或改变正则表达式
let str = 'woman1manm';
let patt = /(wo)?man/g;
let str2 = str.replace(patt,'2222'); // 222212222m
let patt2 = /2222/g;
patt.compile(patt2); // 改变了正则表达式
let str3 = str2.replace(patt,'woman'); // 返回 woman1womanmm

// 2. exec() 未匹配到返回一个null,匹配到则返回一个数组arr
// arr[0]表示匹配到的这个字符串,arr[1]...arr[n]表示依次匹配到的圆括号的值,如果有的话
// 并且arr有两个属性,index表示第一次匹配到的字符串下标,input表示需要匹配的字符串本身
let str = '22222womanmmmmmmmanmmmmman';
let patt = /(wo)?(man)/g;
let what = patt.exec(str); 
console.log(what);

// 返回 :
// [ 'woman',
//   'wo',
//   'man',
//   index: 5,
//   input: '22222womanmmmmmmmanmmmmman' ]

// 因为patt是全局搜索,所以exec还可以继续搜索,并且这个时候patt还有一个属性lastIndex表示 匹配文本的最后一个字符的下一个位置

console.log(patt.lastIndex) // 10
what = patt.exec(str);
console.log(what);

// 返回 :
// [ 'man',
//   'undefined',
//   'man',
//   index: 16,
//   input: '22222womanmmmmmmmanmmmmman' ]
//当patt再也匹配不到时,lastIndex会被重置为0

//3. test(str) 检索字符串是否有这个模式,有返回true否则false
// 1. search(patt),搜索字符串是否含有这个匹配,如果有就返回其实位置下标,没有就返回-1。

// 2. match(patt), 在字符串内检索指定的模式,返回存放匹配结果的数组,该数组内容依赖于regexp是否具有g。
// 如果没有g,则返回的结果与exec相同。既,str.match(patt) 和 patt.exec(str)的返回结果相同。如果有g,是全局匹配,则会返回所有匹配结果。

let str = '22222womanmmmmmmmanmmmmman';
let patt = /(wo)?(man)/g;
let result = str.match(patt);
console.log(result); // [ 'woman', 'man', 'man' ]

// 3. replace(patt,str) ,返回一个新的字符串,如果有g将所有匹配值换成目标字符串,没有则替换第一个。
// $1、$2、...、$99代表圆括号所匹配的值
var name = "Doe, John";
console.log(name.replace(/(\w+)\s*, \s*(\w+)/, "$2 $1")); // John Doe

12.常用的正则表达式:

/^([a-zA-Z0-9])+([a-zA-Z0-9_-\.])*@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-])+/ 

开始必须是数组英文字母开头,然后是可以是任意数字字母下划线或者点,然后@,之后是邮箱名,域名。

/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/
/^[1][34578][0-9]{9}$/
/^-?(\d+)[\.]?(\d+)$/

13. es6中的let,const和var有什么区别?

if(true){
    let a =0;
    var b =0;
}
console.log(a) // 报错,未定义
console.log(b) // 就是0
console.log(a); // 结果是报错,不是undefined
let a = 0; 


if(true){
    var c = 0; // 报错,因为c已经定义在此区域了。
    let c = 0;
}

14.var f = function g(){ return 23; }; typeof g()结果是什么?为什么?

15.最完美的数组去重:

function normalize(arr) {
    // 判断传入的是否是数组
    if (arr && Object.prototype.toString.call(arr) !== '[object Array]') {
        return;
    }

    // 当作为对象属性时,会发生强制类型转换,为了区别类型,创建不同对象来去重。
    // 例如: 1,'1' 都会被转化为 obj['1'],导致去重出错, 故区别类型。
    const objectSet = {};

    // Object.create(null), 以防和原型上的函数名冲突。
    // 例如: obj[toString], 本来是没有重复的,但是obj[toString]会查找到
    // 原型上的toString方法,“temp in map”会为true,故利用Object.create(null)
    // 使原型为空
    objectSet.number = Object.create(null);
    objectSet.string = Object.create(null);
    objectSet.array = Object.create(null);
    objectSet.boolean = Object.create(null);
    objectSet.object = Object.create(null);
    objectSet.undefined = Object.create(null);

    let len = arr.length, temp, type, map;

    for (let i = len - 1; i >= 0; i--) {
        temp = arr[i];
        // 取相应的去重对象。
        if(Object.prototype.toString.call(temp) === '[object Array]'){
            map = objectSet.array;
        }else{
            type = typeof temp;
            map = objectSet[type];
        }
        if (temp in map) {
            arr.splice(i, 1);
        } else {
            map[temp] = true;
        }
    }
    return arr;
}

const arr = [1, '1', 1, 'toString', ['toString'], 1, '', 2, '', null, 'null', 2, 2, null, 3, 3];
console.log(normalize(arr));

16. 把字符串转化为大小写:

toUpperCase(): 转大写
toLowerCase(): 转小写

17. 为什么js是阻塞加载的?

当引用了js的时候,浏览器发送一个js请求,就会一直等待请求的返回,因为浏览器需要一个稳定的dom结构,而js中很有可能直接改变了dom结构,为了防止渲染好的树又被js代码修改,所以就会阻塞其他的下载和呈现。

18. 数组长度问题:

 var arr = [];
arr[0]  = 'a';
arr[1]  = 'b';
arr.foo = 'c';
console.log(arr.length); //2 

length返回的是array数组索引长度。

var arr = [];
arr[9] = 1;
console.log(arr.length); // 10;
console.log(arr[8]); // undefines;

19. ToPrimitive()是什么?

简单来说,就是将对象(object)转化为原始类型(undefined, null, boolean ,string, number)的方法,在计算一些强制类型转换时,都需要将对象转化为原始类型再进行计算。

ToPrimitive(obj)等价于,obj.valueOf(),如果为原始值则返回结果,否则,计算obj.toString(),如果是原始值返回,否则抛出异常。

注:此处有个例外,即Date类型的对象,它会先调用toString()方法,后调用valueOf()方法。

// Array
console.log([,2,'ss'].valueOf());  // 返回数组本身[,2,'ss'] ,仍然不是原始类型
console.log([,2,'ss'].toString()); // 返回每个元素中间以逗号隔开的字符串:,2,ss

// Object
console.log({a:11,b:'uu'}.valueOf()); // 返回对象本身{ a: 11, b: 'uu' }
console.log({a:11,b:'uu'}.toString()); // 返回字符串 [object Object]

console.log(function(){return 1}.valueOf()); // 返回[Function]代表函数本身
console.log(function(){return 1}.toString()); // 返回函数定义的字符串 :function (){return 1}

20. '+new Array(017)' 的结果?

21. 不能用变量提升的思路取思考

var foo = {n: 1};
var bar = function(foo){
    console.log(foo.n)
    var foo = {n:2};
}
bar(foo);

这里有正常的传参和执行。

22. innerHTML, outerHTML, innerText, outerText的区别?

<div id="div1"><span>abcd</span></div>

//写
//div.innerHTML = "<p>大米</p>";    //div保留
//div.outerHTML = "<p>大米</p>";    //div也被取代了
//div.innerText = "米老鼠";
//console.log(div);           //div中包裹米老鼠
div.outerText = '<p>www</p>'  //将原来单元素直接变成了纯文本,包括外围<div>

23. Array方法的返回结果?

24. 常用排序时间复杂度?

image

25. AMD 和 CMD 的区别有哪些?

// 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()   // ...
})

26.keydown、keypress、keyup的区别

27. 原生实现点击按钮切换弹框的隐藏,且点击弹框以外的地方使弹框隐藏:

<!DOCTYPE html>
<html lang="en">

<head>
    <style>
        html {
            height: 100%;
        }
        
        body {
            height: 100%;
        }
        
        .div {
            height: 300px;
            width: 300px;
            background-color: rosybrown;
            transition: opacity 0.4s ease;
        }
    </style>
</head>

<body>
    <button class="btn1">toggle</button>
    <div class="div" style="opacity: 1;">
        <button class="btn2">console1</button>
        <button class="btn3">console2</button>
    </div>
    <br />
    <div class="div"></div>
    <script>
        var toggle = document.getElementsByTagName('button')[0];
        var console1 = document.getElementsByTagName('button')[1];
        var console2 = document.getElementsByTagName('button')[2];
        var div = document.getElementsByClassName('div')[0];
        var isShow = true;

        toggle.onclick = function () {
            if (isShow) {
                div.style.opacity = '0';
                isShow = false;
            } else {
                div.style.opacity = '1';
                isShow = true;
            }
        }

        div.addEventListener('click', function (e) {
            e.stopPropagation();
        }, false);

        console1.addEventListener('click', function (e) {
            console.log(1);
            // e.stopPropagation();
        }, false);

        console2.onclick = function (e) {
            console.log(2);
            // e.stopPropagation();
        }

        document.body.addEventListener('click', function (e) {
            var target = e.target;
            // 点击toggle事件也会冒泡到body上,本身toggle的效果就没了,所以事件在toggle时直接退出不执行
            if(target === toggle){
                return;
            }
            div.style.opacity = '0';
            isShow = false;
        }, false);
    </script>
</body>

</html>

思路:切换隐藏很简单,主要是点击周围的地方隐藏弹框。那么就在body上绑定一个事件,点击页面任何一个地方都隐藏,然后再去除弹框区域就行了。如何去除,就在弹框上绑定一个click事件,此事件发生后停止冒泡,那么点击弹框上任何内容的时候,事件都会冒泡到弹框上,然后弹框再停止冒泡,那么事件就不会走到body上,也就不会在弹框区域触发隐藏弹框事件。

28. js单例模式:

var Universe;

(function () {

    var instance;

    Universe = function Universe() {

        if (instance) {
            return instance;
        }

        instance = this;

        // 其它内容
        this.start_time = 0;
        this.bang = "Big";
    };
} ());

//测试代码
var a = new Universe();
var b = new Universe();
alert(a === b); // true
a.bang = "123";
alert(b.bang); // 123
var single = (function(){
    var unique;
    function getInstance(){
        if( unique === undefined ){
            unique = new Construct();
        }
        return unique;
    }
    function Construct(){
        // ... 生成单例的构造函数的代码
    }
    return {
        getInstance : getInstance
    }
})();

js实现单例模式

上一篇下一篇

猜你喜欢

热点阅读