第一次在掘金上写东西,就当是一个记事本在使用吧,掘金对样式的支持还是挺人性化的。 学习文章地址:2020年前端面试复习必读文章【超百篇文章/赠复习导图】 我这边就不贴文章的内容了(要征询同意),主要还是写一些自己对知识点的理解。
开始:
1.1 执行上下文/作用域链/闭包
执行上下文:
简而言之,执行上下文是评估和执行 JavaScript 代码的环境的抽象概念。每当 Javascript 代码在运行的时候,它都是在执行上下文中运行。
我看的这篇文章[译]理解 JavaScript 中的执行上下文和执行栈
上下文其实就是一个js的环境,他可以是定义变量的环境,也可以是执行的环境;
插一下 感觉掘金这个编辑器还是挺有意思的 如果感兴趣可以自己试试~因为可能涉及到原文的一些知识我这边也懒得去找原作者了,所以尽量是为了让自己以后看得懂就行。如果有读者阅读到可以直接去原文地址学习0v0
执行上下文的类型:
全局执行上下文 ,函数执行上下文,Eval 函数执行上下文;
这边三个概念可以结合我上面那句话;
他可以是定义变量的环境:
全局执行上下文;
也可以是执行的环境:
函数执行上下文,函数只有在运行了 才会向执行栈上方加入函数执行上下文;
执行栈:
后入先出
创建执行上下文:
1、绑定this
2、创建词法环境
3、创建变量环境
在 ES6 中,词法环境组件和变量环境的一个不同就是前者被用来存储函数声明和变量(let 和 const)绑定,而后者只用来存储 var 变量绑定。
这些所谓的环境就是记录我们js代码的声明啊 函数啊之类的(注意这个时候变量都是没有分配的 只有定义,所以这个也是var 一个变量的前面去引用这个变量 不会报错而是返回undefind也就是所谓的变量提升)
执行阶段:
赋值运行
可以继续编辑就好 接下去 会不定期更新 主要还是根据上面学习文章的目录来
作用域链
作用域链有分为 静态和动态,而js是静态,静态的特点就是函数在申明的时候就已经确定了作用域。我觉得这句话可以很好的解释this的指向
var name ="juejing";
var person = {
name: "shree",
getName: function(){
return this.name;
}
}
person.getName();//shree
上面getName其实的this 根据上面所说的 函数在申明的时候就已经确定了作用域 我们看最后一行运行getName是person去引用,相当于他的声明就是原来person里面,这个时候他的this就是指向person。
var name ="juejing";
var person = {
name: "shree",
getName: function(){
return this.name;
}
}
var newGetName = person.getName;
newGetName();//juejing
一开始 我读到函数在申明的时候就已经确定了作用域这句话的时候就想到 这种情况,这边定义getName这个函数的时候 this就是指向person,而我们所知道this如果是直接引用的方式,他的this是指向window或者是undefined的(严格模式下)。 那我们这边要怎么解释呢?其实,这边的newGetName函数已经和person那个不是同一个了,也不能说是不是同一个,可以说是内存已经是分开管理了(我不知道我这么理解对不对),所以实际newGetName这个函数定义的时候,他的作用域或者说他的执行上下文是全局或者window,所以这个时候他的this就是指向window。
闭包
关于闭包,这个问题可能已经算是老生常谈了。其实我现在还是不很清楚我已经完全理解了,看过的文章也很多的。多少说点我的理解。
上面说的执行上下文,其中的函数上下文,只在函数执行的时候才会加入到js的执行栈当中,上下文里会有函数中的变量、参数等等,其实就是可以所有该函数的所以内容。当碰到return的时候,函数执行上下文就会销毁。那么如果这些都销毁掉了,就不存在闭包了,所以 闭包是一个单独的概念。
在函数执行的时候,他会产生一个背包,js会把函数内所用到的变量储存到函数的这个背包里。
function test(){
var num = 0;
return function(){
num+=1;
console.log(num)
}
}
var shree = test();
shree();//1
shree();//2
shree();//3
如果只了解到了执行上下文,那当var shree = test();结束的时候 test的函数上下文已经被销毁了,当然其中肯定是包含这个num这个变量,那为什么后面运行test retur函数的时候num依旧有值,所以这个就事我上面说到的概念:在函数执行的时候,他会产生一个背包,js会把函数内所用到的变量储存到函数的这个背包里。 一开始读文的时候,说看了执行上下文会对闭包有一些新的理解。但是看完之后其实我是觉得执行上下文和闭包这个没什么联系(可能是函数里引用了不属于自己执行上下文中的变量为了防止这个变量消失?他就会增加一个背包去存放他的这个变量?嗯??我好像被自己说服了)- -
同时我这里想到,如果他是增加一个背包去存放他这个变量值,那么他们的内存的地址是一样的么?
我试图写出可以单独改变num值的demo,但是我发现如果存在闭包,那么也就不可能单独改变num,与其说闭包产生的这个背包,不如说是每个变量他会去判断自己是否存在闭包中,如果是 那么内存就不会删除,而我们上面所说的背包其实就不存在
上面只是个人理解不知道对不对,如果有有缘的大佬看到可以指点一二。
1.2 this/call/apply/bind
this
关于this我在上部分讲到过,只需要理解函数在申明的时候就已经确定了作用域这句话就可以了
这张图我是很久之前也是在掘金上看到的,看这个应该可以一目连了。
call/appy/bind
他们主要的区别就是call第二个参数不是数组,他可以有第三第四。。。的参数,apply第二个参数是数组,他们两者都是直接运行对应的方法的。而bind是创造一个转移了this的方法,实际并没有执行。这个方法可以用来当作监听事件的回调等等。。
至于手写call/apply,主要的思路就是,往需要转移的this的这个对象里插入需要执行的函数,然后再删除。
bind的话实际返回一个apply的方法即可
1.3 原型/继承
在规范里,prototype 被定义为:给其它对象提供共享属性的对象。
也就是说他也是一个对象,只不过被放到每个的对象里。
我们随便在console中打印一个对象,可以看到shree里面除了我们声明的a,b两个属性,还有个_proto_(隐式引用),里面的内容就是prototype。
在控制台却可以发现它有 _proto_ 属性,这意味着 obj 被隐式地挂载了另一个对象的引用,置于 _proto_ 属性中。
表面上看,上图的对象里存在一个_proto__属性。实际上,它只是开发者工具为了方便让开发者查看原型,故意渲染出来的虚拟节点。虽然跟对象的其它属性并列,但并不在该对象中。proto 属性既不能被 for in 遍历出来,也不能被 Object.keys(obj) 查找出来。访问对象的 obj._proto 属性,默认走的是 Object.prototype 对象上 _proto_ 属性的 get/set 方法。
1)通过 Object.getPrototypeOf(obj) 间接访问指定对象的 prototype 对象。
2)通过 Object.setPrototypeOf(obj, anotherObj) 间接设置指定对象的 prototype 对象。
子级的_proto_就是父级的prototype,原型对象(Person.prototype)是 构造函数(Person)的一个实例(但是Person.prototype._proto_是指向obj.prototype)
让我们看一下这张神图
根据这张图我得出的结论就是不管是什么,它的.__proto__就是他父级的prototype
从构造函数开始说
function Shree(){
this.a = 1
}
Shree.prototype.age = 123;
var test = new Shree();
console.log([Shree]);
console.log(test);
我们从这张图最上面的部分开始看。根据我们刚刚说的不管是什么,它的.__proto__就是他父级的prototype。这里test的父级是Shree,所以他的__proto__就是Shree的prototype,很显然图中就是这么指示的,为了验证我们看看打印的内容。
而其中的constructor指向了Shree本身。
那Shree他的.__proto__呢,又回来我们那句话不管是什么,它的.__proto__就是他父级的prototype,那么Shree作为一个函数,他的父级就是Function,所以它的.__proto__指向了它的父级的prototype,也就是Function.prototype,这边我们也可以验证一下。
那么根据刚刚说的逻辑,Function.prototype他的constructor应该指向他自己本身(function Function),那么他的_proto_是什么呢,要找一个人的_proto_永远是去找他的父级prototype,所以(function Function)的_proto_还是指向了Function.prototype,如果你这个时候又回到他的constructor,就会发现他们是互相引用的,就是一个死循环了。
而又到了Function.prototype,实际上你会发现任何prototype就是一个对象,根据js万物皆对象,那么Function.prototype他是没有prototype的,他那的.__proto__就是他父级的prototype,Function的父级其实就是Object,所以这个时候Function.prototype它的.__proto__就是Object的prototype,那是Object的prototype的.__proto__也可以说是Function.prototype的爷爷级是什么呢,那这个时候到底了,为null。
其实也可以把Object.prototype看成任何一个普通对象。你也可以理解成所有方法的prototype的___proto__就是Object.prototype;prototype只是用来描述函数的继承关系的一个对象(这是我的理解)
不管是什么,它的.__proto__就是他父级的prototype
这句话要注意:Object.prototype,它的.__proto__是null,因为Object.prototype他实际上是一个对象,那么他的父级其实就是Object,那么Object.prototype不就是他本身了么,所以这边设定为null。而这里的Object实际上是一个function,因为只有function才会有prototype。通过到constructor指向了本身Object()这个function,那他既然是function他的__proto__会指向他的父级的prototype,也就是Function.prototype。
以上分析主要还是根据那张经典神图来进行的。
总结三点:
1、__proto__就是他父级的prototype(只有函数有prototype)
2、constructor属性也是对象所独有的(函数也是对象),它是一个对象指向一个函数,这个函数就是该对象的构造函数。(对象本身没有constructor,其__proto__为对象的prototype,而prototype有constructor)
3、prototype就是相当于介绍自己的一个对象属性,里面有constructor和__proto__以及我们手动添加的属性。
重写new方法
function myNew() {
var obj = {},
fn = [].shift.call(arguments);
obj.__proto__ = fn.prototype;
<!--const F=function(){};-->
<!--F.prototype= fn.prototype;-->
<!--obj=new F();//指向正确的原型-->
var res = fn.apply(obj, arguments);
return typeof res === "object" ? res : obj
}
1.4 Promise
1.5 深浅拷贝
JSON.parse(JSON.stringify(originArray))
const MY_IMMER = Symbol('my-immer1')
const isPlainObject = value => {
if (
!value ||
typeof value !== 'object' ||
{}.toString.call(value) != '[object Object]'
) {
return false
}
var proto = Object.getPrototypeOf(value)
if (proto === null) {
return true
}
// console.log(Object.hasOwnProperty.call(proto, 'constructor'))
var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor
return (
typeof Ctor == 'function' &&
Ctor instanceof Ctor &&
Function.prototype.toString.call(Ctor) ===
Function.prototype.toString.call(Object)
)
}
const isProxy = value => !!value && !!value[MY_IMMER]
function produce(baseState, fn) {
const proxies = new Map()
const copies = new Map()
const objectTraps = {
get(target, key) {
if (key === MY_IMMER) return target
const data = copies.get(target) || target
return getProxy(data[key])
},
set(target, key, val) {
const copy = getCopy(target)
const newValue = getProxy(val)
// 这里的判断用于拿 proxy 的 target
// 否则直接 copy[key] = newValue 的话外部拿到的对象是个 proxy
copy[key] = isProxy(newValue) ? newValue[MY_IMMER] : newValue
return true
}
}
//生成proxy对象,并且塞入缓存
const getProxy = data => {
if (isProxy(data)) {
return data
}
if (isPlainObject(data) || Array.isArray(data)) {
if (proxies.has(data)) {
return proxies.get(data)
}
const proxy = new Proxy(data, objectTraps)
proxies.set(data, proxy)
return proxy
}
return data
}
const getCopy = data => {
if (copies.has(data)) {
return copies.get(data)
}
const copy = Array.isArray(data) ? data.slice() : { ...data }
copies.set(data, copy)
return copy
}
const isChange = data => {
if (proxies.has(data) || copies.has(data)) return true
}
const finalize = data => {
if (isPlainObject(data) || Array.isArray(data)) {
if (!isChange(data)) {
return data
}
const copy = getCopy(data)
Object.keys(copy).forEach(key => {
copy[key] = finalize(copy[key])
})
return copy
}
return data
}
const proxy = getProxy(baseState)
fn(proxy)
return finalize(baseState)
}
const state = {
info: {
name: 'yck',
career: {
first: {
name: '111'
}
}
},
data: [1]
}
const data = produce(state, draftState => {
// console.log(draftState.info.age)
// draftState.info.age = 26
draftState.info.career.first.name = {age:12}
})
// data.info.career.first.name= '333'
console.log(data, state)
console.log(data.data === state.data)
1.6 事件机制/Event Loop
异步和同步任务,需要注意的是,如果异步任务的时间到了,同步任务还在执行的话,异步任务会等到同步任务全部执行后,立即执行。 除了Process.nextTick、Promise.then catch finally(注意我不是说 Promise)、MutationObserver。其他都是MacroTask任务。 至于MicroTask优先级高于MacroTask,这也就是为什么,setTimeout优先级比promise.then低
2 CSS
3 框架
3.1 MVVM
从observer出发,observer获取到vm对象,取出其中的data,进行proxy代理,每个代理通过闭包会创建一个Dep依赖收集器,get往Dep中添加watch订阅者,set通知Dep触发更新事件,对应的Dep中的所有watch收到通知后执行watch中的run(回调)(这里的回调可以说是更新视图);倒回去,从compile出发,compile通过解析模版获取到和视图挂钩的数据,创建相对于数据的Watch,并添加回调函数(用途更新视图);
总结:一个数据对应一个dep依赖管理,一个dep对应多个watch,一个watch对应一个视图更新数据(时间)