前言
最近參與一次關(guān)于Vue2.0的集中學(xué)習(xí)。主要學(xué)習(xí)了以下內(nèi)容。
- 響應(yīng)式原理的實(shí)現(xiàn)
- vue的模板編譯
- 依賴(lài)收集和異步更新機(jī)制
- Vue dom算法的實(shí)現(xiàn)
現(xiàn)在對(duì)學(xué)習(xí)內(nèi)容進(jìn)行一次集中總結(jié)整理,方便以后的學(xué)習(xí)。
開(kāi)發(fā)環(huán)境的搭建
rollup
Rollup 是一個(gè) JavaScript 模塊打包器,可以將小塊代碼編譯成大塊復(fù)雜的代碼, rollup.js更專(zhuān)注于Javascript類(lèi)庫(kù)打包。
-
安裝rollup
npm install @babel/preset-env @babel/core rollup rollup-plugin-babel rollup-plugin-serve cross-env -D 配置文件rollup.config.js
/**
* rollup 的配置文件
*/
import babel from 'rollup-plugin-babel';
import serve from 'rollup-plugin-serve';
export default {
//入口
input: './src/index.js',
output: {
format: 'umd', // 模塊化類(lèi)型
file: 'dist/vue.js',
name: 'Vue', // 打包后的全局變量的名字
sourcemap: true //源碼映射
},
plugins: [
babel({
exclude: 'node_modules/**' //忽略打包文件
}),
process.env.ENV === 'development'?serve({
open: true,
openPage: '/public/index.html',
port: 8000,
contentBase: ''
}):null
]
}
- 腳本文件
在package.json 中添加
"scripts": {
"build:dev": "rollup -c",//打包
"serve": "rollup -c -w" //啟動(dòng)
},
- 啟動(dòng)
在控制臺(tái)執(zhí)行npm run serve
響應(yīng)式原理的實(shí)現(xiàn)
原理
Vue 2.0依賴(lài)Object.defineProperty 數(shù)據(jù)劫持來(lái)實(shí)現(xiàn)數(shù)據(jù)響應(yīng)式的。
var obj={
a:1,
b:2,
c:3,
d:[1],
}
Object.keys(obj).forEach(key=>{
let value=obj[key];
Object.defineProperty(obj,key,{
get(){
console.log(`獲取obj${key}值${value}`); //獲取obja值1
return value;
},
set(newValue){
console.log(`obj 中${key} 被賦值 ${newValue}`) //obj 中b 被賦值 3
}
})
})
obj.a;
obj.b=3
obj.d.push(2); //不能對(duì)push 方法進(jìn)行數(shù)據(jù)劫持
在上述實(shí)例 我們發(fā)現(xiàn)可以通過(guò)Object.defineProperty對(duì)數(shù)據(jù)進(jìn)行劫持,進(jìn)行響應(yīng)式操作梳理,但也有如下問(wèn)題。
如果對(duì)象事多層嵌套,需要進(jìn)行遞歸處理,否則不能監(jiān)聽(tīng)到數(shù)據(jù)的更新。所以我們?cè)陂_(kāi)發(fā)中避免在 data中數(shù)據(jù)層級(jí)太深,影響性能。
對(duì)數(shù)組中的方法不能進(jìn)行數(shù)據(jù)劫持。
push,pop,unshift,shift,splice,reverse,sort,需要進(jìn)行特殊處理。
實(shí)現(xiàn)
Observer
/**
* 數(shù)據(jù)觀測(cè)
*/
import {isObject} from '../util/index.js'
import {newArrayProto} from './array'
import Dep from './dep.js'
class Observer{
constructor(data)
{
//將 this 掛載在data 上 可以使用ob 調(diào)用方法
Object.defineProperty(data,'_ob_',{
enumerable:false,
configurable:false,
value:this
})
if(data instanceof Array)
{
//數(shù)組是 [].__proto__=Array.prototype
//更改需要觀測(cè)數(shù)組的原型鏈
data.__proto__=newArrayProto;
this.observeArray(data);
}
else{
//監(jiān)測(cè)對(duì)象
this.walk(data);
}
}
/**
* 觀測(cè)數(shù)組
* @param {*} data
*/
observeArray(data){
for(let i=0;i<data.length;i++)
{
observe(data[i])
}
}
/**
* 遍歷監(jiān)測(cè)對(duì)象
* @param {*} data
*/
walk(data){
// Object.keys 不可遍歷不可枚舉類(lèi)型 所以 _ob_ 不會(huì)被遍歷
Object.keys(data).forEach(key=>{
defineReactive(data,key,data[key])
})
}
}
/**
* 數(shù)據(jù)監(jiān)測(cè)
* @param {*} data
* @param {*} key
* @param {*} value
*/
function defineReactive(data,key,value){
let dep=new Dep();
observe(value);// value 還是對(duì)象,遞歸
Object.defineProperty(data,key,{
get(){
return value;
},
set(newValue){
if(newValue===value) return;
//對(duì)于賦值的如果是對(duì)象 進(jìn)行響應(yīng)式監(jiān)測(cè)
observe(newValue);
value=newValue;
}
})
}
/**
* 觀測(cè)數(shù)據(jù)方法
* @param {*} data
*/
export function observe(data)
{
//不是對(duì)象
if(!isObject(data)) return;
//說(shuō)明已經(jīng)被觀測(cè)
if(data._ob_ instanceof Observer) return;
return new Observer(data);
}
注意
- 如果觀察對(duì)象還是一個(gè)對(duì)象,需要遞歸進(jìn)行observe。
- 觀測(cè)后該對(duì)象加上ob標(biāo)記,指向當(dāng)前class Observer的實(shí)例。既可以方便調(diào)用實(shí)例中的方法,又可以標(biāo)識(shí)當(dāng)前對(duì)象已經(jīng)被觀測(cè),避免重復(fù)觀測(cè)。
- 如果檢測(cè)到數(shù)據(jù)類(lèi)型是數(shù)組,修改被觀測(cè)數(shù)組上的原型鏈,具體方法詳看如下代碼
數(shù)組響應(yīng)式處理
export let newArrayProto=Object.create(Array.prototype);
let oldMethods=Array.prototype;
//需要重寫(xiě)數(shù)組的方法
let methods=[
'push',
'pop',
'unshift',
'shift',
'splice',
'sort',
'reverce'
];
methods.forEach((method)=>{
newArrayProto[method]=function(...args){
//依然執(zhí)行原數(shù)組原型上的方法
let result= oldMethods[method].call(this,...args);
let insered=null,//新增的元素
ob=this._ob_
switch(method){
case 'push':
case 'unshift':
insered=args;
break;
case 'splice':
insered=args.splice(2);
break;
default:
break;
}
//對(duì)于新增元素進(jìn)行觀測(cè)
insered && ob.observeArray(insered);
return result;
}
})
思路
-
創(chuàng)建新的數(shù)組原型對(duì)象。
let newArrayProto=Object.create(Array.prototype); 重寫(xiě)原型對(duì)象的7個(gè)方法(
push,pop,unshift,shift,splice,reverse,sort),這七個(gè)方法執(zhí)行時(shí)還是要調(diào)用數(shù)組原型上的方法,同時(shí)
也可以實(shí)現(xiàn)觸發(fā)這七個(gè)方法,觸發(fā)更新。需要注意的是push,unshift,splice三個(gè)方法會(huì)新增數(shù)組數(shù)據(jù),因此也要對(duì)新增數(shù)據(jù)進(jìn)行響應(yīng)式觀測(cè)。修改數(shù)組原型鏈
if(data instanceof Array)
{
//數(shù)組是 [].__proto__=Array.prototype
//更改需要觀測(cè)數(shù)組的原型鏈
data.__proto__=newArrayProto;
this.observeArray(data);
}
調(diào)用 initState
在是生命周期 beforeCreate 和 created之間調(diào)用,進(jìn)行數(shù)據(jù)響應(yīng)式處理,然后再進(jìn)行模板編譯和掛載($mount),下一節(jié)總結(jié)在Vue.prototype.mount 實(shí)現(xiàn)的功能。
Vue.prototype._init=function(options){
const vm=this;
//將參數(shù)掛載到 vm 上
vm.$options = mergeOptions(vm.constructor.options,options);
callHook(vm,'beforeCreate');
initState(vm);
callHook(vm,'created');
if(vm.$options.el)
{
this.$mount(vm.$options.el);
}
}
github地址
https://github.com/yuxuewen/vue2.0_source.git
結(jié)語(yǔ)
以上是總結(jié)的vue2.0響應(yīng)式原理的實(shí)現(xiàn),熟悉源碼并非是真正自己去實(shí)現(xiàn)一個(gè)Vue,而是通過(guò)作者的設(shè)計(jì)思路來(lái)拓展我們的視野,對(duì)平時(shí)開(kāi)發(fā)有很大意義。本人前端小白,如果錯(cuò)誤,請(qǐng)諒解,并歡迎批評(píng)指正。
掘金地址:https://juejin.im/user/5efd45a1f265da22f511c7f3/posts