本次任務css
讓咱們一步一步來作出這樣一個'loader', 首先我先介紹一下文件結構, 在'src'文件夾平級創建'loader'文件夾, 裏面能夠存放之後我作的全部'loader'.html
不可或缺的步驟就是定義'loader'的路徑.
cc_vue/config/common.js
新增resolveLoader項vue
resolveLoader: { // 方式1: // 若是我書寫 require('ccloader'); // 那麼就會去 path.resolve(__dirname, '../loader')尋找這個引入. alias: { ccloader: path.resolve(__dirname, '../loader') }, // 方式2: 當存在require('xxx');這種寫法時, 先去'node_modules'找尋, 找不到再去path.resolve(__dirname,'../loader')找找看. modules:[ 'node_modules', path.resolve(__dirname,'../loader') ] },
'loader'文件的配置寫完了, 那麼能夠開始正式寫這個'loader'了.
cc_vue/loader/cc-loader.jsnode
// 1: 'source'就是你讀取到的文件的代碼, 他是'string'類型. function loader(source) { // ..具體處理函數 // 2: 處理完了必定要把它返回出去, 由於可能還有其餘'loader'要處理它, 或者是直接執行處理好的代碼. return source; } module.exports = loader;
模板的定義webpack
<!DOCTYPE html> <html lang='en'> <head> <meta charset='UTF-8' /> <meta name='viewport' content='width=device-width, initial-scale=1.0' /> <meta http-equiv='X-UA-Compatible' content='ie=edge' /> <title>編譯模板</title> #--style--> </head> <body> <div id="app"> #--template--> </div> </body> </html>
最終要達到的'.cc'文件的書寫方式git
<template> <div class="box" v-on:click='add'> <span>{{n}}</span> </div> </template> <script> console.log('此處也可執行代碼'); export default { el: "#app", data: { n: 2 }, methods: { add() { this.n++; } } }; </script> <style> .box { border: 1px solid; height: 600px; } </style>
讀取'.cc'文件
cc_vue/config/common.jsgithub
{ test: /\.cc$/, use: ['cc-loader'] },
loader正式走起
cc_vue/loader/cc-loader.jsweb
// 解析cc文件模板 // fs模塊主要用來讀寫文件 let fs = require('fs'); let path = require('path'); function loader(source) { // 1: 咱們先把定義好的'模板文件'讀取出來. let template = fs.readFileSync( path.resolve(__dirname, './index.html'), 'utf8' // 可能存在中文的 ); // 2: 去除空格, 這樣能更好的匹配... source = source.replace(/\s+/g, ' '); // 3: 匹配出'css'樣式 let s = (/<style>(.*)<\/style>/gm.exec(source)||[])[1]; // 4: 匹配出js代碼 let j = /<script>(.*)<\/script>/gm.exec(source)[1]; // 5: 匹配出模板元素 let t = /<template>(.*)<\/template>/gm.exec(source)[1]; // 6: 注入模板元素 template = template.replace(/(#--template-->)/, t); // 7: 注入樣式, 防止出現undefined啥的... template = template.replace(/(#--style-->)/, `<style> ${s||''}</style>`); // 8: 把這個處理好的模板結構放入最後要執行的'html'文件中 fs.writeFileSync( path.resolve(__dirname, '../public/index.html'), `${template}`, err => console.log(err) ); // 9: 這裏咱們把'js'代碼繼續導出, 這樣其餘文件引入咱們'.cc'文件其實就是引入了'.cc'文件的'js'腳本. return j; } module.exports = loader;
總體來講仍是挺簡易的, 剛開始作的時候想複雜了, 接下來咱們就來引用它.
cc_vue/src/index.js面試
import component from '../use/5:loader相助/index.cc'; //... // 由於最後我導出的只有'js'腳本, 也就是'new'的時候的配置項, 因此直接進行下面的操做就能夠. new C(component);
生命週期這種東西面試總考, 可是說實話沒有多神祕, 核心就是個回調函數而已.
本次我就作兩個生命週期, 定義以下.segmentfault
cc_vue/src/Compiler.js
class Compiler { constructor(el = '#app', vm) { this.vm = vm; // 1: 拿到真正的dom this.el = this.isElementNode(el) ? el : document.querySelector(el); // 2: 製做文檔碎片 let fragment = this.node2fragment(this.el); // 3: 解析元素, 文檔流也是對象 this.compile(fragment); // 4: 進行生命週期函數, 他真的一點都不高大上 vm.$created && vm.$created.call(vm); // 最後一步: 處理完再放回去 this.el.appendChild(fragment); // 調用聲明週期鉤子 vm.$mounted && vm.$mounted.call(vm); } //...
cc_vue/use/5:loader相助/index.cc
完整的測試一下
<template> <div class="box" v-on:click='add'> <span>{{n}}</span> </div> </template> <script> console.log('此處也可執行代碼'); export default { el: "#app", data: { n: 2 }, methods: { add() { this.n++; } }, created() { let d = document.getElementsByClassName("box"); console.log("created", this.n, d); }, mounted() { let d = document.getElementsByClassName("box"); console.log("mounted", this.n, d); } }; </script> <style> .box { border: 1px solid; height: 600px; } </style>
有興趣的朋友能夠測試一下, 本身作這些東西真的通有趣的.
計算屬性屬於很經常使用的功能了, 他的神奇之處在於其中任何一個值的變化都會引發結果的同步更新,下面我就來實現這種看起來很棒的效果.
cc_vue/src/index.js
class C { constructor(options) { //... // 把$computed代理到vm身上 this.proxyVm(this.$computed, this, true);
具體的代理過程須要有所調整
proxyVm(data = {}, target = this, noRepeat = false) { for (let key in data) { if (noRepeat && target[key]) { // 防止data裏面的變量名與其餘屬性重複 throw Error(`變量名${key}重複`); } Reflect.defineProperty(target, key, { enumerable: true, // 描述屬性是否會出如今for in 或者 Object.keys()的遍歷中 configurable: true, // 描述屬性是否配置,以及能否刪除 get() { // 初版 // 計算屬性上的值確定是函數啊, 因此這裏要進行一下判斷 // 由於這個for只走一層, 因此不會出現與內部值'重疊'的現象 // 每次把this指向糾正 if (this.$computed && this.$computed.hasOwnProperty(key)) { return data[key].call(target); } else { return Reflect.get(data, key); } // 第二版, f是新傳進來的變量, 表明是否是函數類型 return f ? data[key].call(target) : Reflect.get(data, key); }, set(newVal) { if (newVal !== data[key]) { Reflect.set(data, key, newVal); } } }); } }
我說下原理
下面是個人測試代碼
<template> <div class="box"> <button v-on:click='addn'> n++</button> <button v-on:click='addm'> m++</button> <p>n: {{n}}</p> <p>m: {{m}}</p> <p>x: {{x}}</p> <p>n+m+x: {{v}}</p> <p>v+v: {{v+v}}</p> </div> </template> <script> export default { data: { n: 1, m: 1, x: 1 }, methods: { addn() { this.n++; console.log(this.v) }, addm() { this.m++; } }, computed: { v() { return this.n + this.m + this.x; } } }; </script>
既然寫了'計算屬性'那就順手把觀察屬性一併完成把, 這個功能也挺有意思的, 咱們可使用這個屬性對一個量進行'觀察', 當這個量變化的時候觸發咱們的函數, 同時傳入兩個參數新值與老值.
我說下思路:
cc_vue/src/Observer.js
//... // 新增init變量, 用來區別是否是第一層數據 // data = {name:'cc',type:['金毛','胖子']}; // name屬於第一層數據, '金毛'屬於第二層數據 observer(data, init = false) { let type = toString.call(data), $data = this.defineReactive(data, init); //...
defineReactive(data, init) { //... set(target, key, value) { if (target[key] !== value) { if (init) { // 對data的數據進行watch處理 (_this.vm.$watch||{})[key] && // 先肯定有watch _this.vm.$watch[key].call(_this, value, target[key]); } target[key] = _this.observer(value); dep.notify(); } return value; }
本次書寫的功能都挺有意思的, 寫的時候也很開心, 畢竟代碼是讓人快樂的東西, 下一期我要往框架裏面加一些好玩的功能, 具體加什麼還沒肯定, 可是我比較喜歡一些搞怪的, 反正你們一塊兒玩耍唄.