公布订阅模式(事件总线)
形容:实现一个公布订阅模式,领有 on, emit, once, off
办法
class EventEmitter {constructor() {
// 蕴含所有监听器函数的容器对象
// 内部结构: {msg1: [listener1, listener2], msg2: [listener3]}
this.cache = {};}
// 实现订阅
on(name, callback) {if(this.cache[name]) {this.cache[name].push(callback);
}
else {this.cache[name] = [callback];
}
}
// 删除订阅
off(name, callback) {if(this.cache[name]) {this.cache[name] = this.cache[name].filter(item => item !== callback);
}
if(this.cache[name].length === 0) delete this.cache[name];
}
// 只执行一次订阅事件
once(name, callback) {callback();
this.off(name, callback);
}
// 触发事件
emit(name, ...data) {if(this.cache[name]) {
// 创立正本,如果回调函数内持续注册雷同事件,会造成死循环
let tasks = this.cache[name].slice();
for(let fn of tasks) {fn(...data);
}
}
}
}
事件是什么?事件模型?
事件是用户操作网页时产生的交互动作,比方 click/move,事件除了用户触发的动作外,还能够是文档加载,窗口滚动和大小调整。事件被封装成一个 event 对象,蕴含了该事件产生时的所有相干信息(event 的属性)以及能够对事件进行的操作(event 的办法)。
事件是用户操作网页时产生的交互动作或者网页自身的一些操作,古代浏览器一共有三种事件模型:
- DOM0 级事件模型,这种模型不会流传,所以没有事件流的概念,然而当初有的浏览器反对以冒泡的形式实现,它能够在网页中间接定义监听函数,也能够通过 js 属性来指定监听函数。所有浏览器都兼容这种形式。间接在 dom 对象上注册事件名称,就是 DOM0 写法。
- IE 事件模型,在该事件模型中,一次事件共有两个过程,事件处理阶段和事件冒泡阶段。事件处理阶段会首先执行指标元素绑定的监听事件。而后是事件冒泡阶段,冒泡指的是事件从指标元素冒泡到 document,顺次查看通过的节点是否绑定了事件监听函数,如果有则执行。这种模型通过 attachEvent 来增加监听函数,能够增加多个监听函数,会按程序顺次执行。
- DOM2 级事件模型,在该事件模型中,一次事件共有三个过程,第一个过程是事件捕捉阶段。捕捉指的是事件从 document 始终向下流传到指标元素,顺次查看通过的节点是否绑定了事件监听函数,如果有则执行。前面两个阶段和 IE 事件模型的两个阶段雷同。这种事件模型,事件绑定的函数是 addEventListener,其中第三个参数能够指定事件是否在捕捉阶段执行。
Compositon api
Composition API
也叫组合式 API,是 Vue3.x 的新个性。
通过创立 Vue 组件,咱们能够将接口的可重复部分及其性能提取到可重用的代码段中。仅此一项就能够使咱们的应用程序在可维护性和灵活性方面走得更远。然而,咱们的教训曾经证实,光靠这一点可能是不够的,尤其是当你的应用程序变得十分大的时候——想想几百个组件。在解决如此大的应用程序时,共享和重用代码变得尤为重要
- Vue2.0 中,随着性能的减少,组件变得越来越简单,越来越难保护,而难以保护的根本原因是 Vue 的 API 设计迫使开发者应用
watch,computed,methods
选项组织代码,而不是理论的业务逻辑。 - 另外 Vue2.0 短少一种较为简洁的低成本的机制来实现逻辑复用,尽管能够
minxis
实现逻辑复用,然而当mixin
变多的时候,会使得难以找到对应的data、computed
或者method
来源于哪个mixin
,使得类型推断难以进行。 - 所以
Composition API
的呈现,次要是也是为了解决 Option API 带来的问题,第一个是代码组织问题,Compostion API
能够让开发者依据业务逻辑组织本人的代码,让代码具备更好的可读性和可扩展性,也就是说当下一个开发者接触这一段不是他本人写的代码时,他能够更好的利用代码的组织反推出理论的业务逻辑,或者依据业务逻辑更好的了解代码。 - 第二个是实现代码的逻辑提取与复用,当然
mixin
也能够实现逻辑提取与复用,然而像后面所说的,多个mixin
作用在同一个组件时,很难看出property
是来源于哪个mixin
,起源不分明,另外,多个mixin
的property
存在变量命名抵触的危险。而Composition API
刚好解决了这两个问题。
艰深的讲:
没有 Composition API
之前 vue 相干业务的代码须要配置到 option 的特定的区域,中小型我的项目是没有问题的,然而在大型项目中会导致前期的维护性比较复杂,同时代码可复用性不高。Vue3.x 中的 composition-api 就是为了解决这个问题而生的
compositon api 提供了以下几个函数:
setup
ref
reactive
watchEffect
watch
computed
toRefs
- 生命周期的
hooks
都说 Composition API 与 React Hook 很像,说说区别
从 React Hook 的实现角度看,React Hook 是依据 useState 调用的程序来确定下一次重渲染时的 state 是来源于哪个 useState,所以呈现了以下限度
- 不能在循环、条件、嵌套函数中调用 Hook
- 必须确保总是在你的 React 函数的顶层调用 Hook
useEffect、useMemo
等函数必须手动确定依赖关系
而 Composition API 是基于 Vue 的响应式零碎实现的,与 React Hook 的相比
- 申明在
setup
函数内,一次组件实例化只调用一次setup
,而 React Hook 每次重渲染都须要调用 Hook,使得 React 的 GC 比 Vue 更有压力,性能也绝对于 Vue 来说也较慢 Compositon API
的调用不须要顾虑调用程序,也能够在循环、条件、嵌套函数中应用- 响应式零碎主动实现了依赖收集,进而组件的局部的性能优化由 Vue 外部本人实现,而
React Hook
须要手动传入依赖,而且必须必须保障依赖的程序,让useEffect
、useMemo
等函数正确的捕捉依赖变量,否则会因为依赖不正确使得组件性能降落。
尽管
Compositon API
看起来比React Hook
好用,然而其设计思维也是借鉴React Hook
的。
代码输入后果
function a(xx){
this.x = xx;
return this
};
var x = a(5);
var y = a(6);
console.log(x.x) // undefined
console.log(y.x) // 6
输入后果:undefined 6
解析:
- 最要害的就是 var x = a(5),函数 a 是在全局作用域调用,所以函数外部的 this 指向 window 对象。所以 this.x = 5 就相当于:window.x = 5。之后 return this,也就是说 var x = a(5) 中的 x 变量的值是 window,这里的 x 将函数外部的 x 的值笼罩了。而后执行 console.log(x.x),也就是 console.log(window.x),而 window 对象中没有 x 属性,所以会输入 undefined。
- 当指向 y.x 时,会给全局变量中的 x 赋值为 6,所以会打印出 6。
代码输入后果
var friendName = 'World';
(function() {if (typeof friendName === 'undefined') {
var friendName = 'Jack';
console.log('Goodbye' + friendName);
} else {console.log('Hello' + friendName);
}
})();
输入后果:Goodbye Jack
咱们晓得,在 JavaScript 中,Function 和 var 都会被晋升(变量晋升),所以下面的代码就相当于:
var name = 'World!';
(function () {
var name;
if (typeof name === 'undefined') {
name = 'Jack';
console.log('Goodbye' + name);
} else {console.log('Hello' + name);
}
})();
这样,答案就高深莫测了。
常见浏览器所用内核
(1)IE 浏览器内核:Trident 内核,也是俗称的 IE 内核;
(2)Chrome 浏览器内核:统称为 Chromium 内核或 Chrome 内核,以前是 Webkit 内核,当初是 Blink 内核;
(3)Firefox 浏览器内核:Gecko 内核,俗称 Firefox 内核;
(4)Safari 浏览器内核:Webkit 内核;
(5)Opera 浏览器内核:最后是本人的 Presto 内核,起初退出谷歌大军,从 Webkit 又到了 Blink 内核;
(6)360 浏览器、猎豹浏览器内核:IE + Chrome 双内核;
(7)搜狗、漫游、QQ 浏览器内核:Trident(兼容模式)+ Webkit(高速模式);
(8)百度浏览器、世界之窗内核:IE 内核;
(9)2345 浏览器内核:如同以前是 IE 内核,当初也是 IE + Chrome 双内核了;
(10)UC 浏览器内核:这个众口不一,UC 说是他们本人研发的 U3 内核,但如同还是基于 Webkit 和 Trident,还有说是基于火狐内核。
参考 前端进阶面试题具体解答
代码输入后果
function runAsync(x) {
const p = new Promise(r =>
setTimeout(() => r(x, console.log(x)), 1000)
);
return p;
}
function runReject(x) {const p = new Promise((res, rej) =>
setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x)
);
return p;
}
Promise.race([runReject(0), runAsync(1), runAsync(2), runAsync(3)])
.then(res => console.log("result:", res))
.catch(err => console.log(err));
输入后果如下:
0
Error: 0
1
2
3
能够看到在 catch 捕捉到第一个谬误之后,前面的代码还不执行,不过不会再被捕捉了。
留神:all
和 race
传入的数组中如果有会抛出异样的异步工作,那么只有最先抛出的谬误会被捕捉,并且是被 then 的第二个参数或者前面的 catch 捕捉;但并不会影响数组中其它的异步工作的执行。
懒加载与预加载的区别
这两种形式都是进步网页性能的形式,两者次要区别是一个是提前加载,一个是缓慢甚至不加载。懒加载对服务器前端有肯定的缓解压力作用,预加载则会减少服务器前端压力。
- 懒加载也叫提早加载,指的是在长网页中提早加载图片的机会,当用户须要拜访时,再去加载,这样能够进步网站的首屏加载速度,晋升用户的体验,并且能够缩小服务器的压力。它实用于图片很多,页面很长的电商网站的场景。懒加载的实现原理是,将页面上的图片的 src 属性设置为空字符串,将图片的实在门路保留在一个自定义属性中,当页面滚动的时候,进行判断,如果图片进入页面可视区域内,则从自定义属性中取出实在门路赋值给图片的 src 属性,以此来实现图片的提早加载。
- 预加载指的是将所需的资源提前申请加载到本地,这样前面在须要用到时就间接从缓存取资源。 通过预加载可能缩小用户的等待时间,进步用户的体验。我理解的预加载的最罕用的形式是应用 js 中的 image 对象,通过为 image 对象来设置 scr 属性,来实现图片的预加载。
代码输入后果
console.log(1);
setTimeout(() => {console.log(2);
Promise.resolve().then(() => {console.log(3)
});
});
new Promise((resolve, reject) => {console.log(4)
resolve(5)
}).then((data) => {console.log(data);
})
setTimeout(() => {console.log(6);
})
console.log(7);
代码输入后果如下:
1
4
7
5
2
3
6
代码执行过程如下:
- 首先执行 scrip 代码,打印出 1;
- 遇到第一个定时器 setTimeout,将其退出到宏工作队列;
- 遇到 Promise,执行外面的同步代码,打印出 4,遇到 resolve,将其退出到微工作队列;
- 遇到第二个定时器 setTimeout,将其退出到红工作队列;
- 执行 script 代码,打印出 7,至此第一轮执行实现;
- 指定微工作队列中的代码,打印出 resolve 的后果:5;
- 执行宏工作中的第一个定时器 setTimeout,首先打印出 2,而后遇到 Promise.resolve().then(),将其退出到微工作队列;
- 执行完这个宏工作,就开始执行微工作队列,打印出 3;
- 继续执行宏工作队列中的第二个定时器,打印出 6。
有哪些可能引起前端平安的问题?
- 跨站脚本 (Cross-Site Scripting, XSS): ⼀种代码注⼊⽅式, 为了与 CSS 辨别所以被称作 XSS。晚期常⻅于⽹络论坛, 起因是⽹站没有对⽤户的输⼊进⾏严格的限度, 使得攻击者能够将脚本上传到帖⼦让其余⼈浏览到有歹意脚本的⻚⾯, 其注⼊⽅式很简略包含但不限于 JavaScript / CSS / Flash 等;
- iframe 的滥⽤: iframe 中的内容是由第三⽅来提供的,默认状况下他们不受管制,他们能够在 iframe 中运⾏ JavaScirpt 脚本、Flash 插件、弹出对话框等等,这可能会毁坏前端⽤户体验;
- 跨站点申请伪造(Cross-Site Request Forgeries,CSRF): 指攻击者通过设置好的陷阱,强制对已实现认证的⽤户进⾏⾮预期的个⼈信息或设定信息等某些状态更新,属于被动攻打
- 歹意第三⽅库: ⽆论是后端服务器应⽤还是前端应⽤开发,绝⼤少数时候都是在借助开发框架和各种类库进⾏疾速开发,⼀旦第三⽅库被植⼊恶意代码很容易引起平安问题。
代码输入后果
function runAsync (x) {const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
return p
}
Promise.race([runAsync(1), runAsync(2), runAsync(3)])
.then(res => console.log('result:', res))
.catch(err => console.log(err))
输入后果如下:
1
'result:' 1
2
3
then 只会捕捉第一个胜利的办法,其余的函数尽管还会继续执行,然而不是被 then 捕捉了。
代码输入后果
var a = 1;
function printA(){console.log(this.a);
}
var obj={
a:2,
foo:printA,
bar:function(){printA();
}
}
obj.foo(); // 2
obj.bar(); // 1
var foo = obj.foo;
foo(); // 1
输入后果:2 1 1
解析:
- obj.foo(),foo 的 this 指向 obj 对象,所以 a 会输入 2;
- obj.bar(),printA 在 bar 办法中执行,所以此时 printA 的 this 指向的是 window,所以会输入 1;
- foo(),foo 是在全局对象中执行的,所以其 this 指向的是 window,所以会输入 1;
代码输入后果
(function(){var x = y = 1;})();
var z;
console.log(y); // 1
console.log(z); // undefined
console.log(x); // Uncaught ReferenceError: x is not defined
这段代码的关键在于:var x = y = 1; 实际上这里是从右往左执行的,首先执行 y = 1, 因为 y 没有应用 var 申明,所以它是一个全局变量,而后第二步是将 y 赋值给 x,讲一个全局变量赋值给了一个局部变量,最终,x 是一个局部变量,y 是一个全局变量,所以打印 x 是报错。
代码输入后果
async function async1() {console.log("async1 start");
await async2();
console.log("async1 end");
setTimeout(() => {console.log('timer1')
}, 0)
}
async function async2() {setTimeout(() => {console.log('timer2')
}, 0)
console.log("async2");
}
async1();
setTimeout(() => {console.log('timer3')
}, 0)
console.log("start")
输入后果如下:
async1 start
async2
start
async1 end
timer2
timer3
timer1
代码的执行过程如下:
- 首先进入
async1
,打印出async1 start
; - 之后遇到
async2
,进入async2
,遇到定时器timer2
,退出宏工作队列,之后打印async2
; - 因为
async2
阻塞了前面代码的执行,所以执行前面的定时器timer3
,将其退出宏工作队列,之后打印start
; - 而后执行 async2 前面的代码,打印出
async1 end
,遇到定时器 timer1,将其退出宏工作队列; - 最初,宏工作队列有三个工作,先后顺序为
timer2
,timer3
,timer1
,没有微工作,所以间接所有的宏工作依照先进先出的准则执行。
代码输入后果
console.log('1');
setTimeout(function() {console.log('2');
process.nextTick(function() {console.log('3');
})
new Promise(function(resolve) {console.log('4');
resolve();}).then(function() {console.log('5')
})
})
process.nextTick(function() {console.log('6');
})
new Promise(function(resolve) {console.log('7');
resolve();}).then(function() {console.log('8')
})
setTimeout(function() {console.log('9');
process.nextTick(function() {console.log('10');
})
new Promise(function(resolve) {console.log('11');
resolve();}).then(function() {console.log('12')
})
})
输入后果如下:
1
7
6
8
2
4
3
5
9
11
10
12
(1)第一轮事件循环流程剖析如下:
- 整体 script 作为第一个宏工作进入主线程,遇到
console.log
,输入 1。 - 遇到
setTimeout
,其回调函数被散发到宏工作 Event Queue 中。暂且记为setTimeout1
。 - 遇到
process.nextTick()
,其回调函数被散发到微工作 Event Queue 中。记为process1
。 - 遇到
Promise
,new Promise
间接执行,输入 7。then
被散发到微工作 Event Queue 中。记为then1
。 - 又遇到了
setTimeout
,其回调函数被散发到宏工作 Event Queue 中,记为setTimeout2
。
宏工作 Event Queue | 微工作 Event Queue |
---|---|
setTimeout1 | process1 |
setTimeout2 | then1 |
上表是第一轮事件循环宏工作完结时各 Event Queue 的状况,此时曾经输入了 1 和 7。发现了 process1
和then1
两个微工作:
- 执行
process1
,输入 6。 - 执行
then1
,输入 8。
第一轮事件循环正式完结,这一轮的后果是输入 1,7,6,8。
(2)第二轮工夫循环从 **setTimeout1**
宏工作开始:
- 首先输入 2。接下来遇到了
process.nextTick()
,同样将其散发到微工作 Event Queue 中,记为process2
。 new Promise
立刻执行输入 4,then
也散发到微工作 Event Queue 中,记为then2
。
宏工作 Event Queue | 微工作 Event Queue |
---|---|
setTimeout2 | process2 |
then2 |
第二轮事件循环宏工作完结,发现有 process2
和then2
两个微工作能够执行:
- 输入 3。
- 输入 5。
第二轮事件循环完结,第二轮输入 2,4,3,5。
(3)第三轮事件循环开始,此时只剩 setTimeout2 了,执行。
- 间接输入 9。
- 将
process.nextTick()
散发到微工作 Event Queue 中。记为process3
。 - 间接执行
new Promise
,输入 11。 - 将
then
散发到微工作 Event Queue 中,记为then3
。
宏工作 Event Queue | 微工作 Event Queue |
---|---|
process3 | |
then3 |
第三轮事件循环宏工作执行完结,执行两个微工作 process3
和then3
:
- 输入 10。
- 输入 12。
第三轮事件循环完结,第三轮输入 9,11,10,12。
整段代码,共进行了三次事件循环,残缺的输入为 1,7,6,8,2,4,3,5,9,11,10,12。
代码输入问题
window.number = 2;
var obj = {
number: 3,
db1: (function(){console.log(this);
this.number *= 4;
return function(){console.log(this);
this.number *= 5;
}
})()}
var db1 = obj.db1;
db1();
obj.db1();
console.log(obj.number); // 15
console.log(window.number); // 40
这道题目看清起来有点乱,然而实际上是考查 this 指向的:
- 执行 db1()时,this 指向全局作用域,所以 window.number 4 = 8,而后执行匿名函数,所以 window.number 5 = 40;
- 执行 obj.db1(); 时,this 指向 obj 对象,执行匿名函数,所以 obj.numer * 5 = 15。
什么是 XSS 攻打?
(1)概念
XSS 攻打指的是跨站脚本攻打,是一种代码注入攻打。攻击者通过在网站注入歹意脚本,使之在用户的浏览器上运行,从而盗取用户的信息如 cookie 等。
XSS 的实质是因为网站没有对恶意代码进行过滤,与失常的代码混合在一起了,浏览器没有方法分辨哪些脚本是可信的,从而导致了恶意代码的执行。
攻击者能够通过这种攻击方式能够进行以下操作:
- 获取页面的数据,如 DOM、cookie、localStorage;
- DOS 攻打,发送正当申请,占用服务器资源,从而使用户无法访问服务器;
- 毁坏页面构造;
- 流量劫持(将链接指向某网站);
(2)攻打类型
XSS 能够分为存储型、反射型和 DOM 型:
- 存储型指的是歹意脚本会存储在指标服务器上,当浏览器申请数据时,脚本从服务器传回并执行。
- 反射型指的是攻击者诱导用户拜访一个带有恶意代码的 URL 后,服务器端接收数据后处理,而后把带有恶意代码的数据发送到浏览器端,浏览器端解析这段带有 XSS 代码的数据后当做脚本执行,最终实现 XSS 攻打。
- DOM 型指的通过批改页面的 DOM 节点造成的 XSS。
1)存储型 XSS 的攻打步骤:
- 攻击者将恶意代码提交到⽬标⽹站的数据库中。
- ⽤户关上⽬标⽹站时,⽹站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器。
- ⽤户浏览器接管到响应后解析执⾏,混在其中的恶意代码也被执⾏。
- 恶意代码窃取⽤户数据并发送到攻击者的⽹站,或者假冒⽤户的⾏为,调⽤⽬标⽹站接⼝执⾏攻击者指定的操作。
这种攻打常⻅于带有⽤户保留数据的⽹站性能,如论坛发帖、商品评论、⽤户私信等。
2)反射型 XSS 的攻打步骤:
- 攻击者结构出非凡的 URL,其中蕴含恶意代码。
- ⽤户关上带有恶意代码的 URL 时,⽹站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。
- ⽤户浏览器接管到响应后解析执⾏,混在其中的恶意代码也被执⾏。
- 恶意代码窃取⽤户数据并发送到攻击者的⽹站,或者假冒⽤户的⾏为,调⽤⽬标⽹站接⼝执⾏攻击者指定的操作。
反射型 XSS 跟存储型 XSS 的区别是:存储型 XSS 的恶意代码存在数据库⾥,反射型 XSS 的恶意代码存在 URL ⾥。
反射型 XSS 破绽常⻅于通过 URL 传递参数的性能,如⽹站搜寻、跳转等。因为须要⽤户被动关上歹意的 URL 能力⽣效,攻击者往往会联合多种⼿段诱导⽤户点击。
3)DOM 型 XSS 的攻打步骤:
- 攻击者结构出非凡的 URL,其中蕴含恶意代码。
- ⽤户关上带有恶意代码的 URL。
- ⽤户浏览器接管到响应后解析执⾏,前端 JavaScript 取出 URL 中的恶意代码并执⾏。
- 恶意代码窃取⽤户数据并发送到攻击者的⽹站,或者假冒⽤户的⾏为,调⽤⽬标⽹站接⼝执⾏攻击者指定的操作。
DOM 型 XSS 跟前两种 XSS 的区别:DOM 型 XSS 攻打中,取出和执⾏恶意代码由浏览器端实现,属于前端 JavaScript ⾃身的安全漏洞,⽽其余两种 XSS 都属于服务端的安全漏洞。
代码输入后果
async function async1 () {console.log('async1 start');
await new Promise(resolve => {console.log('promise1')
resolve('promise1 resolve')
}).then(res => console.log(res))
console.log('async1 success');
return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')
这里是对下面一题进行了革新,加上了 resolve。
输入后果如下:
script start
async1 start
promise1
script end
promise1 resolve
async1 success
async1 end
代码输入问题
function fun(n, o) {console.log(o)
return {fun: function(m){return fun(m, n);
}
};
}
var a = fun(0); a.fun(1); a.fun(2); a.fun(3);
var b = fun(0).fun(1).fun(2).fun(3);
var c = fun(0).fun(1); c.fun(2); c.fun(3);
输入后果:
undefined 0 0 0
undefined 0 1 2
undefined 0 1 1
这是一道对于闭包的题目,对于 fun 办法,调用之后返回的是一个对象。咱们晓得,当调用函数的时候传入的实参比函数申明时指定的形参个数要少,剩下的形参都将设置为 undefined 值。所以 console.log(o);
会输入 undefined。而 a 就是是 fun(0)返回的那个对象。也就是说,函数 fun 中参数 n 的值是 0,而返回的那个对象中,须要一个参数 n,而这个对象的作用域中没有 n,它就持续沿着作用域向上一级的作用域中寻找 n,最初在函数 fun 中找到了 n,n 的值是 0。理解了这一点,其余运算就很简略了,以此类推。
代码输入后果
Promise.resolve().then(() => {console.log('1');
throw 'Error';
}).then(() => {console.log('2');
}).catch(() => {console.log('3');
throw 'Error';
}).then(() => {console.log('4');
}).catch(() => {console.log('5');
}).then(() => {console.log('6');
});
执行后果如下:
1
3
5
6
在这道题目中,咱们须要晓得,无论是 thne 还是 catch 中,只有 throw 抛出了谬误,就会被 catch 捕捉,如果没有 throw 出谬误,就被继续执行前面的 then。