prototype重定向
function Fn(){}
Fn.prototype.say = () => {}
Fn.prototype = new Fn()
重定向有几个问题
- 1 重定向后prototype指向的对象可能不具备constructor对象,必须自己手动配置。
- 2 可能导致浏览器默认开启的原型对象上的方法丢失。解决:重定向的对象最好和之前的原型对象做一个合并处理。
如
Fn.prototype = Object.assing({}, Fn.prototype, new Fn())
原型重定向的优点:
- 1方便批量向原型更新方法
- 2 原型继承的方案就是基于原型重定向完成的。
手撕call bind apply
call的作用可以改变函数的this,并执行。
const a = {d:1}
var d = 2
function D(){
console.log(this.d)
}
D() // 2 this指向window
D.call(a) // 1. this指向了a
D.apply(a) // 1
const test = D.bind(a)
test() // 1 this指向了a
原理: 首先D基于_proto _ 找到了Function.prototype.call方法。将call方法执行。 实现
function _call(obj, ...args){
const a = Symbol('obj')
obj[a] = this //让obj短暂的拥有了这个办法并且执行。
const result = obj[a](...args)
delete obj[a]
return result
}
apply跟call一样,不同的是入参的区别,apply第二个参数必须是数组,他会一个个传给fn。
function apply(obj, args){
if(!Array.isArray(args)){
return;
}
const a = Symbol('obj')
obj[a] = this //让obj短暂的拥有了这个办法并且执行。
const result = obj[a](...args)
delete obj[a]
return result
}
bind是将函数的this改变之后,并传入对应的参数之后,返回一个新的函数,不会执行。
functuion bind(obj, ...args){
const self = this
return function(...args2){
self().call(fn,...args,...args2)
}
}
js中的四种检测
typeof xx xx instance X constructor Obhect.prototype.toString.call(xx)
typeof
typrof [value] => 字符串,包含类型。
局限性:
typeof null => 'object'
typeof 对象,除了function检测出来时'function',其他都是'object'
因为:typeof检测类型的机制是,按照计算机底层存储的二进制来进行检测的。 以000开始的对象,而null在计算机的存储都是。 而所有对象的存粗二进制前三个值都是0。所以null也会被判断为object。 好处:简单,速度。 特点:只能识别原始类型和function。
instanceof
因为typeof的局限性,所以instanceof来充当壮丁了。因为instance本身不是用来判断类型的,而是用来判断实例是否是某个类的。 但是instanceof不能用来检测原始值类型。
[] instanceof Array // true
[] instanceof Object // true
{} instanceof Array // false
{} instance Object //true
new Number(1) instanceof Number // true
因为数组也是对象,只不过是特殊的对象。 Array.prototype._ proto _ = Object.prototype. 原理:按照原型链检测,只要当前检测的构造函数(他的原型对象),出现在实例的原型上(原型链),检测结果就是true。 实现:
f instanceof Fn =>
第一步:
先判断Fn上有没有这个属性 Symbol.hasInstance
有的话将f作为参数执行 Fn[Symbol.hasInstance](f),
将其结果返回作为instaceof的结果。
f instanceof Fn === Fn[symbol.hasInstance(f)]
- 在函数中,Function.prototype就有这个Symbol.hasInstance属性,
并且,如果直接修改如 Fn[Symbol.hasInstace] =()=>{}不会成功 只能通过es6的写法,
class Fn{static [Symbol.hasInstace](){}}
去修改才会成功 所以所有的函数都会有这个属性
局限性: 可能可以通过prototype来修改原型,所以判断的值也有可能有误。
const _instanceof = function _instanceof(obj, Ctor){
let proto = Object.getPrototypeOf(obj)
while(proto){
if(proto === Ctor.prototype){
return true
}
proto = Object.getPrototypeOf(proto)
}
return false
}
instancof的本质就是判断Ctor的类的原型是否在obj的原型链上。
constructor
constructor也是被拉下临时顶替的,因为它能弥补instancof的不足。
const arr = []
arr.constructor === Array //true
arr.constructor === Object //false
//还有
const Fn = {}
const fn = new Fn()
fn.constructor === Object //false
Fn.prototype = {}
const fn1 = new Fn()
f1.constructor === Object //true,
//他的constructor是通过{}._proto_去Object.prototype找的。
但是constructor的修改更加容易,所以更加容易出错。
Object.prototype.toString.call(val)
最完美的。他的值是
'[object String|Number|Array|Object...]'
//第二个就是类型,首字母大写。
大部分内置类都有自己的toString,用来转换为字符串, 但是Object.prototype.toString是用来检测数据类型的,返回值中包含自己所属的构造函数的信息。
- 用call的原因是让所有类型都可以i使用Object.prototype上的toString,里面的this指向谁就检测谁。
- 但是当实例对象拥有Symbol.toStringtag属性,救返回该属性值。
深浅拷贝
JSON.parse(JSON.string())
缺点:属性值symbol,undefined,functiion会丢失,属性值是Error或者正则会转为对象,属性值是bigInt报错,属性值是Date的,转化为字符串,再重新转为对象,结果还是字符串。原理:把json字符串转划为对象,浏览器会重新分配内存,实现深拷贝。 如: 可以自己手写一个函数递归遍历来实现深拷贝。
前端开发中的同步异步概念
基础可以看js中的eventloop 这里再记录一些细节。
渲染进程:
- GUI渲染线程,渲染页面 & 绘制图形
- JS引擎线程,解析和执行js代码跟GUI渲染线程互斥。
- 事件触发线程:监听事件触发
- 定时器触发线程:监听定时器计时
- 异步http请求线程:基于http从服务器获取数据
- Webscoker
- …
异步编程实现机制:
- 多线程机制
- eventloop事件循环机制
- …
js是“单线程的”,浏览器只分配一个js引擎线程来解析和执行js代码。js中大部分操作都是同步的,少部分操作,结合Eventloop机制,实现了异步处理。
异步任务:
- 异步微任务 microtask:
promise.then,async,await, queueMicork(手动创建微任务),process.nextTick,MutationObserver(监听dom)… - 异步宏任务 macrotask:
定时器,事件绑定,fetch/ajax,node的setImmediate,script整个代码块…
js底层运行机制。 所有的代码都会在执行栈中(主线程,js引擎)执行,然后还有
-
WebApiS 任务监听队列,所有的异步任务都需要在这个队列中进行监听,监听何时执行。 -
还有一个EventQueue,,任务/事件等待队列。 -
当异步任务在监听队列当中检测到已经可以执行了,就会把执行的任务挪到任务等待队列(EventQueue)。 -
当主线程执行完同步代码之后,再按照优先级,依次从任务等待队列中找到对应的异步任务,把它放到主线程中去执行。然后执行完继续来任务等待队列中找。这就是事件循环机制eventloop。优先级顺序就是:先找可以执行的微任务,再找可执行的宏任务。优先级相同,谁先进队就先执行谁。队列的特点也是先进先出。
微任务,宏任务。
EventQueue有两个队列,一个微任务队列,一个宏任务队列。 如
setTimeout(()=>{},2000)
for(let i = 0; i< 99999; i++){}
Promise.then(()=>{console.log(123)})
当js引擎执行到这行代码的时候,会将其放入webApis队列,并且浏览器开启一个定时器监听线程,开始计时!(这个时间需要大概5-7ms完成) 然后同步代码到循环,循环没多久,setTimeout就到时间了,但是js是单线程的,只能同时做一件事情,所以把可执行的setTimeout放入EventQueue中。 遇到Promise.then的时候,将它放入EventQueue的微任务队列。
Promise
实现原理可以了解手撕promise
async await
实现原理可以了解 async
async函数中,await是异步的微任务,遇到await之后,函数体外的代码会继续执行,函数体内await之后的代码会暂停执行,把他们当作微任务,放置在EventQueue的微任务队列中,相当于promise.then。
面试题:
- 首先a执行,打印’all-start’,
- 然后遇到await testA(),执行testA函数。打印 “estA- start”。await下面的代码会放入微任务执行,等待await后的promise返回成功。所以继续执行同步代码,打印"中间穿插"
- 等到2s后,await后面的promise返回成功,打印’testA end’
- 然后打印 “test常量 testA’
- 接着遇到await testB(),执行testB(),打印 testB strat
- 后面的代码放入微任务中。1s过后,打印testB end
- 然后打印test常量 testB
结果正确。
类的继承
类的继承,封装,多态。
- 封装: 实现某个功能的代码进行封装处理,后期想实现这个功能,直接调用函数即可完成“低耦合,高内聚“
- 多态:方法名相同,参数个数不同会认为是多个方法(重载)
- 继承 子类继承父类的方法。
原型继承
直接让子类的prototype指向new Parent()
function F(){}
function S(){}
S.prototype = new F()
S.prototype.constructor = S
直接让S.prototyoe指向了F的实例,那么S的实例可以通过原型链找到F上的prototype的方法属性。 特点:并没有copy一份父类的方法,只是建立父类与子类的原型的关系。子类实例可以通过原型链,_ proto _去获取。赋予父类私有的属性,会变成子类共有的,不可以传值给父类的构造函数。
call继承,把父类当作普通方法,去初始化对象。
function F(){
this.f = 200
}
function S(){
F.call(this) //通过父类去初始化this
this.s =. 300
}
特点:父类的原型跟子类完全没关系,
两者结合 寄生组合继承
function F(){}
function S(){
F.call(this)
}
S.prototype = Object.create(F.prototype)//创建一个新的空对象,proto指向F,并且作为S的prototype。
S.prototype.constructor = S
es6 继承
使用extends加上super,一旦使用extends,还编写了constructor,就必须super。他的原理类似于call继承。 extends+super类似于寄生组合继承。
|