JS 基础

自己实现一个 bind 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Function.prototype.bind = function (obj, ...arg) {
var context = this;
var bound = function (...newArg) {
arg = arg.concat(newArg);
return context.apply(obj, arg);
};
var F = function () {};
F.prototype = context.prototype;
bound.prototype = new F();
return bound;
};
function func(a, b) {
console.log(this);
console.log(a);
console.log(b);
}
const newFunc = func.bind({ new: true }, 1);
newFunc(2);

累加输出

1
2
3
4
5
6
7
8
9
var getId = (function () {
var i = 0;
return function () {
return i++;
};
})();
console.log(getId()); // 0
console.log(getId()); // 1

实现一个 add 方法

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
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;
function add(...args1) {
// 第一次执行时,定义一个数组专门用来存储所有的参数
var _args = [...args1];
// 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
var _adder = function(...args2) {
_args.push(...args2);
return _adder;
};
// 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
_adder.toString = function () {
return _args.reduce(function (a, b) {
return a + b;
});
}
return _adder;
}
add(1)(2)(3) // 6
add(1, 2, 3)(4) // 10
add(1)(2)(3)(4)(5) // 15
add(2, 6)(1) // 9

B 继承 A

1
2
3
4
5
6
7
function A(...) {}
A.prototype...
function B(...) {}
B.prototype...
B.prototype = Object.create(A.prototype);
// 再在 A 的构造函数里 new B(props);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Parent() {
this.name = 'parent';
}
function Child() {
this.age = 40;
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
var c = new Child();
console.log(c.name); // parent
console.log(c.age); // 40
console.log(Child.prototype.constructor); // Child 函数

对象的深拷贝

  • 一般对象和数组对象的克隆
1
2
3
4
5
6
7
8
9
10
11
12
13
function deepClone(obj) {
if (typeof obj !== 'object') {
return;
}
var newObj = obj instanceof Array ? [] : {};
for (var item in obj) {
if (obj.hasOwnProperty(item)) {
newObj[item] =
typeof obj[item] == 'object' ? deepClone(obj[item]) : obj[item];
}
}
return newObj;
}
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 clone(obj) {
var copy;
switch (typeof obj) {
case 'undefined':
break;
case 'number':
copy = obj - 0;
break;
case 'string':
copy = obj + '';
break;
case 'boolean':
copy = obj;
break;
case 'object':
if (obj === null) {
copy = null;
} else {
if (Object.prototype.toString.call(obj).slice(8, -1) === 'Array') {
copy = [];
for (var i = 0; i < obj.length; i++) {
copy.push(clone(obj[i]));
}
} else {
copy = {};
for (var j in obj) {
copy[j] = clone(obj[j]);
}
}
}
break;
default:
copy = obj;
break;
}
return copy;
}
  • 这个方法不能够拷贝函数
1
JSON.parse(JSON.stringify(obj));

代码的执行顺序

参考这一次,彻底弄懂 JavaScript 执行机制

任务队列中,在每一次事件循环中,macrotask 只会提取一个执行,而 microtask 会一直提取,直到 microsoft 队列为空为止。 也就是说如果某个 microtask 任务被推入到执行中,那么当主线程任务执行完成后,会循环调用该队列任务中的下一个任务来执行,直到该任务队列到最后一个任务为止。而事件循环每次只会入栈一个 macrotask,主线程执行完成该任务后又会检查 microtasks 队列并完成里面的所有任务后再执行 macrotask 的任务。
macrotasks: setTimeout, setInterval, setImmediate, I/O, UI rendering。
microtasks: process.nextTick, Promise, MutationObserver。

同步执行的代码 -> promise.then -> setTimeout

js 引擎单线程执行的,它是基于事件驱动的语言。它的执行顺序是遵循一个叫做事件队列的机制。浏览器有各种各样的线程,比如事件触发器、网络请求、定时器等等,线程的联系都是基于事件的。js 引擎处理到与其他线程相关的代码,就会分发给其他线程。他们处理完之后,需要 js 引擎计算时就是在事件队列里面添加一个任务。这个过程中 js 并不会阻塞代码等待其他线程执行完毕,而且其他线程执行完毕后添加事件任务告诉 js 引擎执行相关操作,这就是 js 的异步编程模型。在指定时间内,将任务放入事件队列,等待 js 引擎空闲后被执行。

那怎么知道主线程执行栈为空啊?js 引擎存在 monitoring process 进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去 Event Queue 那里检查是否有等待被调用的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 输出 2 5 3 6 1 4
setTimeout(() => {
console.log(1);
}, 0);
var promise1 = new Promise(function (resolve, reject) {
console.log(2);
resolve(3);
});
promise1.then((val) => {
console.log(val);
});
setTimeout(() => {
console.log(4);
}, 0);
var promise2 = new Promise(function (resolve, reject) {
console.log(5);
resolve(6);
});
promise2.then((val) => {
console.log(val);
});
1
2
3
4
5
6
7
8
9
10
// 不知道是多少,就是从2开始打印的
var i = 1;
setTimeout(() => {
i = 0;
console.log(i);
}, 5000);
while (i) {
i++;
console.log(i);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 输出 2 1 4 3 5
setTimeout(() => {
console.log(1);
}, 0);
const p = new Promise(function (resole, reject) {
console.log(2);
setTimeout(() => {
resole(3);
console.log(4);
}, 0);
});
p.then((a) => {
console.log(a);
});
setTimeout(() => {
console.log(5);
}, 0);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//输出 2,6,5,3,4,1
setTimeout(function () {
console.log(1);
}, 0);
new Promise(function (resolve, reject) {
console.log(2);
resolve();
})
.then(function () {
console.log(3);
})
.then(function () {
console.log(4);
});
process.nextTick(function () {
console.log(5);
});
console.log(6);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 输出 2 10 3 5 4 1
setTimeout(function () {
console.log(1);
}, 0);
new Promise(function (resolve, reject) {
console.log(2);
for (var i = 0; i < 10000; i++) {
if (i === 10) {
console.log(10);
}
i == 9999 && resolve();
}
console.log(3);
}).then(function () {
console.log(4);
});
console.log(5);

Event Table

就是个注册站,可以理解成一张 事件=>回调函数 对应表,它就是用来存储 JavaScript 中的异步事件 (request, setTimeout, IO 等) 及其对应的回调函数的列表。当指定的事情发生时,Event Table 会将这个函数移到 Event Queue。

实现一个 once 函数,传入函数参数只执行一次

1
2
3
4
5
6
7
8
9
10
function ones(func) {
var tag = true;
return function () {
if (tag == true) {
func.apply(null, arguments);
tag = false;
}
return undefined;
};
}

将原生的 ajax 封装成 promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var myNewAjax = function (url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('get', url);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
var json = JSON.parse(xhr.responseText);
resolve(json);
} else if (xhr.readyState == 4 && xhr.status != 200) {
reject('error');
}
};
});
};

如何实现一个私有变量,用 getName 方法可以访问,不能直接访问

如下方式无法解决这个问题,enumerable: false 是 for-in 中不会遍历。

1
2
3
4
5
6
7
8
9
obj = {
name: 'abc',
getName: function () {
return this.name;
},
};
Object.defineProperty(obj, 'name', {
enumerable: false,
});

如下方式可行。

1
2
3
4
5
6
7
function product() {
var name = 'abc';
this.getName = function () {
return name;
};
}
var obj = new product();

==和===、以及 Object.is 的区别

  • == 等于,存在强制转换成 number,null == undefined 的问题
  • === 严格等于,不存在类型转换,存在 NaN !== NaN, +0 === -0
  • Object.is 加强版严格等于

requestAnimationFrame

浏览器(所以只能在浏览器中使用)专门为动画提供的 API,让 DOM 动画、Canvas 动画、 SVG 动画、WebGL 动画等有一个统一的刷新机制。requestAnimationFrame 不需要设置时间间隔,RAF 采用的是系统时间间隔,并请求浏览器在下一次重绘之前调用指定的函数来更新动画,一次,若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用 window.requestAnimationFrame(),一般用于动画。

  • requestAnimationFrame 会把每一帧中的所有 DOM 操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率
  • 在隐藏或不可见的元素中,requestAnimationFrame 将不会进行重绘或回流,这当然就意味着更少的 CPU、GPU 和内存使用量
  • requestAnimationFrame 是由浏览器专门为动画提供的 API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了 CPU 开销

setTimeout 与 requestAnimationFrame 的区别

引擎层面:

  • setTimeout 属于 JS 引擎,存在事件轮询,存在事件队列
  • requestAnimationFrame 属于 GUI 引擎,发生在渲染过程中的重绘重排部分,与电脑分辨路保持一致

性能层面:

  • 当页面被隐藏或最小化时,定时器 setTimeout 仍在后台执行动画任务
  • 当页面处于未激活的状态下,该页面的屏幕刷新任务会被系统暂停,requestAnimationFrame 也会停止

应用层面:

  • 利用 setTimeout,这种定时机制去做动画,模拟固定时间刷新页面
  • requestAnimationFrame 由浏览器专门为动画提供的 API,在运行时浏览器会自动优化方法的调用,在特定性环境下可以有效节省了 CPU 开销

用 setTimeout 来实现 setInterval

setInterval 有两个问题:1.某些间隔会被跳过 2.多个定时器的代码执行时间 可能会比预期小。

1
2
3
4
5
function say() {
//do something
setTimeout(say, 200);
}
setTimeout(say, 200);
1
2
3
4
setTimeout(function () {
// do something
setTimeout(arguments.callee, 200);
}, 200);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function _setInterval(fn, time) {
function cb() {
fn();
setTimeout(arguments.callee, time);
}
setTimeout(cb, time);
}
function _setInterval(fn, time) {
fn();
setTimeout(() => _setInterval(fn, time), time);
}
_setInterval(() => {
console.log('a');
}, 1000);

setTimeout 的时间

首先要明白 Javascript 是单线程,单线程就意味着所有任务需要排队。然后会将所有任务分成两类:同步任务和异步任务!同步任务:在主线程上执行的任务,只有前一个任务执行完成,才会执行后一个!异步任务:不进入主线程、而进入“任务队列”的任务,当主线程上的任务执行完,主线程才会去执行“任务队列”。

对于 setTimeout(fn,200),当到 200ms 时,fn 会被放进“任务队列”,而“任务队列”必须要等到主线程已有的代码执行完才会执行 fn,所以当程序执行到 setTimeout(fn,200)这一行时,时间就开始计算,但是 fn 实际执行时并不一定是在 200ms 后,可能是在更久的时间后(取决于主线程上的同步代码的执行时间)。

JS 怎么控制一次加载一张图片,加载完后再加载下一张

1
2
3
4
5
6
7
8
9
10
<script type="text/javascript">
var obj = new Image();
obj.src = '图片地址';
obj.onload = function () {
alert('图片的宽度为:' + obj.width + ';图片的高度为:' + obj.height);
document.getElementById('mypic').innnerHTML =
"<img src='" + this.src + "' />";
};
</script>
<div id="mypic">onloading……</div>

如何实现 sleep 的效果(es5 或者 es6)

  • while 循环的方式,容易造成死循环
1
2
3
4
5
6
7
function sleep(ms) {
var start = Date.now(),
expire = start + ms;
while (Date.now() < expire);
console.log('1111');
return;
}
  • 通过 promise 来实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function sleep(ms) {
var temple = new Promise((resolve) => {
console.log(111);
setTimeout(resolve, ms);
});
return temple;
}
sleep(500).then(function () {
console.log(222);
});
async function test() {
var temple = await sleep(1000);
console.log(222);
return temple;
}
test();
  • 通过 generate 来实现
1
2
3
4
5
6
7
8
9
10
11
function* sleep(ms) {
yield new Promise(function (resolve, reject) {
console.log(111);
setTimeout(resolve, ms);
});
}
sleep(500)
.next()
.value.then(function () {
console.log(222);
});

简单的实现一个 promise

实现一个完美符合 Promise/A+规范的 Promise

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 myPromise(constructor) {
let self = this;
self.status = 'pending'; //定义状态改变前的初始状态
self.value = undefined; //定义状态为resolved的时候的状态
self.reason = undefined; //定义状态为rejected的时候的状态
function resolve(value) {
//两个==="pending",保证了状态的改变是不可逆的
if (self.status === 'pending') {
self.value = value;
self.status = 'resolved';
}
}
function reject(reason) {
//两个==="pending",保证了状态的改变是不可逆的
if (self.status === 'pending') {
self.reason = reason;
self.status = 'rejected';
}
}
//捕获构造异常
try {
constructor(resolve, reject);
} catch (e) {
reject(e);
}
}
myPromise.prototype.then = function (onFullfilled, onRejected) {
let self = this;
switch (self.status) {
case 'resolved':
onFullfilled(self.value);
break;
case 'rejected':
onRejected(self.reason);
break;
default:
}
};
var mm = new myPromise(function (resolve, reject) {
resolve('123');
});
mm.then(
function (success) {
console.log(success);
},
function () {
console.log('fail!');
}
);
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
class PromiseM {
constructor(process) {
this.status = 'pending';
this.msg = '';
process(this.resolve.bind(this), this.reject.bind(this));
return this;
}
resolve(val) {
this.status = 'fulfilled';
this.msg = val;
}
reject(err) {
this.status = 'rejected';
this.msg = err;
}
then(fufilled, reject) {
if (this.status === 'fulfilled') {
fufilled(this.msg);
}
if (this.status === 'rejected') {
reject(this.msg);
}
}
}
var mm = new PromiseM(function (resolve, reject) {
resolve('123');
});
mm.then(
function (success) {
console.log(success);
},
function () {
console.log('fail!');
}
);

Function.proto(getPrototypeOf)是什么?

获取一个对象的原型,在 chrome 中可以通过 __proto__ 的形式,或者在 ES6 中可以通过 Object.getPrototypeOf 的形式。

1
2
3
4
5
6
7
8
9
Function.__proto__ === Function.prototype; // true
Function.prototype.constructor === Function; // true
Function.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null; // true
function F() {}
var f = new F();
F.__proto__ === Function.prototype; // true
F.constructor === Function; // true
f.constructor === F; // true

JS 判断类型

typeof,instanceof,Object.prototype.toString.call() 等。

数组去重

  • indexOf 循环去重
  • ES6 Set 去重 Array.from(new Set(array))
  • Object 键值对去重,把数组的值存成 Object 的 key 值,比如 Object[value1] = true,在判断另一个值的时候,如果 Object[value2]存在的话,就说明该值是重复的

JS 实现跨域

跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对 JavaScript 实施的安全限制,那么只要协议、域名、端口有任何一个不同,都被当作是不同的域。跨域原理,即是通过各种方式,避开浏览器的安全限制。

  • JSONP
  • document.domain + iframe
  • window.name + iframe
  • location.hash + iframe
  • 服务端设置 Access-Control-Allow-Origin
  • 启一个代理服务器进行数据转发

JS 数据类型

引自 MDN JavaScript 数据类型和数据结构

最新的 ECMAScript 标准定义了 8 种数据类型:

  • 6 种原始类型,使用 typeof 运算符检查:
    • undefined:typeof instance === “undefined”
    • Boolean:typeof instance === “boolean”
    • Number:typeof instance === “number”
    • String:typeof instance === “string
    • BigInt:typeof instance === “bigint”
    • Symbol :typeof instance === “symbol”
  • null:typeof instance === “object”。
  • Object:typeof instance === “object”。任何 constructed 对象实例的特殊非数据结构类型,也用做数据结构:new Object,new Array,new Map,new Set,new WeakMap,new WeakSet,new Date,和几乎所有通过 new keyword 创建的东西。

除 Object 以外的所有类型都是不可变的(值本身无法被改变)。

记住 typeof 操作符的唯一目的就是检查数据类型,如果我们希望检查任何从 Object 派生出来的结构类型,使用 typeof 是不起作用的,因为总是会得到 “object”。检查 Object 种类的合适方式是使用 instanceof 关键字。但即使这样也存在误差。

JS 的全排列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function fullpermutate(str) {
var result = [];
if (str.length > 1) {
//遍历每一项
for (var m = 0; m < str.length; m++) {
//拿到当前的元素
var left = str[m];
//除当前元素的其他元素组合
var rest = str.slice(0, m) + str.slice(m + 1, str.length);
//上一次递归返回的全排列
var preResult = fullpermutate(rest);
//组合在一起
for (var i = 0; i < preResult.length; i++) {
var tmp = left + preResult[i];
result.push(tmp);
}
}
} else if (str.length === 1) {
result.push(str);
}
return result;
}

补充 get 和 post 请求在缓存方面的区别

  • get 请求类似于查找的过程,用户获取数据,可以不用每次都与数据库连接,所以可以使用缓存
  • post 做的一般是修改和删除的工作,所以必须与数据库交互,不能使用缓存

说一下闭包

闭包就是能够读取其他函数内部变量的函数,或者子函数在外调用,子函数所在的父函数的作用域不会被释放。

  • 单例模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var Singleton = (function () {
var instance;
var CreateSingleton = function (name) {
if (instance) {
return instance;
}
this.name = name;
this.getName();
instance = this;
return instance;
};
CreateSingleton.prototype.getName = function () {
console.log(this.name);
};
return CreateSingleton;
})();
var a = new Singleton('a');
var b = new Singleton('b');
a.getName();
b.getName();
console.log(a === b);

说说前端中的事件流

HTML 中与 javascript 交互是通过事件驱动来实现的,例如鼠标点击事件 onclick、页面的滚动事件 onscroll 等等,可以向文档或者文档中的元素添加事件侦听器来预订事件。想要知道这些事件是在什么时候进行调用的,就需要了解一下“事件流”的概念。

什么是事件流:事件流描述的是从页面中接收事件的顺序,DOM2 级事件流包括下面几个阶段:

  • 事件捕获阶段
  • 处于目标阶段
  • 事件冒泡阶段

addEventListener 是 DOM2 级事件新增的指定事件处理程序的操作,这个方法接收 3 个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是 true,表示在捕获阶段调用事件处理程序;如果是 false,表示在冒泡阶段调用事件处理程序。IE 只支持事件冒泡。

DOM0 级和 DOM2 级有什么区别:

  • document.getElementById("btn").onclick = function(){}; 是 DOM0,只能绑定一个事件
  • document.getElementById("btn").addEventListener("click", function(){}, false); 是 DOM2,可以绑定多个事件

如何让事件先冒泡后捕获

如果要实现先冒泡后捕获的效果,对于同一个事件,监听捕获和冒泡,分别对应相应的处理函数,监听到捕获事件,先暂缓执行,直到冒泡事件被捕获后再执行捕获事件。

说一下事件委托或事件代理

简介:事件委托指的是,不在事件的发生地(直接 dom)上设置监听函数,而是在其父元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上事件的触发,通过判断事件发生元素 DOM 的类型,来做出不同的响应。

举例:最经典的就是 ul 和 li 标签的事件监听,比如我们在添加事件时候,采用事件委托机制,不会在 li 标签上直接添加,而是在 ul 父元素上添加。 好处:比较合适动态元素的绑定,新添加的子元素也会有监听函数,也可以有事件触发机制。

经过试验,在事件捕获阶段做事件代理也可以啊!!!

mouseover 和 mouseenter 的区别

mouseenter:当鼠标移入元素本身(不包含元素的子元素)会触发事件,也就是不会冒泡,对应的移除事件是 mouseleave

mouseover:当鼠标移入元素或其子元素都会触发事件,所以有一个重复触发,冒泡的过程,对应的移除事件是 mouseout

异步加载 JS 的方法

defer:如果 script 标签设置了该属性,会异步的下载并且不会影响到后续 DOM 的渲染,会在文档渲染完毕后 DOMContentLoaded 事件调用前执行;如果有多个设置了 defer 的 script 标签存在,则会按照顺序执行所有的 script。

async:如果 script 标签设置了该属性,会异步的下载并在允许的情况下执行,并不会按着 script 在页面中的顺序来执行,而是谁先加载完谁执行。

一句话,defer 是“渲染完再执行”,async 是“下载完就执行”。另外,如果有多个 defer 脚本,会按照它们在页面出现的顺序加载,而多个 async 脚本是不能保证加载顺序的。

Ajax 解决浏览器缓存问题

  • 在 ajax 发送请求前加上 anyAjaxObj.setRequestHeader(“If-Modified-Since”,”0”)
  • 在 ajax 发送请求前加上 anyAjaxObj.setRequestHeader(“Cache-Control”,”no-cache”)
  • 在 URL 后面加上时间戳:”nowtime=” + new Date().getTime()
  • 如果是使用 jQuery,直接这样就可以了 $.ajaxSetup({cache:false},这样页面的所有 ajax 都会执行这条语句就是不需要保存缓存记录。

垃圾回收的方法

标记清除和计数引用,用引用计数法会存在内存泄露,如 objA 和 objB 通过各自的属性相互引用。

eval 是做什么的

将对应的字符串解析成 JS 并执行,应该避免使用,因为非常消耗性能(2 次,一次解析成 JS,一次执行)。

说一下类的创建和继承

JS 的 new 操作符做了哪些事情

new 操作符新建了一个空对象,这个对象原型指向构造函数的 prototype,执行构造函数后返回这个对象。

  • 创建一个类的实例:创建一个空对象 obj,然后把这个空对象的proto设置为构造函数的 prototype
  • 初始化实例:构造函数被传入参数并调用,关键字 this 被设定指向该实例 obj
  • 返回实例 obj

JS 的节流和防抖

http://www.cnblogs.com/coco1s/p/5499469.html

暂停死区

在代码块内,使用 let、const 命令声明变量之前,该变量都是不可用的。这在语法上, 称为“暂时性死区”。

编写代码,满足以下条件:(1)Hero(“37er”);执行结果为 Hi! This is 37er (2)Hero(“37er”).kill(1).recover(30);执行结果为 Hi! This is 37er Kill 1 bug Recover 30 bloods (3)Hero(“37er”).sleep(10).kill(2)执行结果为 Hi! This is 37er //等待 10s 后 Kill 2 bugs //注意为 bugs (双斜线后的为提示信息, 不需要打印)

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
function Hero(name) {
let o = new Object();
o.name = name;
o.time = 0;
console.log('Hi! This is ' + o.name);
o.kill = function (bugs) {
setTimeout(function () {
if (bugs == 1) {
console.log('Kill ' + bugs + ' bug');
} else {
console.log('Kill ' + bugs + ' bugs');
}
}, 1000 * this.time);
return o;
};
o.recover = function (bloods) {
console.log('Recover ' + bloods + ' bloods');
return o;
};
o.sleep = function (sleepTime) {
o.time = sleepTime;
return o;
};
return o;
}

说一下什么是 virtual dom

用 JavaScript 对象结构表示 DOM 树的结构,然后用这个树构建一个真正的 DOM 树,插到文档当中。当状态变更的时候,重新构造一棵新的对象树,用新的树和旧的树进行比较,记录两棵树差异,把所记录的差异应用到所构建的真正的 DOM 树上,视图就更新了。Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。

简单实现 Node 的 Events 模块,订阅-发布模式

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
function Events() {
this.handles = {};
this.on = function (eventName, callBack) {
if (!this.handles[eventName]) {
this.handles[eventName] = [];
}
this.handles[eventName].push(callBack);
};
this.emit = function (eventName, obj) {
if (this.handles[eventName]) {
for (var i = 0; i < this.handles[eventName].length; i++) {
this.handles[eventName][i](obj);
}
}
};
this.remove = function (eventName, callBack) {
if (!callBack) {
this.handles[eventName] = [];
} else {
for (var i = 0; i < this.handles[eventName].length; i++) {
if (callBack === this.handles[eventName][i]) {
this.handles[eventName].splice(i, 1);
}
}
}
};
return this;
}
var events = new Events();
events.on('say', function (name) {
console.log('Hello', name);
});
events.emit('say', 'Jony yu');

写个函数,可以转化下划线命名到驼峰命名

1
2
3
4
5
6
7
8
9
10
11
12
13
function UnderlineToHump(para) {
var result = '';
var arr = para.split('_');
for (var s of arr) {
if (result.length === 0) {
result = result + s.toLowerCase();
} else {
result =
result + s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
}
}
return result;
}

数组的随机排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function shuffle(arr) {
arr.sort(function () {
return Math.random() - 0.5;
});
}
function shuffle(arr) {
let i = arr.length;
while (i) {
let j = Math.floor(Math.random() * i);
i--;
[arr[j], arr[i]] = [arr[i], arr[j]];
}
}

iframe 的优缺点

优点

  • 程序调入静态页面比较方便,能够把嵌入的网页原样展现出来
  • 模块分离,便于更改,增加代码的可重用
  • 重载页面时不需要重载整个页面,只需要重载页面中的一个框架页
  • 可以解决跨域问题

缺点

  • 样式和脚本需要额外链入,增加服务器的 http 请求,占用 http 链接数
  • 会影响搜索引擎优化,不利于网站排名
  • 链接导航需要定义好,否则用户可能无法离开当前框架
  • 阻塞页面加载,影响网页加载速度,window 的 onload 事件需要在所有 iframe 加载完毕后触发
  • 多处滚动条问题

vue2 vs. vue3

  • 针对 API 的优化,createApp,生命周期函数修改,组合式 api,如 setup 回调
  • Object.defineProperty 改为 ES6 的 Proxy
  • 加强 TypeScript 支持
  • 更快,重构了虚拟 DOM
  • 更小,Tree-shaking

Vue Function-based API RFC

React 和 Vue 区别

React 和 Vue 区别

  • 监听数据变化的实现原理不同

Vue 通过 getter/setter 以及一些函数的劫持,能精确知道数据变化,Vue 使用的是可变数据。React 默认是通过比较引用的方式(diff)进行的,React 更强调数据的不可变。

  • 数据流的不同

Vue 双向绑定。 React 单向数据流。如果使用了 Vuex 以及 Redux 等单向数据流的状态管理框架,可能感受不到这一点的区别了。Vuex 和 Redux 的区别如下。

从表面上来说,store 注入和使用方式有一些区别。在 Vuex 中,$store被直接注入到了组件实例中,因此可以比较灵活的使用:使用dispatch、commit提交更新,通过mapState或者直接通过this.$store 来读取数据。在 Redux 中,我们每一个组件都需要显示的用 connect 把需要的 props 和 dispatch 连接起来。另外,Vuex 更加灵活一些,组件中既可以 dispatch action,也可以 commit updates,而 Redux 中只能进行 dispatch,不能直接调用 reducer 进行修改。

从实现原理上来说,最大的区别是两点:Redux 使用的是不可变数据,而 Vuex 的数据是可变的,因此,Redux 每次都是用新 state 替换旧 state,而 Vuex 是直接修改。Redux 在检测数据变化的时候,是通过 diff 的方式比较差异的,而 Vuex 其实和 Vue 的原理一样,是通过 getter/setter 来比较的,这两点的区别,也是因为 React 和 Vue 的设计理念不同。React 更偏向于构建稳定大型的应用,非常的科班化。相比之下,Vue 更偏向于简单迅速的解决问题,更灵活,不那么严格遵循条条框框。因此也会给人一种大型项目用 React,小型项目用 Vue 的感觉。

  • 组件通信的区别

React 本身并不支持自定义事件,Vue 中子组件向父组件传递消息有两种方式:事件和回调函数,而且 Vue 更倾向于使用事件。但是在 React 中我们都是使用回调函数的,这可能是他们二者最大的区别。

  • 模板渲染方式的不同

React 是通过 JSX 渲染模板,而 Vue 是通过一种拓展的 HTML 语法进行渲染。在深层上,模板的原理不同:React 是在组件 JS 代码中,通过原生 JS 实现模板中的常见语法,比如插值,条件,循环等,都是通过 JS 语法实现的,更加纯粹更加原生。而 Vue 是在和组件 JS 代码分离的单独的模板中,通过指令来实现的,比如条件语句就需要 v-if 来实现对这一点,这样的做法显得有些独特,会把 HTML 弄得很乱。

举个例子,说明 React 的好处:react 中 render 函数是支持闭包特性的,所以我们 import 的组件在 render 中可以直接调用。但是在 Vue 中,由于模板中使用的数据都必须挂在 this 上进行一次中转,所以我们 import 一个组件完了之后,还需要在 components 中再声明下,这样显然是很奇怪但又不得不这样的做法。

  • 渲染过程不同

Vue 可以更快地计算出 Virtual DOM 的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。React 在应用的状态被改变时,全部子组件都会重新渲染。通过 shouldComponentUpdate 这个生命周期方法可以进行控制,但 Vue 将此视为默认的优化。

  • 框架本质不同

Vue 本质是 MVVM 框架,由 MVC 发展而来。React 是前端组件化框架,由后端组件化发展而来。

debounce 实现

1
2
3
4
5
6
7
8
9
10
11
12
function getDebounced(func, time) {
let timer;
return function (...args) {
const that = this;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
func.apply(that, args);
}, time);
};
}

throttle 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function getThrottle(fn, delay) {
let valid = true;
return function () {
if (!valid) {
// 休息时间 暂不接客
return false;
}
// 工作时间,执行函数并且在间隔期内把状态位设为无效
valid = false;
setTimeout(() => {
fn();
valid = true;
}, delay);
};
}

webpack 打包过程

其中包含四个核心概念

入口(entry):指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的
输出(output):告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist
loader:让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)
插件(plugins):插件则可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。

  • 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;
  • 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译;
  • 确定入口:根据配置中的 entry 找出所有的入口文件
  • 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
  • 完成模块编译:在经过第 4 步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
  • 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
  • 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

Promise.all 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Promise.prototype.all = function (promises) {
let results = [];
let promiseCount = 0;
let promisesLength = promises.length;
return new Promise(function (resolve, reject) {
for (let i = 0; i < promisesLength; i++) {
Promise.resolve(promises[i]).then(
function (res) {
promiseCount++;
results[i] = res;
if (promiseCount === promisesLength) {
return resolve(results);
}
},
function (err) {
return reject(err);
}
);
}
});
};

localStorage 监听

当同源页面的某个页面修改了 localStorage,其余的同源页面只要注册了 storage 事件,就会触发。

  • 同一浏览器打开了两个同源页面
  • 其中一个网页修改了 localStorage
  • 另一网页注册了 storage 事件

在同源的两个页面中,可以监听 storage 事件:

1
2
3
window.addEventListener('storage', function (e) {
alert(e.newValue);
});

在同一个页面中,对 localStorage 的 setItem 方法进行重写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var originalSetItem = localStorage.setItem;
localStorage.setItem = function (key, newValue) {
var setItemEvent = new Event('setItemEvent');
setItemEvent.newValue = newValue;
window.dispatchEvent(setItemEvent);
originalSetItem.apply(this, arguments);
};
window.addEventListener('setItemEvent', function (e) {
alert(e.newValue);
});
localStorage.setItem('name', 'wang');

router 原理

【源码拾遗】从 vue-router 看前端路由的两种实现

更新视图但不重新请求页面,是前端路由原理的核心之一,目前在浏览器环境中这一功能的实现主要有 2 种方式,Hash 模式和 History 模式。

hash(#)是 URL 的锚点,代表的是网页中的一个位置,单单改变#后的部分,浏览器只会滚动到相应位置,不会重新加载网页,也就是说 #是用来指导浏览器动作的,对服务器端完全无用,HTTP 请求中也不会不包括#;同时每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用”后退”按钮,就可以回到上一个位置。可以为 hash 的改变添加监听事件:window.addEventListener("hashchange", funcRef, false)

HTML5 History API 提供了一种功能,能让开发人员在不刷新整个页面的情况下修改站点的 URL,就是利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。

虚拟 dom 的作用

之前使用原生 js 或者 jquery 写页面的时候会发现操作 DOM 是一件非常麻烦的一件事情,往往是 DOM 标签和 js 逻辑同时写在 js 文件里,数据交互时不时还要写很多的 input 隐藏域,如果没有好的代码规范的话会显得代码非常冗余混乱,耦合性高并且难以维护。

另外一方面在浏览器里一遍又一遍的渲染 DOM 是非常非常消耗性能的,常常会出现页面卡死的情况;所以尽量减少对 DOM 的操作成为了优化前端性能的必要手段,vdom 就是将 DOM 的对比放在了 js 层,通过对比不同之处来选择新渲染 DOM 节点,从而提高渲染效率。

Virtual DOM 是用 JS 对象记录一个 dom 节点的副本,当 dom 发生更改时候,先用虚拟 dom 进行 diff,算出最小差异,然后再修改真实 dom,当用传统的方式操作 DOM 的时候,浏览器会从构建 DOM 树开始从头到尾执行一遍流程,效率很低。而虚拟 DOM 是用 javascript 对象表示的,而操作 javascript 是很简便高效的。虚拟 DOM 和真正的 DOM 有一层映射关系,很多需要操作 DOM 的地方都会去操作虚拟 DOM,最后统一一次更新 DOM,因而可以提高性能。

找出数组中唯一不同的数

1
2
3
4
5
6
7
function findOnly(arr) {
let result = 0;
arr.forEach((i) => {
result = i ^ result;
});
return result;
}
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
// 如果存在2个这样的元素
function findOnlyTwo(arr) {
let result = 0;
let post = 0;
let x = 0;
let y = 0;
// 得到所以元素异或结果
for (let i = 0; i < arr.length; i++) {
result ^= arr[i];
}
// 获取 result 二进制最低位1的位置 1&1 =1
for (let i = 0; i < 32; i++) {
if (((result >> i) & 1) == 1) {
post = i;
break;
}
}
// 遍历数组得到 x
for (let i = 0; i < arr.length; i++) {
if ((arr[i] >> post) & (1 == 1)) {
x ^= arr[i];
}
}
y = result ^ x;
console.log(x, y);
}

找出数组中不相邻元素的最大和

1
2
3
4
5
6
7
8
9
10
11
12
// 动态规划
function getMaxSum(arr) {
let result = [];
result.push(arr[0]);
result.push(Math.max(arr[0], arr[1]));
for(let i = 2; i< arr.length; i++) {
let a = result[i-2] + arr[i];
let b = result[i-1];
result.push(Math.max(a, b));
}
return result[result.length - 1];
}

Vue 大数据量性能优化

  • 减少无用字段
  • 数据扁平化
  • 利用computed
  • 数据静态化