YeaseonZhang

2017前端面试题及答案总结

“金三银四,金九银十”,用来形容求职最好的几个月。但是随着行业的饱和,初中级前端er就业形势不容乐观。

行业状态不可控,我们能做的当然只是让自己变得更加具有竞争力。

今年自己也用了几个月的时间来准备笔记面试,巩固基础知识。特此将自己在这个过程总结的题目分享出来,希望对于求职和准备求职的同学有所帮助。

CSS

列举不同的清除浮动的技巧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/* 1.添加新元素 */
<div class="outer">
<div class="div1"></div>
<div class="div2"></div>
<div class="div3"></div>
<div class="clearfix"></div>
</div>
.clearfix {
clear: both;
}

/* 2.为父元素增加样式 */
.clearfix {
overflow: auto;
zoom: 1; // 处理兼容性
}

/* 3.:after 伪元素方法 (作用于父元素) */
.outer {
zoom: 1;
&:after {
display: block;
height: 0;
clear: both;
content: '.';
visibillity: hidden;
}
}

一像素边框

使用sass语法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/* 定义 */
@mixin border-1px ($color) {
position: relative;
&:after {
display: block;
position: absolute;
left: 0;
bottom: 0;
width: 100%;
border-top: 1px solid $color;
context: '';
}
}

@media (-webkit-min-device-pixel-radio: 1.5), (min-device-pixel-radio: 1.5) {
border-1px {
&:after {
-webkit-transform: scaleY(0.7);
transform: scaleY(0.7);
}
}
}

@media (-webkit-min-device-pixel-radio: 2), (min-device-pixel-radio: 2) {
border-1px {
&:after {
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
}
}
}

/* 使用方式 */
@inclue border-1px(rgba(7, 17, 27, .1));

形成BFC(Block Formatting Context)的几种方式

BFC全称”Block Formatting Context”, 中文为“块级格式化上下文”。BFC元素特性表现原则就是,内部子元素再怎么翻江倒海,翻云覆雨都不会影响外部的元素。

  • floatleft|right
  • overflowhidden|auto|scroll
  • displaytable-cell|table-caption|inline-block
  • positionabsolute|fixed

布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/* HTML */
<div class="container">
<div class="left">Left silder</div>
<div class="content">Main content</div>
</div>

/* CSS */
.container {
overflow: hidden;
}

.left {
float: left;
width: 200px;
margin-bottom: -9999px;
padding-bottom: 9999px;
background-color: #eee;
}

.content {
margin-left: 200px;
margin-bottom: -9999px;
padding-bottom: 9999px;
background-color: #ccc;
}

.left, .content {
min-height: 200px;
height: auto !important;
}

JS

asyncdefer区别

异步(async) 脚本将在其加载完成后立即执行,而 延迟(defer) 脚本将等待 HTML 解析完成后,并按加载顺序执行。

location.replace()location.assign()区别

location.replace()的url不会出现在history中

new操作符

  • 创建一个空对象,并且this变量引用该对象,同时还继承了 该函数的原型
  • 属性和方法被加入到this引用的对象中
  • 新创建的对象由this所引用,并且最后隐式的返回this

AMD CMD CommonJS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* AMD是RequireJS对模块化的定义
* CMD是seaJS对模块化的定义
* CommonJS是Node对模块化的规范
**/

/* AMD 依赖关系前置 */
define(['./a', './b'], function (a, b) {
a.something();
b.something();
})

/* CMD 按需加载,依赖就近 */
define(function (require, exports, module) {
var a = require('./a');
a.something();

var b = require('./b');
b.something();
})

DOM 操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 创建节点
createDocumentFragment()
createElement()
createTextNode()

// 添加 移除 替换 插入
appendChild()
removeChild()
replaceChild()
insertBefore()

// 查找
getElementsByTagName()
getElementsByName()
getElementsByClassName()
getElementById()
querySelector()
querySelectorAll()

JS设置css样式的几种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 1.直接设置style属性 */
element.style.height = '100px';

/* 2.直接设置属性 */
element.setAttribute('height', '100px');

/* 3.使用setAttribute设置style属性 */
element.setAttribute('style', 'height: 100px !important');

/* 4.使用setProperty设置属性,通过第三个参数设置important */
element.style.setProperty('height', '300px', 'important');

/* 5.设置cssText */
element.style.cssText += 'height: 100px !important';

阻止默认行为

1
2
3
4
5
6
7
8
9
10
function stopDefault( e ) {
// 阻止默认浏览器动作(W3C)
if ( e && e.preventDefault ) {
e.preventDefault();
} else {
// IE中阻止函数器默认动作的方式
window.event.returnValue = false;
}
return false;
}

阻止冒泡

1
2
3
4
5
6
7
8
9
10
function stopBubble(e) {
// 如果提供了事件对象,则这是一个非IE浏览器
if ( e && e.stopPropagation ) {
// 因此它支持W3C的stopPropagation()方法
e.stopPropagation();
} else {
// 否则,我们需要使用IE的方式来取消事件冒泡
window.event.cancelBubble = true;
}
}

Ajax交互过程

  • 创建XMLHttpRequest对象,也就是创建一个异步调用对象.
  • 创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息.
  • 设置响应HTTP请求状态变化的函数.
  • 发送HTTP请求.
  • 获取异步调用返回的数据.
  • 使用JavaScript和DOM实现局部刷新.

考察知识点最广的JS面试题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Foo() {
getName = function () { alert(1); }
return this;
}
Foo.getName = function () { alert(2); }
Foo.prototype.getName = function () { alert(3); }
var getName = function () { alert(4); }
function getName () { alert(5); }

/* 写出输出 */
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();

具体讲解参见一道常被人轻视的前端JS面试题

JS数组深浅拷贝

  • slice实现
1
2
3
4
5
6
7
8
var arr = ['old', 1, true, null, undefined];

var new_arr = arr.slice();

new_arr[0] = 'new';

console.log(arr) // ["old", 1, true, null, undefined]
console.log(new_arr) // ["new", 1, true, null, undefined]
  • concat实现
1
2
3
4
5
6
7
8
var arr = ['old', 1, true, null, undefined];

var new_arr = arr.concat();

new_arr[0] = 'new';

console.log(arr) // ["old", 1, true, null, undefined]
console.log(new_arr) // ["new", 1, true, null, undefined]

以上两种方法只是浅拷贝,如果数组元素是基本类型,就会拷贝一份新的;但是如果数组元素是对象或者数组,就只会拷贝引用(类似指针),修改其中一个就会影响另外一个。

1
2
3
4
5
6
7
8
9
var arr = ['old', 1, true, ['old1', 'old2'], {old: 1}];

var new_arr = arr.concat();

new_arr[0] = 'new';
new_arr[3][0] = 'new1';

console.log(arr) // ["old", 1, true, ['new1', 'old2'], {old: 1}]
console.log(new_arr) // ["new", 1, true, ['new1', 'old2'], {old: 1}]
  • JSON.stringify实现数组深拷贝
1
2
3
4
5
6
7
8
9
var arr = ['old', 1, true, ['old1', 'old2'], {old: 1}];

var new_arr = JSON.parse(JSON.stringify(arr));

new_arr[0] = 'new';
new_arr[3][0] = 'new1';

console.log(arr) // ["old", 1, true, ['old1', 'old2'], {old: 1}]
console.log(new_arr) // ["new", 1, true, ['new1', 'old2'], {old: 1}]

简单粗暴,但是问题是不能拷贝函数,不推荐。

然后我们来手动实现深浅拷贝。

  • 浅拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
var shallowCopy = function (obj) {
// 判断是否是数组或者对象
if (typeof obj !== 'object') {
return
}
var newObj = obj instanceof Array ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key];
}
}
return newObj;
}
  • 深拷贝
1
2
3
4
5
6
7
8
9
10
11
12
var deepCopy = function (obj) {
if (typeof obj !== 'object') {
return
}
var newObj = obj instanceof Array ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
}
}
return newObj
}

数组去重

  • filter + indexOf
1
2
3
4
5
6
function unique (arr) {
var res = arr.filter(function (item, index, array) {
return array.indexOf(item) === index;
})
return res;
}
  • filter + sort
1
2
3
4
5
function unique (arr) {
return arr.concat().sort().filter(function (item, index, array) {
return !index || item !== array[index - 1];
})
}
  • ES6
1
2
3
function uniqu3 (arr) {
return [... new Set(arr)];
}

找出数组中的最大值

  • reduce
1
2
3
4
5
6
7
var arr = [6, 4, 1, 8, 2, 11, 3];

function max (prev, next) {
return Math.max(prev, next)
}

console.log(arr.reduce(max));
  • apply
1
2
3
var arr = [6, 4, 1, 8, 2, 11, 3];

console.log(Math.max.apply(null, arr));
  • ES6
1
2
3
4
5
6
7
var arr = [6, 4, 1, 8, 2, 11, 3];

function max (arr) {
return Math.max(...arr);
}

console.log(max(arr));

打乱数组的方法

1
2
3
4
5
6
7
8
var arr = [];
for (var i = 0; i < 100; i++) {
arr[i] = i;
}

arr.sort(function () {
return 0.5 - Math.random();
});

数组扁平化

1
2
3
4
5
6
7
8
9
10
11
12
var arr = [1, [2, [3, 4]]];

function flatten(arr) {

while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}

return arr;
}

console.log(flatten(arr))

排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 冒泡
function bubbleSort2(arr) {
var len = arr.length;
for (var i = 0; i <= len - 1; i++) {
for (var j = 0; j <= len - i; j++) {
if (arr[j + 1] < arr[j]) {
var temp;
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
return arr;
}

// 快速排序
function quickSort(arr) {
if(arr.length == 0) {
return []; // 返回空数组
}

var cIndex = Math.floor(arr.length / 2);
var c = arr.splice(cIndex, 1);
var l = [];
var r = [];

for (var i = 0; i < arr.length; i++) {
if(arr[i] < c) {
l.push(arr[i]);
} else {
r.push(arr[i]);
}
}

return quickSort(l).concat(c, quickSort(r));
}

数字格式化 1234567890 -> 1,234,567,890

1
2
3
4
5
6
function formatNum (num) {
return num.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
var num = '1234567890';
var res = formatNum(num);
console.log(res);

打乱数组的方法

1
2
3
4
5
6
7
8
var arr = [];
for (var i = 0; i < 100; i++) {
arr[i] = i;
}

arr.sort(function () {
return 0.5 - Math.random();
})

尾调用优化

即只保存内层函数的调用帧(只有开启严格模式,才会生效),只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则无法进行“尾调用优化。

1
2
3
4
5
6
7
8
9
10
11
12
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n-1);
}
factorial(5)

/* 优化尾递归 */
function factorial(n, total) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}
factorial(5, 1)

柯里化

实现add(1,2)add(1)(2)均输出3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function add () {
let sum = 0;
Array.prototype.forEach.call(arguments, function (item, index){
if (typeof item !== 'number') {
return false;
} else {
sum += item;
}
})
var tmp = function () {
Array.prototype.forEach.call(arguments, function (item, index){
if (typeof item !== 'number') {
return false;
} else {
sum += item;
}
})
return tmp;
}
tmp.toString = function () {
return sum
}
return tmp;
}
add(1, 2); // 3
add(1)(2); // 3
add(1, 2, 3)(1, 4)(2, 2)(1) // 16

ES8 新特性

  • 字符串填充
1
2
str.padStart(targetLength [, padString])
str.padEnd(targetLength [, padString])
  • valuesentries函数
1
2
Object.values(obj)
Object.entries(obj)
  • getOwnPropertyDescriptors函数
1
Object.getOwnPropertyDescriptors(obj)
  • 函数参数逗号结尾
1
function es8(var1, var2, var3,) {}
  • 异步函数
    async关键词定义的函数声明了一个可以异步执行的函数,返回一个AsyncFunction类型的对象。
1
2
3
4
5
6
7
8
9
10
11
12
fucntion fetchTextByPromise () {
return new Promise(resolve => {
setTimeout(() => {
resolve('es8');
}, 2000);
});
}
async function sayHello () {
const externalFetchedText = await fetchTextByPromise();
console.log(`Hello, ${externalFetchedText}`);
}
sayHello();

数据类型判断

1
2
3
4
5
6
7
8
9
10
var class2type = {};

'Boolean Number String Function Array Date RegExp Object Error Null Undefined'.split(' ').map((item, index) => {
class2type['[object ' + item + ']'] = item.toLowerCase();
})

function type (obj) {
return typeof obj === 'object' || typeof obj === 'function' ?
class2type[{}.toString.call(obj)] || 'object' : typeof obj;
}

防抖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*
* func:需要调用的函数
* wait: 防抖时间
* immediate:布尔值,是否立即执行
**/

var debounce = function (func, wait, immediate) {
var timeout;
return function () {
var context = this;
var args = arguments;

if (timeout) clearTimeout(timeout);

if (immediate) { // 是否立即执行func
var callNow = !timeout;
timeout = setTimeout(function () {
timeout = null;
}, wait);

if (callNow) {
func.apply(context, args);
}
} else {
timeout = setTimeout(function () {
func.apply(context, args);
}, wait);
}
}
}

简单的字符串模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var TemplateEngine = function(tpl, data) {
var re = /<%([^%>]+)?%>/g, match;
while(match = re.exec(tpl)) {
tpl = tpl.replace(match[0], data[match[1]])
}
return tpl;
}

var template = '<p>Hello, my name is <%name%>. I\'m <%age%> years old.</p>';
console.log(TemplateEngine(template, {
name: "Yeaseon",
age: 24
}));

// '<p>Hello, my name is Yeaseon. I\'m 24 years old.</p>';

applycallbind

  • applycall

在严格模式下,未指定环境对象而调用函数,则this 值不会转型为window。除非明确把函数添加到某个对象或者调用apply()或call(),否则this 值将是undefined。

在非严格模式下,call、apply的第一个参数传递为null或undefined时,函数体内的this会指向默认的宿主对象,在浏览器中则是window。

  • applycallbind比较
1
2
3
4
5
6
7
8
9
10
11
12
13
var obj = {
x: 81
};

var foo = {
getX: function () {
return this.x;
}
}

console.log(foo.getX.bind(obj)());
console.log(foo.getX.apply(obj));
console.log(foo.getX.call(obj));

很明显,bind方法后面多了一对括号。也就是说,当你希望改变上下文环境之后并且立即执行,而是回调执行的时候(多用于事件监听器函数),使用bind()方法,而apply/call则会立即执行函数。

  • 定义一个 log 方法,让它可以代理 console.log 方法。
1
2
3
4
5
function log(){
console.log.apply(console, arguments);
};
log(1); //1
log(1,2); //1 2
  • 给每一个 log 消息添加一个”(app)”的前辍,比如:
1
log("hello world");    //(app)hello world
1
2
3
4
5
function log(){
var args = Array.prototype.slice.call(arguments);
args.unshift('(app)');
console.log.apply(console, args);
};
  • apply实现bind
1
2
3
4
5
function bind (fn, context) {
return function () {
return fn.apply(context, argument);
}
}

创建对象

  • 工厂模式
1
2
3
4
5
6
7
8
9
10
11
12
function createPerson(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");
  • 构造函数模式
1
2
3
4
5
6
7
8
9
10
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
  • 原型模式
1
2
3
4
5
6
7
8
9
10
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
  • 构造函数 + 原型模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];
}
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
}
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Count,Van"
alert(person2.friends); //"Shelby,Count"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true

JS实现Jquery的extend()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
function extend() {
// 默认不进行深拷贝
var deep = false;
var name, options, src, copy, clone, copyIsArray;
var length = arguments.length;
// 记录要复制的对象的下标
var i = 1;
// 第一个参数不传布尔值的情况下,target 默认是第一个参数
var target = arguments[0] || {};
// 如果第一个参数是布尔值,第二个参数是 target
if (typeof target == 'boolean') {
deep = target;
target = arguments[i] || {};
i++;
}
// 如果target不是对象,我们是无法进行复制的,所以设为 {}
if (typeof target !== "object" && !isFunction(target)) {
target = {};
}

// 循环遍历要复制的对象们
for (; i < length; i++) {
// 获取当前对象
options = arguments[i];
// 要求不能为空 避免 extend(a,,b) 这种情况
if (options != null) {
for (name in options) {
// 目标属性值
src = target[name];
// 要复制的对象的属性值
copy = options[name];

// 解决循环引用
if (target === copy) {
continue;
}

// 要递归的对象必须是 plainObject 或者数组
if (deep && copy && (isPlainObject(copy) ||
(copyIsArray = Array.isArray(copy)))) {
// 要复制的对象属性值类型需要与目标属性值相同
if (copyIsArray) {
copyIsArray = false;
clone = src && Array.isArray(src) ? src : [];

} else {
clone = src && isPlainObject(src) ? src : {};
}

target[name] = extend(deep, clone, copy);

} else if (copy !== undefined) {
target[name] = copy;
}
}
}
}

return target;
};

自定义事件(通过观察者模式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function EventTarget () {
this.handlers = {};
}

EventTarget.prototype = {
constructor: EventTarget,
addHandler: function (type, handler) {
if (typeof this.handlers[type] == 'undefined') {
this.handlers[type] = [];
}
this.handlers[type].push(handler)
},
fire: function (event) {
if (!event.target) {
event.target = this;
}
if (this.handlers[event.type] instanceof Array) {
var handlers = this.handlers[event.type];
for (var i = 0, len = handlers.length; i < len; i++) {
handlers[i](event);
}
}
},
removeHandler: function (type, handler) {
if (this.handlers[type] instanceof Array) {
var handlers = this.handlers[type];
for (var i = 0, len = handlers.length; i < len; i++) {
if (handlers[i] === handler) {
break;
}
}
handlers.splice(i, 1);
}
}
}

安全

跨域的几种方法

  1. 主域相同的跨域
1
document.domain
  1. window.postMessage

  2. JSONP跨域(只支持GET)

1
2
3
4
5
6
7
8
9
10
11
function todo(data){
console.log('The author is: '+ data.name);
}

var script = document.createElement('script');
/* 向服务器www.yeaseonzhang.com发出请求。注意,该请求的查询字符串有一个callback参数,用来指定回调函数的名字。 */
script.src = 'http://www.yeaseonzhang.com/author?callback=todo';
document.body.appendChild(script);
/* 服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。 */
todo({"name": "fewjq"});
/* 由于<script>元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了todo函数,该函数就会立即调用。作为参数的JSON数据被视为JavaScript对象。*/
  1. websocket跨域

XSS 和 CSRF

性能

CSS 优化

  • 正确的时机调用CSS
  • 使用媒体查询标记<link>,选择性加载
  • 减少css文件数量
  • 压缩css代码

渲染