JavaScript高级
Posted 天落枫
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript高级相关的知识,希望对你有一定的参考价值。
文章目录
1 数据类型判断
- 基本数据类型(5种)
String
Number
Boolean
null(常用于引用变量赋初值和垃圾回收)
undefined - 对象(引用)类型(2种)
Object(普通对象{},数组对象[],正则对象/\\d/,Math,Date都是属于对象的)
Function函数
ES6新增:Symbol、Map
判断数据类型方法:
- typeof:返回数据类型的字符串表达,原理是判定内存中数据类型二进制,用于除null外的基本数据类型判断(null的二进制为000,而typeof判断以000开头的类型为对象类型,所以null这种类型被判定为对象),对于引用数据类型:除函数返回"function",其余都返回"object"(类型是小写)
// typeof检测情况:
// 基本数据类型:string boolean number undefined 正确检测
// null:object
// 函数:function
- instanceof:只能对象进行比较,判断的是当前对象是否是其子类实例或本身创建出来的对象,原理是:判断当前对象的原型链上是否包含该类型,包含则判定为true,否则为false。 只能比较引用对象(Function和Array的实例都是Object的子对象)
// instanceof检测情况:
// 引用类型可以正确检测
// 对于被手动修改了原型的的对象来说,就不一定准确了,如:函数A.prototype=Object.create(Array.prototype);A instanceof Array 为true
手动实现instanceof方法:
function instance_of(example,clazz){
let proto = Object.getPrototypeOf(example)
let cProto = clazz.prototype
while(true){
if(proto === null){
return false
}
if(proto===cProto){
return true
}
proto = Object.getPrototypeOf(proto)
}
}
// 注意:考虑ie兼容性问题,使用Object.getPrototypeOf方法而不是对象.__proto__去获取实例对象的原型
- constructor
可以用来判定基本数据类型,也可以判定是否是对象的细分类型,但由于该属性也可以随便改,所以也不算准确
var a = []
var b = 1
console.log(a.constructor === Array) // true
console.log(b.constructor === Number) // true
Number.prototype.constructor = "AA" // 内置类的原型都随便改了,所以也不是万能的
- Object.prototype.toString.call
标准的数据类型检测方法!由于Object的toString返回的是当前对象所属类型,所以可以调用Object.prototype.toString.call去改变this所属,以检测对象类型。
但是,该方法比较麻烦,还需要对返回的结果进行处理才能拿到类型,所以一般而言,结合typeof检测基本数据类型+toString检测类数据类型比较好(instanceof和constructor可以被改变,所以不适用)
// 思路:使用typeof检测基本数据类型,除null外,使用Object.prototype.toString检测引用数据类型,将返回的奇怪格式结果封装成映射表
// JQuery源码里toType全能方法
const class2Type = {};
const toString = class2Type.toString;
[
"String",
"Number",
"Boolean",
"Object",
"Array",
"Function",
"Symbol",
"Date",
"RegExp",
"Error"
].forEach((name) => (class2Type[`[object ${name}]`] = name.toLowerCase()));
function toType(obj) {
// null和undefined对于==来说是相等的
if (obj == null) {
return obj + "";
}
const t = typeof obj;
if (t === "object") return class2Type[toString.call(obj)];
return t;
}
console.log(toType(1));
console.log(toType("1"));
console.log(toType(true));
console.log(toType([]));
console.log(toType({}));
console.log(toType(null));
console.log(toType(undefined));
console.log(toType(new Date()));
console.log(toType(new RegExp()));
console.log(toType(() => {}));
// 结果如下
number
string
boolean
array
object
null
undefined
date
regexp
function
===
:对于值判定是否是相等值,对于对象判断引用地址是否相同,特殊的:null===null
trueundefined===undefined
truenull==undefined
true
2 三类循环性能分析
前提知识点:
console.time
和console.timeEnd
,用于启动计时器和终止计时器,计算程序运行时间,传入的值来确定是哪个计时器
console.time("abc");
...
console.timeEnd("abc");
for循环 vs while循环
let arr = new Array(9999999).fill(0);
console.time("abc");
for(let i=0;i<arr.lenght;i++){}
console.timeEnd("abc");
console.time("aaa");
let i=0;
while(i<arr.length){
i++;
}
console.timeEnd("aaa");
结论:for
循环速度快一倍。但是当使用var
声明i
变量时,两者性能相同。结合for
适用于确定次数,而while
适用于不定次数,使用时需要灵活选择。
测试环境在chrome浏览器中,非node环境,js循环在浏览器中调用的是浏览器的C++库
可以这么理解,while中的i声明在外部,为全局变量占用空间,所以操作时比较费时间,而for是块中,当使用var时,两者都是全局所以差不多时间消耗
数组中的循环
Array.prototype.forEach/map/reduce/filter/find/some/every
包含多种方法
forEach(回调函数,this指向):第二个参数为this的指向,用于调用回调函数,如果不传则默认为window
forEach
:性能比for和while都差,但是优点在于函数式编程方法
函数式:what:传入需要处理什么,但只关注结果,无法控制过程,不能中途打断退出,由于会做一些和循环无关的操作,性能也会消耗
命令式:how:对过程控制精细,无关代码少,性能比较高
for in
for in
:性能极差,速度比forEach慢很多,功能:迭代当前对象中所有可枚举的属性(私有属性大部分可枚举,公有属性部分可枚举(原型链))
for(let key in arr){}
特点:
- 遍历顺序以数字key优先
- 无法遍历Symbol属性key(解决办法:
if(typeof Symbol !== "undefined") Object.keys(obj).concat(Object.getOwnPropertySymbols(obj))
获取到obj中所有key) - 可以遍历到共有可枚举(解决办法:
if(!obj.hasOwnProperty(key)) break;
)
for of
迭代器:代表一种规范,实现了该规范就会有属性:Symbol.iterator
for of
原理是按照迭代器规范来遍历的
数组、Set、Map实现了,Object没有实现不可被迭代
原理:在需要迭代的对象上获取Symbol.iterator
属性,该属性对应一个函数,调用该函数返回一个对象,该对象包含next属性,是一个函数,每次迭代,调用next属性,返回一个对象:{done:false,value:xxx}
如果迭代完毕返回{done:true,value:undefined}
即可
// 迭代器规范
某个对象.[Symbol.iterator] = function(){
return {
next(){
if(终止条件){
return {done:true,value:undefined}
}
// ...
return {done:false,value:xxx}
}
}
}
类数组对象:{0:‘aa’,1:‘bb’,2:‘cc’,length:3}
如果想给 类数组 对象添加迭代器,可以不用自己写,直接使用Array的迭代器:
var a={0:'aa',1:'bb',2:'cc',length:3}
a[Symbol.iterator]=Array.prototype[Symbol.iterator]
for(let v of a){
console.log(v);
}
3 this分析
this代表执行主体,该指向和在哪创建或执行无关,也不是执行上下文
情况分五种:
- 函数前面有点,this就是前面的对象;函数前无点,this就是window(严格模式下是undefined)
- 事件绑定时,回调函数中的this是,被绑定元素本身
- 构造函数中的this指new出来的实例
- 箭头函数中的this绑定的是上下文中的this
- 可以基于Function.prototype.call/apply/bind去改变this指向
// 自己动手写一个call方法
Function.prototype.call = function (obj, ...params) {
let self = this,
key = Symbol("KEY"),
result;
obj == null ? (self = window) : null;
/^(object|function)$/.test(typeof obj) ? null : (obj = Object(obj));
obj[key] = self;
result = obj[key](...params);
delete obj[key];
return result;
};
function abc(p) {
console.log(this, p);
}
let obj = { name: "pp", age: 11 };
abc.call(obj, 1);
// 自己动手写bind方法
Function.prototype.bind = function (obj, ...args) {
let self = this;
return function (...args2) {
return self.apply(obj, args2.concat(args));
};
};
function a(...p) {
console.log(this, p);
}
let obj = { name: "pp", age: 11 };
a.bind(obj, 9)("abc");
鸭子类型(狸猫换太子)
Array.from() 可以通过以下方式来创建数组对象:
伪数组对象(拥有一个 length 属性和若干索引属性的任意对象)
可迭代对象(可以获取对象中的元素,如 Map和 Set 等)
鸭子类型指的是:该对象大部分结构与另一个类型相同,则可以借用另一个类型已有的方法执行,如arguments
与数组类型很相似,所以可以借用数组的很多方法,如:
function func1(){
// use Array.prototype.slice
let args = Array.prototype.slice.call(arguments)
// use Array.prototype.forEach
[].forEach.call(arguments,x=>console.log(x))
}
func1(10,20,30)
4 HTTP网络层的前端性能优化
前端优化点:
- HTTP网络层优化
- 代码编译层优化(webpack)
- 代码运行层优化(html css js vue react)
- 安全优化(xss csrf)
- 数据埋点及性能监控
CRP:关键路径渲染
七层网络模型:
- 物理层:比特、电气特性
- 数据链路层:帧、MAC地址
- 网络层:报文
- 传输层:流量控制、错误检测
- 会话层
- 表示层
- 应用层
TCP/IP四层网络模型:
a. 网络接口层:ARP/RARP
b. 网络层:ICMP/IP
c. 传输层:TCP/UDP
d. 应用层:HTTP/DNS/FTP/SMTP
url从输入到展示结果经历
1. url解析
在前后端请求时需要对一些特殊字符进行编码,如:
%
等,前端有两种编码方式:
对整个url编码:encodeURI/decodeURI
:这种方式只对中文和空格进行编码
对参数编码:encodeURIComponent/decodeURIComponent
:对中文、空格、%、:、/ 都会编码
URI统一资源标识符
URL统一资源定位符
URN统一资源名称
这是一个URL(绝对地址):https://blog.csdn.net/qq_40321119/article/details/102856789
这是一个URN(相对地址):qq_40321119/article/details/102856789
这是一个URI:可以是URL,也可以是URN
2. 缓存
属于产品优化的重点,服务端返回缓存头字段相关配置,浏览器自动实现缓存机制
缓存分为:强缓存/协商缓存。
缓存位置:内存缓存、硬盘缓存。
检测步骤:先检测是否有强缓存,没有或失效就检测是否有协商缓存,没有就去获取最新数据
html页面一般不做缓存,js、css及其他资源会
- 强缓存:
Cache-Control:max-age=
表示多长时间后再次请求新数据HTTP1.1,Expires
指定过期时间HTTP1.0,优先级低于前者
打开新网页:(此时直接检查硬盘缓存,没有则发送请求新数据)
F5刷新:先查内存再查硬盘,都没有就请求数据
Ctr+F5:不使用缓存,请求头会加字段:Cache-control:no-cache
,直接请求最新数据
问题:如果页面更新,之前请求设置了强缓存还能获取最新数据吗?
答案:可以,页面更新后webpack打包的资源名称hash值被改变,浏览器发现新资源未做缓存,所以会重新请求。还有一种方法,如果不使用webpack,则将引入的资源加后缀也可以,后缀名不同也不走缓存。<script src='xxx.js?12345'></script>
- 协商缓存
Last-Modified (HTTP1.0) / ETag (HTTP1.1)
协商缓存就是强缓存失效后,浏览器携带缓存标志向服务器请求,服务器根据缓存标志决定是否使用缓存的过程
处理过程:
携带缓存标志:If-Modified-Since=Last-Modified 值
If-None-Match=ETag 值
服务器没更新:304状态,浏览器直接读缓存
服务器更新了:200状态及最新资源以及Last-Modified / ETag,将结果和缓存标志写入本地
Last-Modified 资源文件最后更新时间,秒
ETag 记录的是一个标识,更新资源文件会生成新的ETag
- LocalStorage数据缓存
一般使用该对象存储数据缓存,缓存格式建议:time:xxx, data:[]
请求时先检测本地缓存对象中是否存在该数据且未过期,否则就直接请求新的
3. DNS解析
域名解析,也是带缓存的
先是走本地hosts文件
没有则走本地DNS解析缓存
没有则走本地DNS服务
没有则走远程域名解析服务器
递归查询、迭代查询
递归
DNS优化:1 减少DNS请求次数(一个页面资源都放到相同的服务器上) 2 预获取prefech
第一种不推荐:实际往往会将不同资源放到不同服务器上,有利于提高资源服务器的性能利用,同时HTTP同源一次大概4-7个请求,资源分布后有利于实现高并发
注意:必须要使用Link标签!
4. 建立连接通道TCP三次握手
三次握手:
client:SYN=1 seq=x
server:SYN=1 ACK=1 seq=y ack=x+1
client:ACK=1 seq=x+1 ack=y+1
四次挥手:
client:FIN=1 seq=u
server:ACK=1 seq=v ack=u+1
服务器继续与客户端传递数据
server:FIN=1 ACK=1 seq=w ack=u+1
client:ACK=1 seq=u+1 ack=w+1
HTTP1.0、1.1、2.0区别
HTTP/1.0 每次请求建立一个TCP连接,用完即关闭,长连接默认关闭
HTTP/1.1 默认开启【长连接keep-alive】:若干请求会串行化处理,前一个请求超时会导致后面所有请求阻塞,新增断点续传(返回码206),新增支持host头域,如果没有会报400(通过host处理一台服务器上有多个共享IP地址的虚拟服务器,1.0默认一个IP对应一个服务器)
HTTP/2.0 新增【多路复用】:多个请求在同一个连接上并行执行,某个请求耗时严重不会影响其他请求
HTTP2.0相比于1.x新增特性
新的二进制格式binary format:1.x基于文本解析,需要考虑的场景很多,二进制不同,健壮性高
header压缩:1.x的header带有大量信息,每次都重复发送,2.0中使用encoder减少header大小,通讯双方各自缓存一份header fields表,避免重复传输,减少大小
服务端推送:可以在HTTP响应头中设置Link命令来让浏览器接收服务器的推送,无需多次请求(页面有个css请求,客户端收到css时,服务端会将js等文件也推送过来,客户端请求时从缓存中读取)
Link: </styless.css>; rel=preload; as=style, <example.png>; rel=preload; as=image
5 对象深拷贝
浅拷贝:拷贝后生成的对象不同,对象中的值指向原来的数据(只拷贝了一级)
深拷贝:拷贝后生成的对象和对象中的值都是新的
浅拷贝
数组浅拷贝方法:
...
展开运算符:let a = [...b]
concat
运算:let a = b.concat([])
slice
运算:let a = b.slice()
对象浅拷贝:
...
展开运算符:let a ={...b}
Object.assign
运算:let a = Object.assign({},b)
- 写循环赋值,
Object.keys(obj)
无法获取Symbol
类型的属性, 因为该属性不可枚举,所以需要再加上Object.getOwnPropertySymbols(obj)
获取所有obj上的属性
获取对象的Symbol属性名称:Object.getOwnPropertySymbols(obj)
深拷贝
实现原理:每次浅克隆一级,如果有下一级继续浅克隆下一级,实现所有深克隆
但是深拷贝中有个问题:循环引用:let a={};a.a=a
,此时循环拷贝会出现栈溢出问题,解决方法是使用缓存,即判断当前需要拷贝的对象是否之前拷贝过,如果拷贝过则直接从缓存中取,否则就创建一个新的
/**
* 深克隆(深拷贝)+ 解决深拷贝函数中循环引用时导致的栈溢出的问题
* @param {object} origin
* @param {*} hashMap WeakMap数据,用于缓存克隆过的对象
* @returns origin / 克隆的origin
*/
function deepCloneCycle(origin, hashMap = new WeakMap()) {
let result = null;
if (hashMap.has(origin)) return hashMap.get(origin); // 查缓存字典中是否已有需要克隆的对象,有的话直接返回同一个对象(同一个引用,不用递归无限创建进而导致栈溢出了)
if (typeof origin === 'object' && origin !== null) { // 【类型判断】引用类型,进行递归拷贝(用typeof判断类型要剔除null的情况)
if (Object.prototype.toString.call(origin) === '[object Array]') {
// 【类型判断】数组类型,创建一个新数组
result = [];
hashMap.set(origin, result); // 哈希表缓存新值
// 【遍历赋值】
origin.forEach(el => {
result.push(deepCloneCycle(el, hashMap)); // 【递归】
});
} else {
// 【类型判断】对象类型,创建一个新对象
result = {};
hashMap.set(origin, result); // 哈希表缓存新值
for (const key in origin) {
// 【遍历赋值】对象这里特殊处理了,不遍历拷贝原型链上的属性
if (origin.hasOwnProperty(key)) {
result[key] = deepCloneCycle(origin[key], hashMap); // 【递归】
}
}
}
} else { // 【类型判断】原始类型直接返回
return origin;
}
return result;
}
当然对于对象上存在Symbol属性的还需要在遍历key的时候把Object.getOwnPropertySymbols(obj)
也算上去
6 对象merge合并
两个对象合并在业务中非常有用,一般替合并规则为:一个原对象A,一个新对象B,将B合并到A上,对于其中的某个属性:
1. A和B该属性为基本数据类型,B直接覆盖A的该属性
2. A该属性为对象,而B为基本数据类型,报错!
3. A是基本数据类型,B为对象,直接覆盖
4. A和B该属性都是对象,递归合并
5. 特殊的,对于属性是数组的,合并中看作基本数据类型!!
示例程序:
// 定义一个函数用于判断如惨是否为对象
function isObj(obj){
if(typeof obj === "object" && obj instanceof Object && !(obj instanceof Array)) return true;
return false;
}
// merge函数,将后者合并到前者
function mergeObj(objo,objn){
let isObjo = isObj(objo);
let isObjn = isObj(objn);
if(isObjo && !isObjn) Throw new TypeError("merge function param must be object!");
if(!isObjo && !isObjn) return objn
if(!isObjo && isObjn) return objn
Object.keys(objn).forEach(key=>{
objo[key] = mergeObj(objo[key],objn[key])
})
}
7 函数柯里化(bind&currying)
简单理解即:调用一个函数返回一个新函数
思想:利用闭包,将目标函数需要处理的一些参数预先处理,并返回该函数
借助工具:bind/call/apply
自己实现一个bind:
function bind(func,context,...args){
return function proxy(){
return func.call(context,...args)
}
}
对于IE8及以下的浏览器,由于不支持bind,需要自己定一个放在function原型上
示例函数:
~ function(proto){
function bind(context,...args){
let that = this;
return function(){
return that.call(context,...args)
}
}
proto.bind = bind;
}(Function.prototype)
8 AOP切面编程
POP:面向过程
OOP:面相对象
AOP:面相切面
示例代码:
// 需要实现的功能
function f(a){console.<以上是关于JavaScript高级的主要内容,如果未能解决你的问题,请参考以下文章
VSCode自定义代码片段12——JavaScript的Promise对象
Vue3官网-高级指南(十七)响应式计算`computed`和侦听`watchEffect`(onTrackonTriggeronInvalidate副作用的刷新时机`watch` pre)(代码片段