js数据类型检测
原因:对象存储在计算机中,都是以000开始的二进制存储,null也是。
- 检测普通对象、数组对象、正则对象、日期对象,返回的结果都是”Object”
- instanceof(检测当前实例属不属于这个类)解决typeof不能检测对象的具体类型
机制
只要当前类出现在实例的原型链上,结果都是true
缺点
```javascript let arr = [] congsole.log(arr instanceof Array) //true congsole.log(arr instanceof RegExp)//false console.log(arr instanceof Object) //true判断不出对象是否为普通对象
function Fn() { this.x = 10; } Fn.prototypoe = Object.create(Arrary.prototype) let f = new Fn; console.log(f instanceof Arrary) //true
console.log(1 instanceof Number) //false
<a name="3zJ2M"></a>
#### 缺点
1. 基本数据类型检测不出来
1. 由于我们可以肆意修改原型的指向,所以检测结果不准确。
<a name="ihmSC"></a>
#### 手斯instanceof
![image.png](https://cdn.nlark.com/yuque/0/2021/png/1699388/1616382455304-b98aa90d-96e7-4b7d-9c33-4b6778a194b1.png#height=382&id=ohWBn&margin=%5Bobject%20Object%5D&name=image.png&originHeight=764&originWidth=1470&originalType=binary&ratio=1&size=462774&status=done&style=none&width=735)
- constructor
用起来比instanceof好,可以检测基本数据类型
```javascript
let a = []
console.log(a.constructor === Arrary) //true
console.log(a.constructor === RegExp) //false
console.log(a.constructor === Object) //false
let b = 1
console.log(b.constructor === Number) //true
b.prototype.constructor = 'aaaaaa'
console.log(b.constructor === Number) //false
缺点
construct可以随便改,也不准
- Object.prototype.toString.call([value])
标准检测方法,Object.prototype.toString不是返回一个字符串,返回的当前实例所属类的信息。
let obj= {name: '王某某'}
obj.toString()//'[Object Object]'
检测数据类型的方法
js中三类循环对比及性能分析
for循环及forEach底层原理
FOR循环是自己控制循环过程
1.基于var声明的时候,for和while性能差不多(不确定循环次数的时候用while)
2.基于let循环的时候,for循环的性能更好,「没有创造全局不释放变量」let有块级上下文
3.重写foreach(比上画性能差)手撕数组迭代方法
Array.prototype.forEach = function forEach(callback, context){
let self = this,
i = 0,
len = self.length;
context = context == null ? window :context
for (; i < len; i++) {
typeof callback === 'function' ? callback.call(context, self[i], i) : null
}
}
for in循环的BUG解决方案
1.迭代所有可枚举属性,「私有(大部分是可枚举的,length是不可枚举的)&公有(出现在类的原型链上的,也有部分是可枚举的)」,按照原型链一级级查找很耗性能
2.问题很多,不能迭代symbol属性、迭代属性会以数字属性优先、共有可枚举的{一般是自定义属性}属性也会进行迭代
(1)遍历的顺序以数字优先
(2)无法遍历symbol属性
(3)可以遍历共有中可枚举的
Object.prototype.fn = function fn(){}
let obj = {
name: 'hahahah',
age: '13',
[Symbol('AA')]: 100,
0: 200,
1: 300
}
for(let key in obj) {
if(!obj.hasOwnProperty(key)) break; //解决问题3
console.log(key);
}
//0,1,name,age,fn
let keys = Object.keys(obj)
if(typeof Symbol !== 'undefined') keys = keys.concat(Object.getOwnPropertySymbols(obj))
keys.forEach(key => {
console.log(`属性名:${key},属性值:${obj[key]}`); //Symbol不能以这种方式转换为字符串
}) //解决问题2,不兼容IE678
for of循环底层机制
1.迭代器iterator规范(部分数据结构实现了迭代器规范,有Symbol.iterator)「具备next方法,每次执行返回一个对象,具备value/done属性」数组,类数组,Set,Map「对象没有实现」
2.让对象具备可迭代性并且使用for of循环
let arr = [10, 20, 30]
arr[Symbol.iterator] = function () {
let self = this,
index = 0
return {
next () {
//必须具备next方法,执行一次next方法,拿到结构中的某一项值
//done:false value:每一次获取的值
if (index > self.length - 1) {
return {
done: true,
value: undefined
}
}
return {
done: false,
value: self[index++]
}
}
}
}
// 1.let itor = arr[Symbol.iterator]() 2.itor.next()...
let obj = {
0: 100,
1: 200,
2: 300,
3: 400,
length: 4
}
obj[Symbol.iterator] = Array.prototype[Symbol.iterator]
console.log(obj[Symbol.iterator]);
for (let val of obj) {
console.log(val);
}
//实现类数组的遍历
谈谈你对this的了解及应用场景
- this的五种情况分析(this执行主体,谁把它执行的,和在哪创建,在哪执行没有必然关系)
函数执行,看方法前面是否有点,没有点this是window「严格模式下this是underfined」,有点,点前面是谁this是谁
const fn = function fn() {
console.log(this)
}
let obj = {
name:'Obj',
fn: fn
}
fn() //window
obj.fn() //obj
给当前元素的某个事件行为绑定方法,当事件行为触发,方法的this是当前元素的本事「排除attachEvent」
document.body.addEventListener('click', function(){
console.log(this);
})//body
构造函数体中的this是当前类的实例
function Factory () {
this.name = "hahahah"
this.age = "123"
console.log(this)
}
let f = new Factory //Factory
箭头函数中没有执行主体,所用的this都是其所处上下文的this
let demo = {
fn(){
console.log(this) //demo
setTimeout(function(){
console.log(this)
},1000) //window
setTimeout(()=>{
console.log(this)
},1000)//demo
}
}
demo.fn()
可以基于Function.prototype伤的call/apply/bind去改变this的指向
function func (x, y) {
console.log(this, x, y)
}
let obj = {
name:'OBJ'
}
//func函数基于__proto__找到Function.prototype.call把call方法执行
//call(context->obj,...params->[10,20])
//在call方法内部「call执行的时候」把func中this改为传入的第一个this改为obj
//并且把params接收的值当作实参传递给func函数,并且让func函数立即执行
func.call(obj,10,20)
func.apply(obj,[10,20])
document.body.addEventListener('click',func.bind(obj,10,20))
//func函数基于__proto__找到Function.prototype.bind把bind方法执行
//和call和apply区别,并没有把func立即执行
//把传递来的值存储起来「闭包存储」
//执行bind会返回一个新的函数 例如:proxy,把proxy绑定给元素的事件,当事件触发执行的是返回的proxy,在proxy内部
//再去把func执行,把this和值都改变为之前存储的那些内容
手撕call/bind源码 ```javascript //原理:就是利用“点”定this机制,context.xxx = self “obj.xxx=func”=>obj.xxx() Function.prototype.call = function call (context,…params) { //this=>func context=>obj …params=>[10, 20] let self = this,
key = Symbol('KEY'),
result context == null ? context = window : null !/^(Object|function)$/i.test(typeof context) ? context = Object(context) : null context[key] = self result = contextkey delete context[key] }
Function.prototype.bind = function bind (context,…params) { //this=>func context=>obj …params=>[10, 20] let self = this return function proxy(…args){ //把func执行并且改变this即可,args是执行proxy的时候可能传递的值 self.apply(context, params.concat(args)) } }
7. 掌握this好玩的应用:鸭子类型
```javascript
Arrary.protopype.slice = function slice(){
let self = this,
result = []
for(let i = 0; i < self.length,i++){
result.push(self[i])
}
return result
}
//克隆返回一个新数组,浅拷贝arr.slice()
//像鸭子,我们就说他是鸭子,类数组像数组「结构,一些操作」不允许用数组的方法我们让其用数组的方法「不能直接用」
function func () {
console.log(arguments)
//把arguments变为数组,这样就可以用数组的方法:Arrary.from/[...arguments]/...
let result = []
for(let i = 0; i < arguments.length,i++){
result.push(arguments[i])
}
return Arrary.prototype.slice.call(arguments)
[].forEach.call(arguments,item=>{
console(item)
})
}
//如果让slice执行:[].slice(),Arrary.prototype.slice()并且让this指向arguments,就相当于把arguments变为数组
HTTP网络层优化
- URL解析
- 网络传输:TCP/IP(+TCP传输通道,IP主机地址,HTTP传输协议,HTTPS:SSL/TLS)FTP传输大文件
- 端口号:每一个端口号代表一个项目 http:http:80 https:443 ftp:21
- 编码:encodeURI/decodeURI
对整个URL编码:处理空格/中文
编码:encodeURIComponent/decodeURIComponnet
对主要传递参数进行编码
escape/unescape:unescape不支持所有浏览器,不应用客户端与服务器
- URI/URL/URI/URL/URN:区别
- 缓存检测:产品优化性能重点
- 先检测检测是否存在强缓存,有未失效,走强缓存,没有强缓存或失效走协商缓存,有或没有
- 缓存位置:内存缓存(加载js时候,ECstack,栈内存,堆内存,浏览器关闭内存释放),硬盘缓存
- html页面一般不做强缓存
把服务器返回的标示存到浏览器中
- 强缓存问题:如果服务器文件更新了,但是本地还没有更新就拿不到最新信息了
- html不做强缓存:每一次html的请求做正常的http请求
- 服务器更新资源后,让资源名称和之前的不一样,这样页面导入新的资源(webpack hashname)
- 当文件更新后,我们在html导入时候,设置一个后缀(时间戳)
- 协商缓存
- 协商缓存和强缓存的区别
- 协商缓存总会和服务器协商,所以一定要发HTTP请求
- 如果没有协商缓存的时候,向服务器发送请求(没有传递任何标识)
- 服务器收到请求
- last-Modified:资源文件最后更新的时间
- ETag:记录的是一个标识,根据资源文件更新生成的,每一次更新都会重新生成ETag
- 客户端拿到信息后进行渲染,把信息和和标识缓存到本地
- 第二次发请求,把标识传到服务器,服务器根据标识判断文件是否更新
- 强缓存和协商缓存只针对于静态资源文件,不经常更新的
- 数据缓存
把最新的数据缓存到本地
5.DNS(域名解析)解析,也是有缓存的,如果之前解析过本地会有缓存(不一定)
- 减少DNS请求(一个页面尽可能少用不同域名:资源都放在相同的服务器上:项目不会这么干,会把不同资源不同服务器上)
- web服务器,数据服务器,图片服务器(根据每个服务器特点选择服务器,资源合理利用,高可用高并发)
- http并发,同一个源同时可以发送4~7个http请求
- 建立连接通道
keep-live:保证TCP通道建立完成后,可以不关闭http1.0需要手动设置
http1.0和2.0的区别:
手撕源码
//数组的浅拷贝
let newArr = [...arr]
newArr = arr.concat([])
newArr = arr.slice()
//对象的浅拷贝
let newObj = {...obj}
newObj = Object.assgin({}, obj)
//循环的方式for in只能遍历可枚举的
newObj = {}
_.each(obj,(value,key)=>{newObj[key] = value})//和第一种方法区别:Symbol不能遍历
//解决for in缺点
let keys = {...Object.keys(obj),...Object.getOwnPropertySymbol(obj)}
//浅拷贝源码
function shallowClone (){
let type = _.toType(obj),//检测数据类型
Ctor = obj.constructor
//对于Symbol BigInt特殊处理,不做处理的话和之前相等
if(/^(symbol|bigint)$/i.test(type)) return Object(obj)
//正则和日期的处理
if(/^(regexp|date)$/i.test(type)) return new Ctor(obj)
//对于错误对象
if(/^error$/i.test(type)) return new Ctor(obj.message)
//对于函数
if(/^function$/i.test(type)){
return function(){
//返回新函数:新函数执行还是把原函数执行,实现和原函数相同的效果
return obj(this,...arguments)
}
}
if(/^(object|arrary)$/i.test(type)){
return type === "arrary" ? [...obj] : {...obj}
//let result = new Ctor()
//_.each(obj,(_,key)=>{result[key] = obj[key]})
let result = new Ctor(),
keys = [...Object.keys(obj),...Object.getOwnPropertySymbol(obj)]
_.each(keys,(_,key)=>{result[key] = obj[key]})
}
//基本数据类型
return obj
}
//深克隆:只要有下一级的,我们就克隆一次
function deepClone(obj,cache = new Set()){
let type = _.toType(obj),//检测数据类型
Ctor = obj.constructor
//不是数组和对象
if(!/^(object|arrary)$/i.test(type)) return shallowClone(obj)
//避免无限套娃
if(cache.has(obj)) return obj
cache.add(obj)
let result = new Ctor(),
keys = [...Object.keys(obj),...Object.getOwnPropertySymbol(obj)]
//再次调用deepClone时候,把cache传递过去,保证每一次递归都是一个cache
_.each(keys,(_,key)=>{result[key] = deepClone(obj[key]),cache})
return result
}
//上述写法会存在栈溢出错误
obj.xxx = {
0:obj
}
对象的merge合并
- 两个对象合并的意义,插件组件封装(参数处理),业务需求(两个多个接口数据合并)
//实现两个对象的合并,基于浅比较实现的对象合并
let options = Object.assgin(options,params)
//params替换options
function merge(){
}