面试题摘选

经典函数,算法,或者知识点归类。

js 相关

数组排序

  • 1.乱序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function shuffle(a) {
for (let i = a.length; i; i--) {
let j = Math.floor(Math.random() * i);
[a[i - 1], a[j]] = [a[j], a[i - 1]];
}
return a;
}

var times = 100000;
var res = {};

for (var i = 0; i < times; i++) {
var arr = shuffle([1, 2, 3]);
var key = JSON.stringify(arr);
console.log(arr,key)
res[key] ? res[key]++ : res[key] = 1;
}

// 为了方便展示,转换成百分比
for (var key in res) {
res[key] = res[key] / times * 100 + '%'
}

console.log(res)
  • 2.数组扁平化 + 升序
1
2
3
var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];

Array.from(new Set(arr.flat(Infinity))).sort((a,b)=>{ return a-b})

compareFn(a, b) 返回值 排序顺序

0 a 在 b 后,如 [b, a]
< 0 a 在 b 前,如 [a, b]
=== 0 保持 a 和 b 原来的顺序

  • 3.普通乱序
1
2
3
4
5
6
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
arr.sort(function () {
let a = Math.random() - 0.5;
console.log(a)
return a;
});
  • 4.普通升序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function quickSort(arr) {
if(arr.length <= 1) {
return arr; //递归出口
}
var left = [],
right = [],
current = arr.splice(0,1);
for(let i = 0; i < arr.length; i++) {
if(arr[i] < current) {
left.push(arr[i]) //放在左边
} else {
right.push(arr[i]) //放在右边
}
}
console.log(left,right,current)
return quickSort(left).concat(current,quickSort(right));
}

quickSort([20,53,77,109,6,5,34,68,24,80,43,24,74,34,63,23])

判断类型

typeof: 只能判断基本类型
instanceof : 检验构造函数的prototype属性是否出现在对象的原型链中的任何位置,返回一个布尔值。 a instanceof Array; prototype属性是可以修改的,所以不一定为真。
Object.prototype.toString.call(a) : ‘[object Array]’; ‘[object Object]’;

Array.isArray(a) 返回布尔值
Object.isObject(a)

bind call apply

  • 相同点: 都是修改 this 的指向
  • 不同点: call , apply 立即执行bind 绑定返回新函数,需调用执行
    fn.call([this],param...)
    fn.apply([this],[param...])
    fn.bind([this],param...)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var obj = {
name: '美美',
age: 18,
say: function(){
console.log(`my name is ${this.name}, I am ${this.age}`)
}
}

var xixi = {
name: 'xixi',
age: 12
}

obj.say()
obj.say.call(xixi)
obj.say.apply(xixi)
obj.say.bind(xixi)()
  • 参数传递 apply 入参是数组,call, bind 入参是 arguments
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
    var obj = {
name: '美美',
age: 18,
say: function(from,to){
console.log(`my name is ${this.name}, I am ${this.age}, from ${from} to ${to}`)
}
}

var xixi = {
name: 'xixi',
age: 12
}

obj.say.call(xixi,'beijing','shanghai')
obj.say.apply(xixi, ['beijing','shanghai'])
obj.say.bind(xixi,'beijing','shanghai')()

// bind 调用参数会追加
let f1 = function () {
console.log(1, arguments);
};
let a = "ab";

let f2 = f1.bind(a, 1);
f2(2, 3); // arguments [1,2,3]
手写实现 apply call bind
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
Function.prototype.myCall = function(context){
if (context == null || context == undefined) {
context = window
}
let key = Symbol()
let rest = [...arguments].slice(1)
context[key] = this
let res = context[key](...rest)
delete context[key];
return res;
}

Function.prototype.myApply = function(context){
if (context == null || context == undefined) {
context = window
}
let key = Symbol()
let rest = [...arguments].slice(1)
context[key] = this
let res = context[key]([...rest])
delete context[key];
return res;
}

Function.prototype.myBind = function (context) {
if (typeof this !== "function") {
throw new TypeError("not a function");
}
let self = this;
let args = [...arguments].slice(1);

function Fn() {};
Fn.prototype = this.prototype;
let bound = function () {
let res = [...args, ...arguments];
context = this instanceof Fn ? this : context || this;
return self.apply(context, res);
}
bound.prototype = new Fn();
return bound;
}



var name = 'Jack';

function person(age, job, gender) {
console.log(this.name, age, job, gender);
}
var Yve = {
name: 'Yvette'
};
let result = person.bind2(Yve, 22, 'enginner')('female');
// Yvette 22 enginner female

深浅拷贝

深浅拷贝都只是针对数组、对象这类引用型数据类型的。

1、什么是深拷贝、浅拷贝

深拷贝:修改新变量的值不会影响原有变量的值,相当于创造一个一模一样的新对象,新旧对象不共享内存
浅拷贝:修改新变量的值会影响原有的变量的值,只复制对象的引用,新旧对象共享同一块内存

2.实现浅拷贝

  • Object.assign({},obj)
  • Array.prototype.concat() 与 Array.prototype.slice()
  • … 运算符

3、深拷贝实现

  • JSON.parse( JSON.stringify() )
  • 递归
1
2
3
4
5
6
7
8
9
10
11
12
function deepCopy(obj){
let newObj = null;
if(typeof(obj) == 'object' && obj !== null){
newObj= obj instanceof Array? [] : {};
for(let i in obj){
newObj[i] = deepCopy(obj[i])
}
}else{
newObj = obj;
}
return newObj;
}

js 事件

事件 是用户操作网页时发生的交互动作或者网页本身的一些操作,现代浏览器一共有三种事件模型。

  1. DOM0 级模型: ,这种模型不会传播,所以没有事件流的概念,但是现在有的浏览器支持以冒泡的方式实现,它可以在网页中直接定义监听函数,也可以通过 js 属性来指定监听函数。这种方式是所有浏览器都兼容的。
  2. IE 事件模型: 在该事件模型中,一次事件共有两个过程,事件处理阶段,和事件冒泡阶段。事件处理阶段会首先执行目标元素绑定的监听事件。然后是事件冒泡阶段,冒泡指的是事件从目标元素冒泡到 document,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。这种模型通过 attachEvent 来添加监听函数,可以添加多个监听函数,会按顺序依次执行。
  3. DOM2 级事件模型: 在该事件模型中,一次事件共有三个过程,第一个过程是事件捕获阶段。捕获指的是事件从 document 一直向下传播到目标元素,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。后面两个阶段和 IE 事件模型的两个阶段相同。这种事件模型,事件绑定的函数是 addEventListener,其中第三个参数可以指定事件是否在捕获阶段执行。

js 冒泡 捕获

捕获优先冒泡
捕获: 由外向内
冒泡: 由内向外

参数 默认冒泡 false, true 就是捕获。
div.addEventListener(“click”,clickhandler,true);

e.stopPropagation() 就是阻止之后的冒泡或者捕获继续传播,说白了就是截断,不再侦听
e.prevent.default(); 阻止默认行为。

闭包

闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。

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
var myFamily = function(){
let init = Object.create(null);
function addPerson(name,age = 0){
if (init[name]) {
delete init[name]
}else{
init[name] = age
}
}
return {
getPerson:()=>{
return init;
},
add:(name,age)=>{
addPerson(name,age)
},
del:(name)=>{
addPerson(name)
}
}
}

var huang = myFamily();
huang.add('hai',20)
huang.add('bing',30)
console.log(huang.getPerson())

var tui = myFamily();
tui.add('hai',45)
tui.add('bing',23)
console.log(tui.getPerson())

js 继承

所谓继承就是通过某种方式让一个对象可以访问到另一个对象中的属性和方法

更多可看 js面向对象编程与继承

js 运算符

1
2
3
4
let number = 0;
console.log(number++); // 0
console.log(++number); // 2
console.log(number); // 2

自增(++) 一元运算符。将操作数的值加一。如果放在操作数前面(++x),则返回加一后的值;如果放在操作数后面(x++),则返回操作数原值,然后再将操作数加一。
自减(–) 一元运算符。将操作数的值减一。前后缀两种用法的返回值类似自增运算符。

CSS 相关

盒子模型

分成标准盒子模型 和 IE 盒子模型
标准盒子模型(W3C 盒子模型):content padding margin border
IE 盒子模型: content(包含 padding + border) margin

CSS3 box-sizing 属性 可以切换两个模型。
box-sizing: content-box|border-box|inherit
使用 IE 盒子模型: box-sizing: border-box
content-box:默认值。如果你设置一个元素的宽为 100px,那么这个元素的内容区会有 100px 宽,并且任何边框和内边距的宽度都会被增加到最后绘制出来的元素宽度中。
border-box: 告诉浏览器:你想要设置的边框和内边距的值是包含在 width 内的。也就是说,如果你将一个元素的 width 设为 100px,那么这 100px 会包含它的 border 和 padding,内容区的实际宽度是 width 减 去(border + padding) 的值。大多数情况下,这使得我们更容易地设定一个元素的宽高。

BFC 是什么? 如何实现?

定义:
块格式化上下文(Block Formatting Context,BFC)是 Web 页面的可视化 CSS 渲染的一部分,是布局过程中生成块级盒子的区域,也是浮动元素与其他元素的交互限定区域 MDN 文档
实现:

  • 根元素或其它包含它的元素
  • 浮动元素 (元素的 float 不是 none)
  • 绝对定位元素 (元素具有 position 为 absolute 或 fixed)
  • 内联块 (元素具有 display: inline-block)
  • 表格单元格 (元素具有 display: table-cell,HTML 表格单元格默认属性)
  • 表格标题 (元素具有 display: table-caption, HTML 表格标题默认属性)
  • 具有 overflow 且值不是 visible 的块元素

格式化上下文影响布局,通常,我们会为定位和清除浮动创建新的 BFC,而不是更改布局,因为它将:

包含内部浮动
排除外部浮动
阻止 外边距重叠

更多css查看 {}

VUE

vue 组件通信

  • 父子: props emit
  • 广播监听
1
2
3
var Event=new Vue();
Event.$emit(事件名,数据);
Event.$on(事件名,data => {})
  • inject provide
  • vuex

nexttick

Vue.$nextTick(cb)
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
原因是,Vue 是异步执行 dom 更新的,一旦观察到数据变化,Vue 就会开启一个队列,然后把在同一个事件循环 (event loop) 当中观察到数据变化的 watcher 推送进这个队列。如果这个 watcher 被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和 DOm 操作。而在下一个事件循环时,Vue 会清空队列,并进行必要的 DOM 更新。

其他

同步异步 微任务 宏任务

js 是单线程,执行顺序 同步 再异步,先微任务 再宏任务。

  • js 宏任务有:setTimeout、setInterval、setImmediate、Ajax、DOM 事件
  • js 微任务有:process.nextTick、MutationObserver、Promise.then catch finally

JS 是单线程,碰见同步执行同步直到执行完毕,遇到异步放到执行队列中去,异步(宏任务和微任务),在异步中微任务是优于宏任务执行的
执行顺序:主线程 >> 主线程上创建的微任务 >> 主线程上创建的宏任务

import required 区别

  • 1.模块加载的时间
    require:运行时加载
    import:编译时加载(效率更高)【由于是编译时加载,所以 import 命令会提升到整个模块的头部】
  • 2.模块的本质
    require:模块就是对象,输入时必须查找对象属性
    import:ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,再通过 import 命令输入
1
2
3
4
5
6
// CommonJS模块
let { exists, readFile } = require('fs');
// 等同于
let fs = require('fs');
let exists = fs.exists;
let readfile = fs.readfile;

上面 CommonJs 模块中,实质上整体加载了 fs 对象(fs 模块),然后再从 fs 对象上读取方法

// ES6 模块

1
import { exists, readFile } from 'fs';

上面 ES6 模块,实质上从 fs 模块加载 2 个对应的方法,其他方法不加载

  • 3.严格模式
    (1)CommonJs 模块默认采用非严格模式
    (2)ES6 的模块自动采用严格模式,不管你有没有在模块头部加上 “use strict”;
    (3)CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用

虚拟 dom diff?

用 JavaScript 模拟 DOM 树,并渲染这个 DOM 树
比较新老 DOM 树,得到比较的差异对象
把差异对象应用到渲染的 DOM 树。

什么是跨域? 怎么解决?

  • 定义
    跨域是浏览器的安全策略,是指: http 协议 ,域名,端口不一样

  • 解决:

  • cors: Access-Control-Allow-Origin: * || <指定 origin> : CORS 需要浏览器和服务器同时支持,

  • jsonp: http://abc.com?callback=handleData ,在 handleData 里面处理返回的数据

  • websocket

  • node 代理转发 ,Nginx 反向代理等

从输入 URL 到页面加载完成期间经历了什么(简写)

  • 1.浏览器根据请求的 URL 交给 DNS 域名解析,找到真实 IP;
  • 2.浏览器根据 IP 地址向服务器发起 TCP 连接,与浏览器建立 TCP 三次握手
  • 3.发送 HTTP 请求,接受 HTTP 响应
  • 5.服务器处理请求并返回内容。
  • 6.根据 HTTP 请求中的内容来决定如何获取相应的 HTML 文件,浏览器解析 HTML 代码,请求 js,css 等资源,最后进行页面渲染,呈现给用户
  • 7.断开 TCP 连接(四次挥手)

HTTP Cookie(也叫 Web Cookie 或浏览器 Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据。浏览器会存储 cookie 并在下次向同一服务器再发起请求时携带并发送到服务器上

Cookie 主要用于

  • 会话状态管理:如用户登录状态、购物车、游戏分数或其他需要记录的信息
  • 个性化设置:如用户自定义设置、主题和其他设置
  • 浏览器行为跟踪:如跟踪分析用户行为等

设置
可以包含expiresmax-agedomainpathsecure。 基本用法:

1
2
3
4
5
6
7
8
setCookie("value", "key", new Date(Date.now() + 3 * 1000)); //设置3s后过期
function setCookie(key, value, expire) {
window.document.cookie =
key +
"=" +
escape(value) +
(expire == null ? "" : "; expires=" + expire.toGMTString());
}
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/*\
|*|
|*| :: cookies.js ::
|*|
|*| A complete cookies reader/writer framework with full unicode support.
|*|
|*| https://developer.mozilla.org/en-US/docs/DOM/document.cookie
|*|
|*| This framework is released under the GNU Public License, version 3 or later.
|*| http://www.gnu.org/licenses/gpl-3.0-standalone.html
|*|
|*| Syntaxes:
|*|
|*| * docCookies.setItem(name, value[, end[, path[, domain[, secure]]]])
|*| * docCookies.getItem(name)
|*| * docCookies.removeItem(name[, path], domain)
|*| * docCookies.hasItem(name)
|*| * docCookies.keys()
|*|
\*/

var docCookies = {
getItem: function (sKey) {
return (
decodeURIComponent(
document.cookie.replace(
new RegExp(
"(?:(?:^|.*;)\\s*" +
encodeURIComponent(sKey).replace(/[-.+*]/g, "\\$&") +
"\\s*\\=\\s*([^;]*).*$)|^.*$"
),
"$1"
)
) || null
);
},
setItem: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) {
if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) {
return false;
}
var sExpires = "";
if (vEnd) {
switch (vEnd.constructor) {
case Number:
sExpires =
vEnd === Infinity
? "; expires=Fri, 31 Dec 9999 23:59:59 GMT"
: "; max-age=" + vEnd;
break;
case String:
sExpires = "; expires=" + vEnd;
break;
case Date:
sExpires = "; expires=" + vEnd.toUTCString();
break;
}
}
document.cookie =
encodeURIComponent(sKey) +
"=" +
encodeURIComponent(sValue) +
sExpires +
(sDomain ? "; domain=" + sDomain : "") +
(sPath ? "; path=" + sPath : "") +
(bSecure ? "; secure" : "");
return true;
},
removeItem: function (sKey, sPath, sDomain) {
if (!sKey || !this.hasItem(sKey)) {
return false;
}
document.cookie =
encodeURIComponent(sKey) +
"=; expires=Thu, 01 Jan 1970 00:00:00 GMT" +
(sDomain ? "; domain=" + sDomain : "") +
(sPath ? "; path=" + sPath : "");
return true;
},
hasItem: function (sKey) {
return new RegExp(
"(?:^|;\\s*)" +
encodeURIComponent(sKey).replace(/[-.+*]/g, "\\$&") +
"\\s*\\="
).test(document.cookie);
},
keys: /* optional method: you can safely remove it! */ function () {
var aKeys = document.cookie
.replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, "")
.split(/\s*(?:\=[^;]*)?;\s*/);
for (var nIdx = 0; nIdx < aKeys.length; nIdx++) {
aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]);
}
return aKeys;
},
};

Session

箭头函数

匿名函数
没有自己的 this 向上查找 this。
使用 new 操作符会报错
没有 prototype 属性
使用 call,apply,bind 不改变 this 走向
不能使用 yield 关键字,不能用作 Generator 函数
arguments.callee 递归调用箭头函数自身。

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
// 普通函数写法
function factorial (n) {
return !(n > 1) ? 1 : factorial(n - 1) * n;
}

[1,2,3,4,5].map(factorial);

// 箭头函数 arguments.callee
[1,2,3,4,5].map(function (n) {
return !(n > 1) ? 1 : arguments.callee(n - 1) * n;
});


var a = {
b: 21,
sum: function(v){
console.log(this,v)
return this.b + v
}
}

var fc = a.sum().call(null,2)

var f1 = function(){
console.log(arguments)
}
var a = 1;

var f2 = f1.bind(a,2)(3,4)

[1,,3,,6].map((e,i)=> {
console.log(e,i)
return e * 2
})

css3 动画与 canvas 区别与选择 ?

CSS优点:
浏览器会对CSS3动画做一些优化,导致CSS3动画性能上稍有优势(新建一个图层来跑动画)。
CSS3动画的代码相对简单。
CSS缺点:
动画控制上不够灵活。
兼容性不佳
部分动画无法实现(视差效果、滚动动画)

webgl canvas 区别
共同点: 都可以绘制2d 3d图形。
不同点: webGL有硬件加速渲染支持。。

JS优点:
灵活
对比与CSS的keyframe粒度更粗,CSS本身的时间函数是有限的,这块JS都可做弥补。
CSS很难做到两个以上的状态转化
JS缺点:
使用到JS运行时,调试方面不如CSS简单,CSS调试方式固定。
对于性能和兼容性较差的浏览器,CSS可以做到优雅降级,而JS需要额外代码兼容。

当您为UI元素采用较小的独立状态时,使用CSS。
当需要对动画进行大量控制时,使用JavaScript。
在特定的场景下可以使用SVG,可以使用CSS或JS去操作SVG变化。

by 掘金 Awu1227