一、如何正确判断一个数据的基本类型?
1、typeof
typeof 对于原始类型(null undefined string number boolean symbol),除了null 都能显示正确的类型
typeof null === 'object'
复制代码
typeof 对于对象来说,除了函数外其他都是object 类型
typeof [] === 'object'
typeof console.log === 'function'
复制代码
所以typeof 无法正确区分对象和null(数组和null)
2、instanceof 判断已知对象类型的方法
MDN 的解释: instanceof
运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype
属性
判断一个对象是否是数据类型的实例
因而可以衍生出 用constructor 来判断类型({}).constructor == Object,但是constructor 是可以修改的
function Fn(){};
Fn.prototype=new Array();
var f=new Fn()
console.log(f.constructor===Fn); // false复制代码
,因而不准确。
3、通用型,借用Object.prototype.toString.call 来实现
var gettype=Object.prototype.toString
gettype.call('aaaa') 输出 [object String]
gettype.call(2222) 输出 [object Number]
gettype.call(true) 输出 [object Boolean]
gettype.call(undefined) 输出 [object Undefined]
复制代码
// 进行一层封装
let Type = (function(){
let type = {};
let typeArr = ['String', 'Object', 'Number', 'Array', 'Undefined', 'Null', 'Symbol'];
for(let i = 0; i < typeArr.length; i++){
type['Is'+ typeArr[i]] = function(obj){
return Object.prototype.toString.call(obj) === '[object '+ typeArr[i] +']'
}
}
return type
})()
复制代码
eg:(第一个字母小写,第二个字母大写)
Type.IsFunction(function() {}) Type.IsObject({}) 这样使用。复制代码
二、对象的深浅拷贝
什么是浅拷贝?如何实现浅拷贝?什么是深拷贝?如何实现深拷贝?
浅拷贝
利用Object.assign({}, obj) or 展开运算符 ... 来实现浅拷贝
let a = {
age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(a.age) // 1
console.log(b.age) // 2复制代码
let a = {
age: 1
}
let b = { ...a }
a.age = 2复制代码
深拷贝
利用JSON.parse(JSON.stringify(object)) 来解决,(基本够用了)但这个有局限性,
- 会忽略undefined
- 会忽略symbol
- 不能序列化函数
- 不能解决循环引用的对象
自个封装 lodash 深拷贝函数
function deepClone(obj){
function isObject(o){
return (typeof o === 'object' || typeof o === 'function') && o !== null
}
if(!isObject(obj)){
throw new Error('not object')
}
let isArray = Array.isArray(obj)
let newObj = isArray? [...obj] : { ...obj }
Reflect.ownKeys(newObj).forEach((key)=>{
newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
})
return newObj
}
// eg
let obj = {
a: [1,2,3],
b:{
c:2,
d:3
}
}
let newObj = deepClone(obj)
newObj.b.c = 33
console.log(obj.b.c) // 2复制代码
三、call、apply 和 组合继承
function Product(name, price){
this.name = name;
this.price = price;
}
function Food(name, price){
Product.call(this, name, price)
this.category = 'food'
}
let foot1 = new Food('chees', 5);
foot1 // 复制代码
通过Food 构造方法里的call()
,成功使Food 扩展了name 以及price .(借用)
apply()
和 call()
都是为了改变某个函数运行时的上下文而存在的(就是为了改变函数内部的 this
指向)。然后,因为这两个方法会立即调用,所以为了弥补它们的缺失,还有个方法 bind()
。
/*apply()方法 参数是已数组的形式 add.apply(sub, [4,2]) */
function.apply(thisObj[, argArray])
/*call()方法 参数*/
function.call(thisObj[, arg1[, arg2[, [,...argN]]]]);复制代码
四、寄生组合继承(经典例子):
function Parent(value){
this.val = value
}
Parent.prototype.getValue = function(){
console.log(this.val)
}
function Child(value){
Parent.call(this, name)
}
// Child.prototype = new Parent() 组合继承
// 缺点就是在继承父类函数的时候调用了父类构造函数,导致子类的原型上多了不需要的父类属性,存在内存上的浪费
Child.prototype = Object.assign(Parent.prototype, {
constructor:{
value: Child,
enumerable: false,
writable: true,
configurable: true
}
})
const child = new Child(1)
child.getValue() // 1
child instanceof Parent // true
复制代码
继承实现的核心就是将父类的原型赋值给了子类,并且将构造函数设置为子类,这样既解决了无用的父类属性问题,还能正确的找到子类的构造函数。
es6 class 继承
class Parent{
constructor(value){
this.val = value
}
getValue(){
console.log(this.val)
}
}
class Child extends Parent{
constructor(value){
super(value)
this.val = value
}
}
let child = new Child(1)
child.getValue() // 1
child instanceof Parent //true复制代码
js 原型与原型链可以参考这个: https://juejin.im/post/5c72a1766fb9a049ea3993e6
五、防抖与节流
防抖即延时执行,指触发事件后在规定时间内回调函数只能执行一次,如果在规定时间内又触发该事件,则会重新开始算规定时间。
应用场景: 输入联想功能,用户不断输入时,用防抖来节约资源。
function callFn(content){
console.log('这是防抖回调函数')
}
// 利用回调函数 保存定时器标识
function debounce(func ,delay = 500){
let timer = null
return function(args){
let that = this
let _args = args
if(timer) clearTimeout(timer)
timer = setTimeout(function(){
func.call(that, _args)
}, delay)
}
}
let currDebounce = debounce(callFn, 500) // 返回延时函数
// 使用
let evtFn= document.querySelector('body')
evtFn.addEventListener('click', function(e){
currDebounce (e.target.value)
})复制代码
节流
当持续触发事件时,在规定时间段内只能调用一次,如再规定时间内又触发该事件,则return
,什么也不做。
应用场景,频繁触发事件,如滚动、resize 等。
// 定时器版
function throttle(func, delay = 500){
let timer = null;
return function(args){
let that = this;
let _args = args;
if(!timer){
timer = setTimeout(function(){
timer = null;
func.apply(that, _args)
}, delay)
}
}
}
// 时间戳版
function throttle(fun, delay = 500){
let previous = 0;
return function(args){
let now = Date.now();
let that = this;
let _args = args;
if(now - previous > delay){ // 如果时间差大于规定时间,则触发
fun.apply(that, _args)
previous = now
}
}
}复制代码
垃圾回收主要分为:标记清除算法(主流都是这个)与引用计数算法。
将cookie设置成HttpOnly是为了防止XSS攻击,窃取cookie内容,这样就增加了cookie的安全性。
把cookie设置为secure,只保证 cookie 与服务器之间的数据传输过程加密复制代码
六、渲染机制及重绘和回流
浏览器的渲染机制一般分为以下几个步骤:
-
- 处理 HTML 并构建 DOM 树。
- 处理 CSS 构建 CSSOM 树。
- 将 DOM 与 CSSOM 合并成一个渲染树(Render Tree)。
- 根据渲染树来布局,计算每个节点的位置。
- 调用 GPU 绘制,合成图层,显示在屏幕上。
- 重绘是 当节点需要更改外观而不会影响布局的,比如改变 color、background-color、visibility, outline等就叫称为重绘
- 回流是 布局或者几何属性需要改变 就称为回流。回流是影响浏览器性能的关键因素,因为其变化涉及到部分页面(或是整个页面)的布局更新。
回流必定会发生重绘,重绘不一定会引发回流。
浏览器优化:
现代浏览器大多都是通过队列机制来批量更新布局,浏览器会把修改操作放在队列中,至少一个浏览器刷新(即16.6ms)才会清空队列,但当你获取布局信息的时候,队列中可能有会影响这些属性或方法返回值的操作,即使没有,浏览器也会强制清空队列,触发回流与重绘来确保返回正确的值。
主要包括以下属性或方法:
offsetTop
、offsetLeft
、offsetWidth
、offsetHeight
scrollTop
、scrollLeft
、scrollWidth
、scrollHeight
clientTop
、clientLeft
、clientWidth
、clientHeight
width
、height
getComputedStyle()
getBoundingClientRect()
所以,我们应该避免频繁的使用上述的属性,他们都会强制渲染刷新队列。
减少重绘与回流:
1、CSS
- 使用 transform 替代 top
- 使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流
- 避免使用table布局,可能很小的一个小改动会造成整个
table
的重新布局。 - 尽可能在DOM树的最末端改变class,回流是不可避免的,但可以减少其影响。尽可能在DOM树的最末端改变class,可以限制了回流的范围,使其影响尽可能少的节点。
- 避免设置多层内联样式,CSS 选择符从右往左匹配查找,避免节点层级过多。
- CSS3 硬件加速(GPU加速),使用css3硬件加速,可以让
transform
、opacity
、filters
这些动画不会引起回流重绘 。但是对于动画的其它属性,比如background-color
这些,还是会引起回流重绘的,不过它还是可以提升这些动画的性能。
避免频繁操作样式,最好一次性重写
style
属性,或者将样式列表定义为class
并一次性更改class
属性。-
避免频繁操作DOM,创建一个
documentFragment
,在它上面应用所有DOM操作
,最后再把它添加到文档中。 -
避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
<script type="text/javascript">
var pNode,fragment = document.createDocumentFragment();
for(var i=0; i<20; i++){
pNode = document.createElement('p');
pNode.innerHTML = i;
fragment.appendChild(pNode);
}
document.body.appendChild(fragment);
</script>复制代码
documentFragment
节点不属于文档树,因此当把创建的节点添加到该对象时,并不会导致页面的回流。
七、前端安全性XSS 攻击与CSRF攻击
Cross-Site Scripting (跨站脚本攻击)简称XSS,是一种代码注入攻击。攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行,进而实现攻击。
XSS 常见的注入方法:
- 在 HTML 中内嵌的文本中,恶意内容以 script 标签形成注入。
- 在内联的 JavaScript 中,拼接的数据突破了原本的限制(字符串,变量,方法名等)。
- 在标签属性中,恶意内容包含引号,从而突破属性值的限制,注入其他属性或者标签。
- 在标签的 href、src 等属性中,包含
javascript:
(伪协议)等可执行代码。 - 在 onload、onerror、onclick 等事件中,注入不受控制代码。
- 在 style 属性和标签中,包含类似
background-image:url("javascript:...");
的代码(新版本浏览器已经可以防范)。 - 在 style 属性和标签中,包含类似
expression(...)
的 CSS 表达式代码(新版本浏览器已经可以防范)。
常用防范方法:
对输入(和url参数)进行过滤,对输出进行编码,cookie 设置成 http-only
用户输入、url参数、post 请求参数、ajax
- httpOnly(服务端设置): 在 cookie 中设置 HttpOnly 属性后,js脚本将无法读取到 cookie 信息。
- 输入过滤: 一般是用于对于输入格式的检查,例如:邮箱,电话号码,用户名,密码……等,按照规定的格式输入。不仅仅是前端负责,后端也要做相同的过滤检查。因为攻击者完全可以绕过正常的输入流程,直接利用相关接口向服务器发送设置。
- 转义 HTML: 如果拼接 HTML 是必要的,就需要对于引号,尖括号,斜杠进行转义。
function escape(str) {
str = str.replace(/&/g, '&')
str = str.replace(/</g, '<')
str = str.replace(/>/g, '>')
str = str.replace(/"/g, '&quto;')
str = str.replace(/'/g, ''')
str = str.replace(/`/g, '`')
str = str.replace(/\//g, '/')
return str
}复制代码
- 白名单: 对于显示富文本来说,不能通过上面的办法来转义所有字符,因为这样会把需要的格式也过滤掉。这种情况通常采用白名单过滤的办法,当然也可以通过黑名单过滤,但是考虑到需要过滤的标签和标签属性实在太多,更加推荐使用白名单的方式。
1、验证码;强制用户必须与应用进行交互,才能完成最终请求。此种方式能很好的遏制 csrf,但是用户体验比较差。
2、Referer check;请求来源限制,此种方法成本最低,但是并不能保证 100% 有效,因为服务器并不是什么时候都能取到 Referer,而且低版本的浏览器存在伪造 Referer 的风险。
- 前端安全系列(一):如何防止XSS攻击? (真的非常详细!)
- 前端安全系列之二:如何防止CSRF攻击?(真的非常详细!+1)
八、Object.assign 模拟实现
if( typeof Object.assign2 !== 'function' ){
Object.defineProperty(Object, 'assign2', {
value: function(target){
if(!target){
throw new Error('cannot convert undefined or null to object')
}
var to = Object(target)
for(var index = 1; index < arguments.length;index++){
var nextSource = arguments[index]
if(nextSource){
for( var nextKey in nextSource ){
if(Object.prototype.hasOwnProperty.call(nextSource, nextKey)){
to[nextKey] = nextSource[nextKey]
}
}
}
}
return to;
},
writable: true,
configurable: true
})
}复制代码
// 测试用例
let a = {
name: "advanced",
age: 18
}
let b = {
name: "muyiy",
book: {
title: "You Don't Know JS",
price: "45"
}
}
let c = Object.assign2(a, b);
console.log(c);
// {
// name: "muyiy",
// age: 18,
// book: {title: "You Don't Know JS", price: "45"}
// }
console.log(a === c);
// true复制代码
九、vue 记录
一般来说,vue 组件分成三类:
1、 由vue-router 产生的每个页面,它本质上也是一个组件(.vue),主要承载当前页面的html 结构,会包含数据获取、数据整理、数据展示等业务操作,在实际项目开发中,我们写的大部分代码都是这类组件,在协同开发时,每人维护自己的路由页面,很少有交集。这类相对也是最好写的,能完成需求就行。
2、 不包含业务,是个独立、具体的功能基础组件,比如模态框、日期选择器。独立组件的开发难度要高于第一类组件,它侧重点是API的设计、兼容性、性能、以及复杂的功能,也会包含非常多的技巧,比如在不依赖vuex 的情况下,各组件间的通信。一个具有数据校验功能的输入框。
3、 业务组件,在业务中被多个页面复用,会包含页面元素,通用性要差一些,(依托于项目,可以使用项目中的技术栈,如vuex、axios等)。通用弹层
一个再复杂的组件,都是由三部分组成:prop、event、slot 。 Props 最好用对象的写法,这样可以针对每个属性设置类型、默认值或自定义校验属性值。
十、JavaScript模块化方案
在es6之前,js 并没有原生的模块。模块化方案的三个阶段过程:
1、全局变量+命名空间 (window.XX ,以及自执行函数等操作)
2、AMD&commonjs 各类规范带到前端
AMD模块类似:
define(function(require){
const bar = require('./bar')
return function(){}
})复制代码
commonJs 规范,它本不适合浏览器环境,但依赖现代打包工具进行转换之后就可以在浏览器中执行。commonjs 规范格式更加简洁,(nodejs 模块正在使用的就是commonjs 规范)
const bar = require('./bar')
module.exports = function(){
// ....
}复制代码
3、es6模块
模块化方案
import bar from './bar'
export default function(){
// ...
}复制代码
十一、webpack 与gulp 有什么本质区别
gulp 是工具链,构建工具,可以配合各种插件做js 压缩,css 压缩,less 压缩,替代手工实现自动化工作。 1、 构建工具 2、自动化、3、提高效率
webpack 是文件打包工具,把项目的各种js ,css 打包合并成一个或多个文件,主要用于模块化打包。1、打包工具 2、模块化识别 3、编译模块代码 4、代码拆分5、模块热更新。
十二、常用es6 以及es6 规范
到时候移到掘金
https://blog.csdn.net/u010427666/article/details/54944986
十三、seo 相关知识以及如何优化
文档&文章链接
十四、内部原型继承原理
需重新整理
https://blog.csdn.net/u010427666/article/details/53244698
十五、你需要知道的css
css 动画、flex 布局等
https://juejin.im/post/5c7646e2f265da2d8e70f681
十五、性能优化点
代码层面、网络层面、打包依赖等
https://segmentfault.com/a/1190000008273435
十六、浏览器缓存机制(强缓存&协商缓存)
是否过期根据header中的Cache-Control和Expires来判断,(缓存过期后Etag 与last-modify 是由服务器来判断)
nginx 开启强缓存
location ~ ^/static/.*(jpg|jpeg|png|gif|ico|js|css|ttf)$ {
root /home/service/www/web;
expires 12h;
add_header Cache-Control "public";
}
复制代码
nginx 开启gizp 压缩(压缩效率特高)
gzip on; #开启gzip压缩输出
gzip_min_length 1k; #最小压缩文件大小
gzip_buffers 4 16k; #压缩缓冲区
# gzip_http_version 1.0; #压缩版本(默认1.1,前端如果是squid2.5请使用1.0)
gzip_comp_level 3; #压缩等级
gzip_types text/plain application/javascript application/x-javascript text/css application/xml;
gzip_vary on;
gzip_disable "MSIE [1-7]\.";
复制代码
十七、数组乱序
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
arr.sort(function () {
return Math.random() - 0.5;
});
复制代码
洗牌算法:
function shuffle (arr) { var len = arr.length for (var i = 0;i < len - 1;i++) { var idx = Math.floor(Math.random() * (len - i)) var temp = arr[idx] arr[idx] = arr[len - i - 1] arr[len - i - 1] = temp } return arr}复制代码
十八、BFC(块级格式化上下文)
产生BFC:
- 根元素或包含根元素的元素
- 浮动元素 float = left | right 或 inherit(≠ none)
- 绝对定位元素 position = absolute 或 fixed
- display = inline-block | flex | inline-flex | table-cell 或 table-caption
- overflow = hidden | auto 或 scroll (≠ visible)
BFC 作用:
1、 清除浮动,即在容器中创建BFC
2、导致外边距折叠塌陷
我们必须记住的是外边距折叠(Margin collapsing)只会发生在属于同一BFC的块级元素之间。如果它们属于不同的 BFC,它们之间的外边距则不会折叠。所以通过创建一个不同的 BFC ,就可以避免外边距折叠。
https://segmentfault.com/a/1190000013647777
十九、单线程的JavaScript引擎是怎么配合浏览器内核处理这些定时器和响应浏览器事件的呢?
1、单线程,多进程
- 执行一个宏任务(栈中没有就从事件队列中获取)
- 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
- 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
- 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
- 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
https://www.cnblogs.com/joyco773/p/6038022.html
http://www.xuanfengge.com/js-realizes-precise-countdown.html
https://segmentfault.com/a/1190000014940904
二十、websocket 实时推送
相关链接: https://juejin.im/post/5c20e5766fb9a049b13e387b
二十一、http2.0的新特性有哪些?(选项是多路复用、头部压缩、设置优先级、服务端推送、二进制传输)
相关链接: https://juejin.im/post/5c8f30606fb9a070ef60996d
http2.0 /http1.0 302 与304 的区别 206
二十二、讲解一下https 的工作原理(握手过程)
HTTPS在传输数据之前需要客户端(浏览器)与服务端(网站)之间进行一次握手,在握手过程中将确立双方加密传输数据的密码信息。TLS/SSL协议不仅仅是一套加密传输的协议,更是一件经过艺术家精心设计的艺术品,TLS/SSL中使用了非对称加密,对称加密以及HASH算法。握手过程的简单描述如下:
- 浏览器将自己支持的一套加密规则发送给网站。
- 网站从中选出一组加密算法与HASH算法,并将自己的身份信息以证书的形式发回给浏览器。证书里面包含了网站地址,加密公钥,以及证书的颁发机构等信息。
- 获得网站证书之后浏览器要做以下工作:
- a) 验证证书的合法性(颁发证书的机构是否合法,证书中包含的网站地址是否与正在访问的地址一致等),如果证书受信任,则浏览器栏里面会显示一个小锁头,否则会给出证书不受信的提示。
- b)如果证书受信任,或者是用户接受了不受信的证书,浏览器会生成一串随机数的密码,并用证书中提供的公钥加密。
- 使用约定好的HASH计算握手消息,并使用生成的随机数对消息进行加密,最后将之前生成的所有信息发送给网站。
- 网站接收浏览器发来的数据之后要做以下的操作:
- a) 使用自己的私钥将信息解密取出密码,使用密码解密浏览器发来的握手消息,并验证HASH是否与浏览器发来的一致。
- b) 使用密码加密一段握手消息,发送给浏览器。
- 浏览器解密并计算握手消息的HASH,如果与服务端发来的HASH一致,此时握手过程结束,之后所有的通信数据将由之前浏览器生成的随机密码并利用对称加密算法进行加密。
[copy链接](github.com/airuikun/We…)
[热更新原理](zhuanlan.zhihu.com/p/30669007)
vue 3.0 新特性
目前,Vue 的反应系统是使用 Object.defineProperty
的 getter 和 setter。 Vue 3 将使用 ES2015 Proxy 作为其观察者机制。 这消除了以前存在的警告,使速度加倍,并节省了一半的内存开销。
为了继续支持 IE11,Vue 3 将发布一个支持旧观察者机制和新 Proxy 版本的构建。
二十三、前端代码日志收集
思路:
参照:
别人优质汇总:
https://juejin.im/post/5c64d15d6fb9a049d37f9c20
**3-5年内部岗位(平安、乐信、500万、vivo、oppo)推荐机会,欢迎发简历到: zgxie@126.com**