javascript 有哪些原始类型
Undefined,Null,Boolean,Number,String,Symbol,Bigint (一项新的提案让这个答案可能增加 Record 和 Tuple 这两个不可变数据类型)
执行上下文和作用域链
变量或者函数的执行上下文决定了他们可以访问那些数据,每个函数都有自己的执行上下文,当函数被执行的时候, 它的上下文会被推入 执行栈 ,并创建一个作用域链,执行时沿着作用域链查找变量, 函数执行完毕后,执行上下文也会被弹出。
闭包
闭包是指那些引用了另一个函数作用域中的变量的函数,通常在嵌套函数中实现。内部的函数作为参数传递或结果返回, 仍能访问到其所在的外部函数的变量。闭包形成的原因是当一个函数执行完毕时,将会销毁其执行上下文以及附带的活 动对象, 但由于外部函数的活动对象已被添加到内部函数的作用域链中,故无法被销毁,仍然保留在内存中,供内部函 数使用。 闭包的使用场景包括防抖、节流、单次调用函数等。
尾调用优化
尾调用优化是 ES6 规范新增的内存管理优化机制(严格模式下开启),当一个函数的返回值是是它内部返回值的时候, 通过重用执行栈栈帧的 方式优化内存管理(内部函数执行上下文执行完毕后不会直接弹出,而是先判断它的外部栈帧 是否有存在必要)。
构造函数
任何函数只要通过 new
操作符调用就是构造函数(一个函数也可以通过 new.target
来判断自己是否最为构造函数 被调用)。当使用 new
操作符执行构造函数时,js 解释器会在内存中新建一个对象,并将该对象的 __proto__
属性 赋值为构造函数的原型对象 prototype
,然后构造函数内部的 this
被赋值为这个新对象,执行完内部代码后返回 这个对象(如果构造函数直接返回了一个对象,除非手动操作否则原型链会断掉)。
原型和原型链
每个函数(包括 ES6 类)都会创建一个 prototype
属性指向它的原型对象,原型对象的 constructor
属性也会 指向这个函数(或类),当这个函数作为构造函数使用,实例化出一个对象时,这个实例对象的 __proto__
属性也会 指向构它的原型对象。当一个函数的原型对象是另一个构造函数的实例时,就会形成原型链。
任何使用了 IEEE754 浮点规范的语言都会存在这个问题,双精度浮点数的可靠位为 15 位,16 位之后的可能是对不上的。 0.1 和 0.2 储存值都比实际值要大一些,所以结果不等于 0.3,比较小数是否相当,应该使用两者的差值与 ES6 新增的 Number.EPSILON
属性比较:
if (0.1 + 0.2 - 0.3 < Number.EPSILON) { console.log(`0.1+0.2=0.3`); }
原型链、借用构造函数、组合继承、原型式继承、寄生式继承、寄生式组合继承。详见这里
自己实现 call
或者 apply
Function.prototype.myApplay = function (newThis, argArray) { const tempObj = newThis ?? window; const funcSymb = Symbol("tempFunc"); // 当一个函数作为对象的属性调用时,函数内this指向这个对象 // 利用这点来达到绑定传入的newThis的目的 tempObj[funcSymb] = this; tempObj[funcSymb](...argArray); };
自己实现 bind
Function.prototype.myBind = function (newThis) { const self = this; // 利用闭包,绑定this return function () { self.apply(newthis, arguments); }; };
浏览器每次发起请求,都会现在浏览器缓存中查找该请求的结果以及缓存标识,且每次拿到结果,都会将该结果 和缓存标识存入浏览器缓存中,而这个缓存过程又分为强制缓存和协商缓存。服务端控制缓存规则的字段包括 Expires
(HTTP1.0 的字段,客户端对比时间存在缺陷,已被后者取代)和 Cache-Control
(优先级更高), Cache-Control
的取值有:
当发送请求时,不存在缓存结果或上述标志,则直接向服务器发起请求,如果有这是缓存标志,且存在缓存结果, 则直接返回缓存结果(强制缓存生效,取缓存先内存后硬盘),如果缓存结果失效,则携带缓存标志向服务器请求, 服务器可能会返回 304(协商缓存生效), 浏览器直接使用缓存,也可能返回 200(协商了决定给新的), 浏览器使用新的结果。
协商缓存的过程中,请求会携带一些信息帮助浏览器判断:
协议、域名、端口任一不同即为跨域,浏览器的同源策略,不限制跨域请求的发送,但会拦截请求响应, 常用的跨域解决办法:
document.domain
为父域名即可window.parent
, 两者间也可以通过 postMessage
API 来通信默认情况下跨域 ajax 请求不会携带 cookie
,除非设置 withCredentials
属性为 true
返回报文的 Set-Cookie
可以设置浏览器 cookie
:
JavaScript 设计之初就是一门处理浏览器网页交互的脚本语言,这决定了它注定是一门单线程语言 (多个线程同时操作 ui 是很麻烦的事情)。浏览器式多线程的,当 js 主线程调用 setTimeout
和 addEventLisenter
之类的方法时,会触发其他线程(定时器触发线程、事件触发线程),以此来实现 异步非阻塞。其他线程执行完毕后会将对应的额回调函数较为任务队列维护,当 js 主线程执行栈为空时, 从这个队列中依次去除任务(回调函数)执行。新一轮的执行若有异步任务则重复以上步骤,这个过程称之为 事件循环。
defer
表示延迟执行脚本,解析到 script 标签时立即下载,等待渲染完成后再执行,并保证按出现顺序执行async
表示异步执行脚本,解析到 script 标签时立即下载,下载完就执行(不管浏览器是否渲染完成,可能造成阻塞) 不保证执行顺序type="module"
表示 ES6 模块,浏览器会异步加载,解析到 script 标签时立即下载,等待渲染完成后再执行, (它会受到 async
的影响,可能阻塞渲染`自底向上分析,以下是 Vue 导出的全过程
this._init(options)
(开发模式下会判断是否作为构造函数调用,直接调用 Vue
方法会报错)Vue
,增强(或者说组合) Vue.prototype
能力:
initMixin
,该混入声明了 Vue.prototype._init
,该方法负责一系列的初始化操作 (以下过程发生在实例化中): initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created')
stateMixin
,加入了数据响应相关的能力:
Vue.prototype
上创建 data
和 props
选项的代理访问 $data
和 $props
Vue.prototype
上增加 $set
, $delete
, $watch
eventsMixin
,事件相关的能力: Vue.prototype.$on Vue.prototype.$once Vue.prototype.$off Vue.prototype.$emit
lifecycleMixin
,生命周期: Vue.prototype._update Vue.prototype.$forceUpdate Vue.prototype.$destroy
renderMixin
,渲染相关:
Vue.prototype
上安装渲染辅助工具,(挂载了原型上的 _o
, _n
, _s
之类的属性上)Vue.prototype.$nextTick
Vue.prototype._render
initGlobalApi(Vue)
加入 Vue 全局方法或属性(函数类的静态方法或者属性):
Vue.config
,全局配置Vue.util
,包含 warn
, extend
, mergeOptions
, defineReactive
方法Vue.set
, Vue.delete
, Vue.nextTick
,与原型上的是一样的Vue.observeable
,2.6 版本的 API,时一个对象可响应Vue.options
,初始化了 components
, directives
, filters
空值选项,以及 _base=Vue
, 并增加内置组件 KeepAlive
initUse
,增加插件拓展能力 Vue.use
(判断插件是否有 install
方法以及是否重复安装)initMixin
,增加混入能力 Vue.mixin
(实际上就是将混传入的 options
与当前 options
, 会影响所有 vm
实例,通常在插件中使用, Vue.use
和 Vue.mixin
方法都返回了 this
,支持链式调用)initExtend
,增加继承能力 Vue.extend
(用于创建 Vue的子类
,会在内部缓存)initAssetRegisters
,增加 Vue.component
, Vue.directives
, Vue.filters
这三个资源注册能力Vue.protoype
环境相关标记 $isServer
, $ssrContext
, 以及全局标记 Vue.FunctionalRenderContext
和 Vue.version
以上为核心 Vue 的导出过程,接下来是导出完整的运行时版本 Vue,从这一步开始,就会区分 web 和 weex 平台了 (各平台的视图相关操作不尽相同),以下以 web 为例:
Vue.config
上继续添加一些相关标记v-model
, v-show
和组件 Transition
, TransitionGroup
patch
方法到 Vue.prototype.__patch__
Vue.prototype.$mount
方法(其实就是调用 mountCommponent
方法)如果是待编译器的版本,会在上述基础上继续:
Vue.prototype.$mount
方法取出暂存,重写 $mount
:
html
或者 body
template
并转换为渲染函数,没有 template
则取挂载元素的 outerHTML
作为 template
$mount
Vue.compile = compileToFunctions
Vue 的实例化过程
Vue 的构造函数中只调用了 this._init
, new Vue()
这个过程详细展开为:
vm.$options
initLifecycle(vm)
初始化生命周期:
vm.$children=[]
和 vm.$refs={}
vm
上生命周期的相关标记, _watcher
, _inactive
, _directInactive
, _isMounted
, _isDestroyed
, _isBeingDestroyed
initEvents(vm)
初始化事件:
vm._events
和 vm._hasHookEvent
initRender(vm)
初始化渲染:
vm._vnode
和 vm._staticTrees
标记vm.$slot
和 vm.$scopedSlots
createElement
到 vm._c
和 vm.$createElement
( alwaysNormalize
参数不同)defineReactive
注册 vm
实例响应对象 vm.$attrs
和 vm.$listeners
callHook(vm, 'beforeCreate')
触发 beforeCreate 钩子initInjections(vm) // resolve injections before data/props
vm.$options
上的 injection
配置shouldObserve
开关injection
,对每个 key
使用 defineReactive
shouldObserve
开关initState(vm)
初始化状态:
vm._watchers=[]
initProps
shouldObserve
开关propsOptions
propsData
是否满足条件(类型,验证等)defineReactive
注册响应对象vm
的 props
访问代理 vm._props.xxx
shouldObserve
开关initMethods
,遍历 metheds
里的方法,对每个方法使用 bind
,绑定 vm
执行上下文 (这也是方法中 this
能访问到 vm
的原因)initData
vm
的 data
访问代理 vm._data.xxx
observe(data,true)
initComputed
vm._computedWatcher
defineComputed
,非 ssr 情况会生成新的 watcher
赋值给 vm._computedWatcher.xxx
initWatch
,遍历 watch
创建相应的 watcher
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
触发 created 钩子vm.$options.el
是否存在,是则调用 vm.$mount
进行挂载Vue 的挂载过程
vm.$options
是否有 render
,没有的话调用 createEmptyVNode
生成空节点beforeMount
钩子updateComponent
回调函数 updateComponent = () => { vm._update(vm._render(), hydrating)}
vm._render()
-> vm.$createdElement
-> createElement
Watcher
,传入 updateComponent
回调,并注入 beforeUpdate
钩子(初始化时,默认会执行`updateComponent 钩子)vm._isMounted
,触发 mounted
钩子响应式原理
再实例挂载的完毕中,创建了一个 watcher
,他的回调 updateComponent
会立即执行依次,调用 vm._render()
的过程中会触发响应式数据的 getter
,进行一次依赖收集,当有更新时则会派发更新。
响应式的核心是 Watcher
、 Observer
、 Dep
(经典的观察者模式),大致的流程为
Vue 的实例化过程中,对 props
, data
等都进行了 defineReactive
操作,该操作就是给定义一个响应式对象, 给对象动态添加 getter
和 setter
在 defineReactive
创建响应式对象的过程中,会实例化一个 Dep
, Dep
类实际上是对 Watcher
的一种管理, 同时 Dep
类上有一个静态属性 target
,指向一个全局唯一的 Watcher
, Watcher
类也会维护需要更新的 Dep
队列,当响应式对象的 getter
触发时,会把自己的 dep
实例加入到目标 watcher
的 dep
实例队列, 同时也会把 watcher
实例加入到 dep
的 subs
队列,这个过程则是依赖收集。
实际上 Watcher
类维护了两个 dep
实例数组,一个表示新添加的,另一个表示上一次添加的
当修改响应式对象时,则会触发相应的 setter
,它会调用 dep
的 notify
方法,然后依次调用 def
实例中的 subs
也就是 watcher
实例数组的 update
方法,将需要更新的回调加到一个队列里,在 nextTick
时后执行
讲一讲 vue2 的响应式原理
Vue 是怎么实现组件化的
从底层代码来看,我觉得组件化的核心是 Vue.extend
,
vue-router 原理
vuex 原理
很开放的问题,以下是一些可展开的点: