一、原型链
1. _proto_ 属性
_proto_属性,是,指向它们的原型对象。
作用:
- 当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的_proto_属性所指向的那个对象(可以理解为父对象)里找,如果还没找到,会继续往上找,直到原型链顶端null,如果还没找到,则返回undefined,这就是
2. prototype 属性
- prototype是
,它是从一个函数(构造函数)指向一个对象(原型对象)。
- 它的含义是函数的原型对象,也就是这个函数所创建的实例的原型对象,由此可知:f1._proto_ === Foo.prototype
- 作用 : 所有实例共享属性和方法
//判断属性是否存在于对象中
obj.hasOwnProperty(prop)
//判断整条原型链上是否有某个属性
prop in obj
二、深拷贝与浅拷贝
1、赋值
当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实改变的是同一个存储空间的内容,因此,两个对象是联动的。
2、浅拷贝
重新在堆中创建内存,拷贝前后对象的基本数据类型互不影响,但拷贝前后对象的引用类型共享同一块内存,会相互影响。
浅拷贝的实现方式:
1.Object.assign()
2.es6展开运算符 ...
// 这是个浅拷贝的方法
function shallowClone(source) {
var target = {};
for(var i in source) {
if (source.hasOwnProperty(i)) {
target[i] = source[i];
}
}
return target;
}
3、实现深拷贝的方式
从堆内存中开辟一个新的区域存放新对象,对对象中的子对象进行递归拷贝,拷贝前后的两个对象互不影响。
JSON.parse(JSON.stringify(oldObj));
这种方法虽然可以实现数组或对象深拷贝,但不能处理函数和正则,因为这两者基于JSON.stringify和JSON.parse处理后,得到的正则就不再是正则(变为空对象),得到的函数就不再是函数(变为null)了。
参考
// 构造函数
function person(pname) {
this.name = pname;
}
const Messi = new person('Messi');
// 函数
function say() {
console.log('hi');
};
const oldObj = {
a: say,
b: new Array(1),
c: new RegExp('ab+c', 'i'),
d: Messi
};
const newObj = JSON.parse(JSON.stringify(oldObj));
// 无法复制函数
console.log(newObj.a, oldObj.a); // undefined [Function: say]
// 稀疏数组复制错误
console.log(newObj.b[0], oldObj.b[0]); // null undefined
// 无法复制正则对象
console.log(newObj.c, oldObj.c); // {} /ab+c/i
// 构造函数指向错误
console.log(newObj.d.constructor, oldObj.d.constructor); // [Function: Object] [Function: person]
一个深拷贝方法
function deepClone(obj) {
if (obj === null) return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (typeof obj !== "object") return obj;
let cloneObj = new obj.constructor();
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 递归拷贝
cloneObj[key] = deepClone(obj[key]);
}
}
return cloneObj;
}
三、闭包实际场景的应用
1. 防抖、节流
2. 用立即执行函数模拟块级作用域
function outputNumbers(count) {
(function () {
for (var i = 0; i < count; i++) {
alert(i);
}
})();
alert(i); //导致一个错误!
}
3. 创建私有变量
var aaa = (function () {
var a = 1;
function bbb() {
a++;
console.log(a);
}
function ccc() {
a++;
console.log(a);
}
return {
b: bbb, //json结构
c: ccc,
};
})();
console.log(aaa.a); //undefined
aaa.b(); //2
aaa.c(); //3
四、["1","2","3"].map(parseInt)//1 NaN NaN
["10","10","10","10","10","10"].map(parseInt)//10 NaN 2 3 4
1.map返回一个新的数组,
2.map默认传的2个参数,item和index
3.parseInt可以接收2个参数parseInt(num,进制),第二个参数的取值范围为2~36,如果是0则看num的开头,如果以0X开头,则16进制,如果以0开头,则8进制,否则按10进制,如果传1进制,结果为NaN
五、严格模式 use strict
特点:
- 变量必须先声明,再赋值,否则会报错
- 全局调用函数时,this的值为undefined
为什么使用严格模式? - 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;
- 消除代码运行的一些不安全之处,保证代码运行的安全;
- 提高编译器效率,增加运行速度;
- 为未来新版本的Javascript做好铺垫
六、浏览器兼容问题思路
- 先考虑要不要做,看用户
- 要兼容到什么程度
- 选择一些兼容性插件,jquery、css reset 、bootstrap、js条件注释、css hack、js hack
- 渐进增强:保证基础的功能,再不断改进
- 优雅降级:一开始构建完整功能,再针对某些低版本或不支持的版本进行兼容
七、前端性能优化
- 减少http请求的数量
- 减少DOM操作,减少不必要的重绘和重排,用定位,脱离文档流
- 优化CSS选择器,不写一大长串
- 压缩css和js,减小文件体积
- 开启Gzip压缩
- css放到顶部,js放到底部
- CDN加速,使用文件缓存
- 控制cookie的大小
八、XSS跨站脚本攻击
特点:
恶意植入html或js代码来窃取cookie信息、会话劫持、钓鱼欺骗等攻击
原因:
浏览器本身不安全,不会判断脚本是否恶意
解决方案
- 重要的cookie标记为httponly,这样我们就不能通过js操作cookie
- 表单数据对值的类型进行限制,age只能为number,name规定为数字字母组合等
- 过滤或移除特殊的html标签:iframe、script
九、CSRF跨站请求伪造
XSS:利用网站的受信任用户的信息
CSRF:伪装成受信任用户的请求来窃取信息
特点:
- 利用用户的浏览器发送请求到目标站点
- 可以利用一个img标签触发get请求来实现CSRF攻击
解决方案 - 提交携带token,对token进行验证
- 不在url中携带用户隐私信息
- 尽量避免使用全站通用的cookie,严格设置cookie的域
十、跨域
- jsonp利用script标签允许跨域的原理来解决
- nginx反向代理
- 服务器配置,
access-control-allow-origin:*
access-control-allow-request
access-control-allow-method
access-control-allow-head
4.iframe 通过postmessage
十一、事件绑定的方式
addEventlistener第三个参数设置事件是在捕获阶段还是冒泡阶段执行
默认是冒泡阶段,值为false
捕获阶段为true
- 事件冒泡
从具体元素向父级元素冒泡(从里到外依次触发)
e.stopPropagation() //阻止冒泡
window.event.cancelBubble = true //阻止冒泡 (谷歌,IE8兼容,火狐不支持) - 事件捕获
从最外层元素逐级向某一个具体元素捕获(从外到里依次触发) -
事件委托
“事件代理”即是把原本需要绑定在子元素的响应事件(click、keydown......)委托给父元素,让父元素担当事件监听的职务
利用事件冒泡的原理,点击子元素会逐层冒泡到父元素,
事件传播的3个阶段
如图所示,事件传播分成3个阶段:
- 捕获阶段:从window对象传导到目标节点(上层传到底层)称为“捕获阶段”(capture phase),捕获阶段不会响应任何事件;
- 目标阶段:在目标节点上触发,称为“目标阶段”
- 冒泡阶段:从目标节点传导回window对象(从底层传回上层),称为“冒泡阶段”(bubbling phase)。事件代理即是利用事件冒泡的机制把里层所需要响应的事件绑定到外层;
事件委托的优点:
- 可以大量节省内存占用,减少事件注册,比如在ul上代理所有li的click事件就非常棒
- 新增子对象时无需再次对其绑定(动态绑定事件)
十二、ajax和fetch的区别
ajax:
- new XMLhttprequest
- 可以监测进度
- 可以取消请求
- 兼容性更好
fetch - es6新增的,基于promise
- 不可以监测进度
- 不能取消请求
- 兼容性稍差
十三、cookie、sessionStorage、localstorage区别
- 存储空间大小:
cookie:4K
localStorage和sessionStorage:5M
localStorage和sessionStorage的区别: -
何时清空
localStorage:localStorage的生命周期是永久的,意思就是如果不主动清除,存储的数据将一直被保存
sessionStorage:生命周期为当前窗口,一旦窗口关闭,那么存储的数据将被清空
image.png - 访问权限
如果一个页面包含多个iframe且他们属于同源页面,那么他们之间是可以共享sessionStorage的
只要不同源就不能共享localStorage的数据
跨域共享localStorage的解决方案:postMessage+iframe
postMessage(data,origin)方法允许来自不同源的脚本采用异步方式进行通信,可以实现跨文本档、多窗口、跨域消息传递。
十四、如何改变this指向
- call,参数是参数列表,返回执行结果,this指向第一个参数
- apply,参数是数组,返回执行结果,this指向第一个参数
- bind,返回的是一个函数,而不是执行结果,this指向第一个参数
- new,this指向新创建的对象
- 箭头函数
十五、如何保证渲染几十万条数据不卡帧
- createDocumentFragment()
特点:
- 此方法创建虚拟节点对象
- documentFragment节点不属于文档树,其父节点是null,当插入文档树是,插入的是它的子孙节点,不包括它自身,也就是()里的节点
- 当需要添加多个dom节点时,可以先将dom节点添加到DocumentFragment中,再统一将DocumentFragment添加到页面,减少dom渲染的次数
- requestAnimationFrame()间隔执行
特点:
- 与setInterval、setTimeout相比,requestAnimationFrame会将每一帧的所有dom操作集中起来在一次重绘或回流中完成,并且重绘和回流的频率和浏览器刷新的频率一致,(60帧/s)
- 在隐藏或不可见的元素中,requestAnimationFrame不会进行回流和重绘,减少CPU和GPU的消耗
如图,每20个dom节点通过DocumentFragment放到一起,然后通过requestAnimationFrame跟随浏览器刷新的频率渲染到页面
十六、进程和线程
关系
- 进程是资源分配的最小单位,线程是调度的最小单位
- 进程相当于工厂,线程相当于工人,一个进程中可以有多个线程
- 进程中的内存资源是共享的,每个线程都可以访问这些资源
多进程与多线程
- 多进程:听歌的同时写代码
- 多线程:比如webpack多线程压缩js,parallalUglifyJs
以Chrome浏览器为例,打开一个tab页相当于创建了一个进程,一个进程中包含多个线程,比如js引擎线程、渲染线程、http请求线程,发起一个请求相当于创建了一个线程,请求返回之后会销毁线程
浏览器内核的作用
本质就相当于渲染引擎,通过获取页面内容,获取CSS,然后进行计算和组合最终生成可视化的页面。
浏览器内核是多线程:
- GUI渲染线程
主要负责解析HTML、CSS,构建DOM树,布局
回流和重绘会执行GUI渲染线程
GUI渲染线程和js引擎线程互斥,一个执行另一个会被挂起 - js引擎线程
负责执行js代码 - 定时触发器线程
遇到定时器,会将定时器交给定时器触发线程处理,当计数完毕,事件触发器线程会将回调函数加入到执行队列尾部等待js引擎线程执行 - 事件触发器线程
将定时器或者ajax异步请求的回调加入到执行队列尾部等待js引擎线程执行 - http请求线程
主要负责执行异步请求,如promise、ajax、axios等,当监听到状态码变化,事件触发器线程会将回调函数加入到执行队列尾部等待js引擎线程执行
十七、token
传统token,基于sessionId
简述:
- 后端对一个随机数(或者用户名密码)进行加密,生成一个字符串(token),返回给前端,
- 前端每次在请求header中带上token,
- 后端再解密验证用户身份。
服务端如何通过token获取用户信息?
服务端在生成token的时候加入了少量用户信息,比如:用户id。最后服务端收到token并解密之后,就可以和用户关联起来
如何识别伪造的token?
防伪造一般通过签名,使用服务端的私钥加密随机数生成签名,然后对签名和随机数进行加密生成token即可
如何应对冒充的情况?
在生成token时加入用户浏览器的相关信息:浏览器名称、版本号、安卓、iphone等,解密token后进行对比
token验证过程
JWT(Json Web Token)
JWT标准的token,由三部分组成:
- header(头部),包含使用的算法
- payload(数据),包含jwt签发者、接收jwt的一方、过期日期、用户相关信息等
- signature(签名),对加密之后的header.payload再次进行加密生成一段密文,主要用于防止冒充的情况
中间用点分隔
为什么使用JWT?相比sessionId有什么优点?
- 支持跨域访问
cookie的数据在发送请求的时候会自动携带在请求中发送,所以cookie不支持跨域访问;token是登录的时候服务端返回给浏览器,浏览器再存在本地localstorage中,再通过Authorization请求头发送给服务端,可以支持单点登录 - 无状态
sessionId需要在服务端存储session信息,token则无需存储 - 更适用于移动端
非浏览器平台是不支持cookie的,这时我们就得使用token - 无需考虑CSRF
不使用cookie就不用考虑CSRF
十八、数据埋点
就是前端收集一些用户的行为数据,跟踪应用的使用情况,为后续优化应用提供数据支持。比如:访问数、访问量、停留时长、跳出时长、
如何埋点
我们只需要向服务器发送数据就好,不需要服务器给我们响应
- 避免跨域(img天然支持跨域)
- 利用空白gif或者1px*1px的img是网站监测常用的手段
- 图片不会阻塞页面加载,不影响用户体验,只要new image对象,不需要append到页面中,只需要监测onload和onerror事件就好
十九、判断数据类型
1、 判断undefined
- 通过typeof(undefined) == ‘undefined’ //true
- 不能通过
==
判断,因为
null == undefined //true
undefined == undefined //true
2、 由上可知 判断undefined和null
通过==
即可
3、 判断null
let test = null
1、不能通过 == 判断
理由同上
//这时test可能是undefined,也可能是null
test == null //true
2、不能通过 !test == true来判断
//此时 test可能是undefined,也可能是数字0,也可能是null
3、 正确方法
test == null && typeof(test) == 'object'
4、判断NaN
使用es6的方法
Object.is(NaN,NaN) //true
NaN == NaN //false
NaN === NaN //false