1.理解Event Loop
- JavaScript是单线。(原因:JavaScript的主要用途是与用户互动,以及操作DOM,这决定了它只能是单线程)
- 所有同步任务都在主线程上执行,形成一个执行栈。
- 主线程之外,还存在一个"任务队列"(消息队列)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
- 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
- 任务队列又分为微任务对列和宏任务对列,微任务队列执行完毕,才回去执行宏任务队列。
- 述过程会不断重复,也就是常说的
Event Loop(事件循环)。
微任务: promise 的回调、node 中的 process.nextTick 、对 Dom 变化监听的 MutationObserver。
宏任务: script 脚本的执行、setTimeout ,setInterval ,setImmediate 一类的定时事件,还有如 I/O 操作、UI 渲 染等。
作者:JakeZhang
链接:juejin.cn/post/684490…
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
总结:主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)
参考:JavaScript 运行机制详解:再谈Event Loop
2.作用域和作用域链
- 作用域:
- Javascript中的作用域说的是变量的可访问性和可见性。也就是说整个程序中哪些部分可以访问这个变量,或者说这个变量都在哪些地方可见。
- Javascript使用词法作用域(静态作用域),这意味着变量的作用在编译阶段(定义时的作用域)就会被确定。
- 全局作用域:任何不在函数中或是大括号中声明的变量,都是在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问
- 函数作用域:如果一个变量是在函数内部声明的它就在一个函数作用域下面。这些变量只能在函数内部访问,不能在函数以外去访问。
- 块级作用域:在大括号中使用let和const声明的变量存在于块级作用域中。在大括号之外不能访问这些变量。
- 作用域链:
- 作用域链就是用来查找变量的,作用域链是由一系列作用域串联起来的。(当在Javascript中使用一个变量的时候,首先Javascript引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域。 如果在全局作用域里仍然找不到该变量,它就会在全局范围内隐式声明该变量(非严格模式下)或是直接报错。)
- 变量按从内向外的查找
- 内层的变量可以屏蔽外层的同名变量
3.闭包
function foo() {
let a = 2
function too() {
console.log(a)
}
return too
}
foo()() // 2
一个函数执行后返回另一个可执行函数,被返回的函数保留有对它定义时外层函数作用域的访问权。foo()() 调用时依次执行了 foo、too 函数。too 虽然是在全局作用域里执行的,但是too定义在 foo 作用域里面,根据作用域链规则取最近的嵌套作用域的属性 a = 2。
优点:
- 用于保存私有属性:将不需要对外暴露的属性、函数保存在闭包函数的父函数里,避免外部操作对值的干扰
- 避免局部属性污染全局变量空间导致的命名空间混乱 缺点:
- 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题
总结:闭包就是能够读取其他函数内部变量的函数,这里把闭包理解为函数内部定义的函数。
4.this指向
- 正常情况下this的指向
- 纯粹的函数调用。(这是函数的最通常用法,属于全局性调用,因此this就代表全局对象)
- 作为对象方法的调用。(函数还可以作为某个对象的方法调用,这时this就指这个上级对象)
- 作为构造函数调用。(所谓构造函数,就是通过这个函数,可以生成一个新对象。这时,this就指这个新对象)
- 怎么改变 this 的指向
- 使用 ES6 的箭头函数。(箭头函数的 this 始终指向函数定义时的 this,而非执行时。,箭头函数需要记着这句话:“箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined”
- 在函数内部使用 _this = this
- 使用 apply、call、bind
- apply 和 call 基本类似,他们的区别只是传入的参数不同(call 方法接受的是若干个参数列表,而 apply 接收的是一个包含多个参数的数组。)
- bind 是创建一个新的函数,我们必须要手动去调用(bind 方法接受的是若干个参数列表)
- 自己理解 this.X的变量和作用域变量不一样,this当前环境没有,他就不会向上查找了 总结:this就是函数运行时所在的环境对象。(this 永远指向最后调用它的那个对象)
5.MVC和MVVM
- MVC (m:模型 v:视图 C:控制器)
特点:各部分之间的通信方式如下,所有通讯都是单向的 。
- View 传送指令到 Controller
- Controller 完成业务逻辑后,要求 Model 改变状态
- Model 将新的数据发送到 View,用户得到反馈
graph TD
View --> Controller --> Model --> View
总结:MVC模式的业务逻辑主要集中在Controller,而前端的View其实已经具备了独立处理用户事件的能力,当每个事件都流经Controller时,这层会变得十分臃肿。而且MVC中View和Controller一般是一一对应的,捆绑起来表示一个组件,视图与控制器间的过于紧密的连接让Controller的复用性成了问题。
- MVVM(m:数据层 v:视图 vm:连接view和model的桥梁)
graph TD
ViewModel-->View --> ViewModel -->Model -->ViewModel
在MVVM的框架下视图View和模型Model是不能直接通信的。它们通过ViewModel来通信,ViewModel通常要实现一个observer观察者,当数据发生变化,ViewModel能够监听到数据的这种变化,然后通知到对应的视图做自动更新,而当用户操作视图,ViewModel也能监听到视图的变化,然后通知数据做改动,这实际上就实现了数据的双向绑定。并且MVVM中的View 和 ViewModel可以互相通信。
- 对比 MVVM不仅仅简化了业务与界面的依赖,还解决了数据频繁更新(频繁DOM操作),降低了应用的耦合,提高了代码的重用性
6.Vue2原理
1._init:初始化生命周期、事件、 props、 methods、 data、 computed 与 watch 等。其中最重要的是通过 Object.defineProperty 设置 setter 与 getter 函数,用来实现「响应式」以及「依赖收集」
2.compile:
- parse:会用正则等方式解析 template 模板中的指令、class、style等数据,形成AST.
- optimize:标记 static 静态节点,
patch的过程, diff 算法会直接跳过静态节点,从而减少了比较的过 程,优化了patch的性能。 - generate:将 AST 转化成 render function 字符串的过程(render 的字符串(vnode节点))。
3.patch:核心 diff 算法,通过同层的树节点进行比较而非对树进行逐层搜索遍历的方式。
4.数据变更
-
setter
-
dep.notify()
-
watcher.update()
-
queueWatcher(this) // watcher把自身传进了queueWatcher()在queueWatcher方法中
-
queue.push(watcher) // 在push之前会检查queue中是否已有该watcher(比如:一个属性改变100次)
-
!waiting && waiting = true && nextTick(() => { // ... 执行queue中所有watcher的run }) (所以当改变一个值是,直接调用dom是拿不到最新的,只有当下一次tick完成才能拿到)
-
waiting= false
总结: 采用数据劫持结合发布-订阅模式,通过Object.defineProperty()方法劫持各个属性的set,get,在数据变动时发布消息给订阅者,触发相应监听回调。数据变化更新视图,视图变化更新数据。
7.原型和原型链
-
引用类型,都具有对象特性,即可自由扩展属性。
-
引用类型,都有一个隐式原型
__proto__属性,属性值是一个普通的对象。 -
引用类型,隐式原型
__proto__的属性值指向它的构造函数的显式原型prototype属性值。 -
当你试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么它会去它的隐式原型
__proto__(也就是它的构造函数的显式原型prototype)中寻找。
总结:原型链的概念: 实例先从自身出发检讨自己,发现并没有 toString 方法。找不到,就往上走,找 构造函数的prototype属性,还是没找到。构造函数的 prototype也是一个对象,那对象的构造函数Object, 所以就找到了 Object.prototype下的 toString方法。这种寻找的过程就是原型链