├── 01-tools.md ├── 02-defineReactive$1.md ├── 03-strats.md ├── 04-VNode.md ├── 05-initLifecycle.md ├── 06-Watcher.md ├── 07-initState.md ├── 08-component&props.md ├── 09-buildInComponent.md ├── 10-_init.md ├── 11-check&opts.md ├── 12-VirtualDom_algorithm.md ├── 13-directives.md ├── 14-gen.md ├── 15-event&style&class.md ├── 16-transition.md ├── 17-parseHTML.md ├── 18-walkAST.md ├── 19-Omega.md └── README.md /01-tools.md: -------------------------------------------------------------------------------- 1 | # 随便写写/一些方法 2 | 3 | 4 | 5 | 6 | 7 | ## RegExp 8 | 9 | 10 | 11 | ### camelize:驼峰命名 12 | 13 | ### capitalize:首字母大写 14 | 15 | ### hyphenate:反驼峰命名 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | --- 26 | 27 | 28 | 29 | 30 | 31 | ## fn 32 | 33 | 34 | 35 | ### makeMap 36 | 37 | 38 | 39 | ```javascript 40 | //功能函数 匹配指定字符串 41 | //例: var isBuiltInTag = makeMap('slot,component',true) 42 | //解析后 list = [slot,component] => map = {slot:true,component:true} 43 | //返回 function(val){return map[val]} 44 | //此函数若传入slot,component会返回true 45 | function makeMap(str,expectsLowercase){ 46 | //创建一个无原型对象 47 | var map = Object.create(null); 48 | var list = str.splite(","); 49 | for(Var i=0;i fn("sss") => Sss 回调函数调用参数字符串实现首字母大写 74 | function cached(fn){ 75 | var cache = Object.create(null); 76 | return (function cachedFn(str){ 77 | //尝试读取缓存 78 | var hit = cache[str]; 79 | //返回缓存 或者调用fn(str)并返回缓存 80 | return hit || (cache[str] = fn(str)); 81 | }); 82 | } 83 | ``` 84 | 85 | 86 | 87 | --- 88 | 89 | 90 | 91 | ### bind$1 92 | 93 | 94 | 95 | ```javascript 96 | //强绑函数上下文 97 | //函数柯里化 98 | //例:var fn_bind = bind$1(fn,ctx); 99 | //此时 fn_bind = function(a){...}; 100 | //调用 fn_bind(...)时 根据传入的参数数量进行强绑 101 | function bind$1(fn,ctx){ 102 | function boundFn(a){ 103 | var l = arguments.length; 104 | //如果未传参 => fn.call(ctx) 105 | //传入一个参数 => fn.call(ctx,a) 106 | //传入多个参数 => fn.apply(ctx,arguments) 107 | return l ? (l > 1 ? fn.apply(ctx,arguments) : fn.call(ctx,a)) :fn.call(ctx); 108 | } 109 | boundFn._length = fn.length; 110 | return boundFn; 111 | } 112 | ``` 113 | 114 | 115 | 116 | --- 117 | 118 | 119 | 120 | ### toArray 121 | 122 | 123 | 124 | ```javascript 125 | // 类数组 => 数组 126 | // list:目标类数组 start:开始位置 127 | function toArray(list,start){ 128 | start = start || 0 ; 129 | var i = list.length - start; 130 | var ret = new Array(i); 131 | while(i--){ 132 | ret[i] = list[i + start]; 133 | } 134 | return ret; 135 | } 136 | ``` 137 | 138 | 139 | 140 | --- 141 | 142 | 143 | 144 | ### extend 145 | 146 | 147 | 148 | ```javascript 149 | //对象混入 复制键值对 重复的覆盖 150 | function extentd(to,_from){ 151 | for(var key in _form){ 152 | to[key] = _form[key]; 153 | } 154 | return to; 155 | } 156 | ``` 157 | 158 | 159 | 160 | --- 161 | 162 | 163 | 164 | ### parsePath 165 | 166 | 167 | 168 | ```javascript 169 | //以点结尾的路径为错误格式 170 | var bailRE = /[^\w.$]/; 171 | function parsePath(path){ 172 | if(bailRE).test(path){ 173 | return 174 | } 175 | else{ 176 | //分割路径 www.baidu.com => [www,baidu,com] 177 | var segments = path.split('.'); 178 | return function(obj){ 179 | for(var i=0;i 获取浏览器信息 23 | // Google Chrome返回下列字符串 24 | // Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36 25 | // IE8返回下列字符串 26 | // Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Win64; x64; Trident/4.0; .NET CLR 2.0.50727; SLCC2; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET4.0C; .NET4.0E; InfoPath.3) 27 | var UA = inBrowser && window.navigator.userAgent.toLowerCase(); 28 | ``` 29 | 30 | 31 | 32 | #### IE 33 | 34 | 35 | 36 | ```javascript 37 | // 判断是否有msie或trident字符串 38 | var isIE = UA && /msie|trident/.test(UA); 39 | ``` 40 | 41 | 42 | 43 | #### IE9 44 | 45 | 46 | 47 | ```javascript 48 | // 判断 msie 9.0 49 | var isIE9 = UA && UA.indexOf('msie 9.0') > 0; 50 | ``` 51 | 52 | 53 | 54 | #### Edge 55 | 56 | 57 | 58 | ```javascript 59 | // 判断 edge/ 60 | var isEdge = UA && UA.indexOf('edge/') > 0; 61 | ``` 62 | 63 | 64 | 65 | #### Android 66 | 67 | 68 | 69 | ```javascript 70 | // 判断 android 71 | var isAndroid = UA && UA.indexOf('android') > 0; 72 | ``` 73 | 74 | 75 | 76 | #### IOS 77 | 78 | 79 | 80 | ```javascript 81 | // 判断 iphone,ipad,ipod,ios 82 | var isIOS = UA && /iphone|ipad|ipod|ios/.test(UA); 83 | ``` 84 | 85 | 86 | 87 | --- 88 | 89 | 90 | 91 | 92 | 93 | ## fn 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | ### nextTick(异步执行器) 102 | 103 | 104 | 105 | ```javascript 106 | // 异步方法优先级 Promise => MutationObserver => setTimeout 107 | var nextTick = (function(){ 108 | var callback = [], // 执行函数队列 109 | pending = false, // 是否执行中 110 | timerFunc; // 异步执行函数 111 | function nextTickHandler(){ 112 | pending = false; 113 | // 拷贝函数队列 114 | var copies = callback.slice(0); 115 | // 重置函数队列 116 | callbacks.length = 0; 117 | // 依次执行函数 118 | for(var i = 0; i < copies.length; i++){ 119 | copies[i](); 120 | } 121 | } 122 | 123 | // Promise已定义且是内置方法 124 | if(typeof Promise !== 'undefined' && isNative(Promise)){ 125 | //决议一个promise对象 126 | var p = Promise.resolve(); 127 | var logError = function(err){console.log(err);}; 128 | timeFunc = function(){ 129 | p.then(nextTickHandler).catch(logError); 130 | // IOS有BUG 调用Promise.then不会完成调用 传空定时器可以解决 131 | if(isIOS){setTimeout(noop);} 132 | }; 133 | } 134 | 135 | // 使用MutationObserver 136 | // 该对象是H5新特性 在DOM更新完触发 仅触发一次 137 | else if(typeof MutationObserver !== 'undefined' && 138 | (isNative(MutationObserver) || 139 | // IOS 7.x 140 | MutationObserver.toString() === '[object MutationObserverConstructor]')){ 141 | var counter = 1; 142 | var observer = new MutationObserver(nextTickHandler); 143 | var textNode = document.createTextNode(String(counter)); 144 | observer.observer(textNode,{ 145 | characterData: true 146 | }); 147 | timerFunc = function(){ 148 | //这个方法会手动触发DOM更新事件 149 | counter = (counter + 1) % 2; 150 | textNode.data = String(counter); 151 | }; 152 | } 153 | 154 | // 都不行就用setTimeout()方法 155 | else{ 156 | timerFunc = function(){ 157 | setTimeout(nextTickHandler,0); 158 | }; 159 | } 160 | // 自执行 返回这个函数 161 | // 接受两个参数 函数与上下文 162 | return function queueNextTick(cb,ctx){ 163 | var _resolve; 164 | // 定义函数队列 165 | callback.push(function(){ 166 | if(cb){cb.call(ctx);} 167 | if(_resolve){_resolve(ctx);} 168 | }); 169 | if(!pending){ 170 | pending = true; 171 | // 一开始觉得这里会执行 后面的if会忽略 172 | // 然而promise是microtask 此时主线程没跑完 所以promise挂起 173 | timerFunc(); 174 | } 175 | // 这里应对不传参数的情况 决议一个空promise 176 | if(!cb && typeof Promise !== 'undefined'){ 177 | return new Promise(function(resolve){ 178 | _resolve = resolve; 179 | }); 180 | } 181 | } 182 | })(); 183 | ``` 184 | 185 | 186 | 187 | --- 188 | 189 | 190 | 191 | ### Set(图) 192 | 193 | 194 | 195 | ```javascript 196 | // ES6新类型Set 197 | // 成员唯一 加入值不会进行类型转换 198 | var _Set; 199 | // 优先使用原生Set方法 200 | if(typeof Set !== 'undefined' && isNative(Set)){ 201 | _Set = Set; 202 | } 203 | // 造一个Set 只支持基本类型的key 204 | else{ 205 | _Set = (function(){ 206 | // 生成无原型对象 207 | function Set(){ 208 | this.set = Object.create(null); 209 | } 210 | // 判断是否有对应的键 211 | Set.prototype.has = function has(key){ 212 | return this.set[key] === true; 213 | }; 214 | // 添加 key:true 215 | Set.prototype.add = function add(key){ 216 | this.set[key] = true; 217 | }; 218 | // 清空Set 219 | Set.prototype.clear = function clear(){ 220 | this.set = Object.create(null); 221 | }; 222 | return Set; 223 | }()) 224 | } 225 | ``` 226 | 227 | 228 | 229 | --- 230 | 231 | 232 | 233 | ### Dep(监视器) 234 | 235 | 236 | 237 | ```javascript 238 | // 全局变量 239 | var uid$1 = 0; 240 | 241 | // 构造函数 里面只有一个数组 装依赖对象 242 | // 每生成一个Dep对象 属性id+1 243 | var Dep = function Dep(){ 244 | this.id = uid$1++; 245 | this.subs = []; 246 | } 247 | // 全局监视器对象 248 | // 同一时间只有一个监视器 249 | Dep.target = null; 250 | // 储存监视对象的栈 251 | var targetStack = []; 252 | 253 | // 弹入依赖项 并将该项设为当前监视值 254 | function pushTarget(_target){ 255 | if(Dep.target){ 256 | targetStack.push(Dep.target); 257 | } 258 | Dep.target = _target; 259 | } 260 | 261 | // 弹出一个依赖项 并设监视值 262 | function popTarget(){ 263 | Dep.target = targetStack.pop(); 264 | } 265 | ``` 266 | 267 | 268 | 269 | #### addSub 270 | 271 | 272 | 273 | ```javascript 274 | Dep.prototype.addSub = function addSub(sub){ 275 | // 弹入元素 276 | this.subs.push(sub); 277 | }; 278 | ``` 279 | 280 | 281 | 282 | #### removeSub 283 | 284 | 285 | 286 | ```javascript 287 | Dep.prototype.removeSub = function removeSub(sub){ 288 | // 删除数组中对应元素 289 | remove$1(this.subs,sub); 290 | } 291 | ``` 292 | 293 | 294 | 295 | #### depend 296 | 297 | 298 | 299 | ```javascript 300 | Dep.prototype.depend = function depend(sub){ 301 | // 如果有依赖对象 添加依赖 302 | if(Dep.target){ 303 | Dep.target.addDep(this); 304 | } 305 | }; 306 | ``` 307 | 308 | 309 | 310 | #### dependArray 311 | 312 | 313 | 314 | ```javascript 315 | function dependArray(value) { 316 | for (var e = (void 0), i = 0, l = value.length; i < l; i++) { 317 | e = value[i]; 318 | e && e.__ob__ && e.__ob__.dep.depend(); 319 | if (Array.isArray(e)) { 320 | dependArray(e); 321 | } 322 | } 323 | } 324 | ``` 325 | 326 | 327 | 328 | #### notify 329 | 330 | 331 | 332 | ```javascript 333 | // 通知所有依赖项 334 | Dep.prototype.notify = function notify(){ 335 | var subs = this.subs.slice(); 336 | for(var i = 0, l = subs.length; i < l; i++){ 337 | subs[i].update(); 338 | } 339 | }; 340 | ``` 341 | 342 | 343 | 344 | --- 345 | 346 | 347 | 348 | ### def 349 | 350 | 351 | 352 | ```javascript 353 | // 对象 键 值 是否可枚举 354 | function def(obj, key, val, enumerable) { 355 | Object.defineProperty(obj, key, { 356 | value: val, 357 | enumerable: !!enumerable, 358 | writable: true, 359 | configurable: true 360 | }); 361 | } 362 | ``` 363 | 364 | 365 | 366 | --- 367 | 368 | 369 | 370 | ### arrayProto 371 | 372 | 373 | 374 | ```javascript 375 | var arrayProto = Array.prototype; 376 | // 构建一个以数组为原型的对象 即arrayMethods.__proto__ => Array.prototype 377 | // 保留对象属性 并拥有数组的方法 378 | // 强 无敌 379 | var arrayMethods = Object.create(arrayProto); 380 | ['push','pop','shift','unshift','splice','sort','reverse'].forEach(function(method){ 381 | // 缓存原生方法 比如function push(){[native code]} 382 | var original = arrayProto[method]; 383 | // 调用Object.defineProperty定义对象 可枚举默认为false 384 | // Object.defineProperty(obj,key,{value:val}) 385 | // 函数def() => arrayMethods = {method:fn(){}} 386 | def(arrayMethods,method,function mutator(){ 387 | // 先将参数列表转换成数组 388 | var arguments$1 = arguments; 389 | var i = arguments.length; 390 | var args = new Array(i); 391 | while(i--){ 392 | args[i] = arguments$1[i]; 393 | } 394 | 395 | //调用数组原生方法并获得结果 396 | var result = original.apply(this,args); 397 | // this指向 398 | var ob = this.__ob__; 399 | var inserted; 400 | // 弹入一个元素时 标记为插入元素 401 | switch(method){ 402 | case 'push': 403 | case 'unshift': 404 | inserted = args; 405 | break; 406 | case 'splice': 407 | // splice(index,del_num,...,insert_data) 第三个参数开始才是插入的值 408 | inserted = args.slice(2); 409 | break; 410 | } 411 | // 如果有插入元素 对每个元素调用observe()方法 412 | if(inserted){ 413 | ob.observeArray(inserted); 414 | } 415 | // 广播变化 416 | ob.dep.notify(); 417 | return result; 418 | }); 419 | }); 420 | ``` 421 | 422 | 423 | 424 | --- 425 | 426 | 427 | 428 | ### Observer(观察者) 429 | 430 | 431 | 432 | ```javascript 433 | var arrayKeys = Object.getOwnPropertyNames(arrayMethods); 434 | 435 | // 组件单向数据流 436 | // 默认设为false 437 | var observerState = { 438 | shouldConvert: true, 439 | isSettingProps: false 440 | }; 441 | 442 | // 观测者构造函数 443 | // value为data对应的对象 假设为{msg:'Hello World'} 444 | var Observer = function Observer(value){ 445 | this.value = value; 446 | // 生成一个新监视器 447 | this.dep = new Dep(); 448 | this.vmCount = 0; 449 | // 此处value为传进来的参数 450 | // 给value对象绑定属性__ob__绑定自己... 451 | // value:{__ob__:this}不可枚举 452 | def(value,'__ob__',this); 453 | // 总结一下目前的this 454 | // this={dep:{id:0,subs:[]}, 455 | // value:{__ob__:this,msg:'Hello World'}, 456 | // vmCount:0} 457 | 458 | if(Array.isArray(value)){ 459 | // hasProto = '__proto__' in {} 能否使用__proto__ 460 | var augment = hasProto ? 461 | protoAugment : copyAugment; 462 | // 可用__proto__就构造原型链 value => {} =>Array.prototype 463 | // arrayKeys = Object.getOwnPropertyNames(arrayMethods) 464 | // 获取数组方法名 ['push','pop'...] 465 | // 不可用__proto__进行方法强绑def(target, key, src[key]) 466 | augment(value,arrayMethods,arrayKeys); 467 | // 遍历数组元素调用observe() 468 | this.observeArray(value); 469 | } 470 | // 对象 471 | else{ 472 | // 调用自定义的defineReactive$$1(obj,key,value)动态添加属性 473 | // value:{msg:'Hello World'} 474 | this.walk(value); 475 | } 476 | }; 477 | ``` 478 | 479 | 480 | 481 | #### walk 482 | 483 | 484 | 485 | ```javascript 486 | Observer.prototype.walk = function walk(obj){ 487 | // Object.keys()返回一个由键组成的数组 488 | var keys = Object.keys(obj); 489 | for(var i = 0; i < keys.length; i++){ 490 | // 挨个对值进行动态绑定 491 | defineReactive$$1(obj, key[i], obj[keys[i]]); 492 | } 493 | }; 494 | ``` 495 | 496 | 497 | 498 | #### observeArray 499 | 500 | 501 | 502 | ```javascript 503 | // 监视一个数组 504 | Observer.prototype.observeArray = function observerArray(items){ 505 | for(var i = 0, l = items.length; i < l; i++){ 506 | observe(items[i]); 507 | } 508 | }; 509 | ``` 510 | 511 | 512 | 513 | #### protoAugment / copyAugment 514 | 515 | 516 | 517 | ```javascript 518 | // 通过__proto__扩展原型链 519 | function protoAugment(target, src){ 520 | target.__proto__ = src; 521 | } 522 | 523 | // 通过别的对象扩展方法 524 | function copyAugment(target, src, keys){ 525 | for(var i = 0; l = keys.length; i < l; i++){ 526 | var key = keys[i]; 527 | // target:{key:src[key]} 528 | def(target, key, src[key]); 529 | } 530 | } 531 | ``` 532 | 533 | 534 | 535 | #### observe(核心方法) 536 | 537 | 538 | 539 | ```javascript 540 | function observe(value, asRootData){ 541 | // 只接受对象 542 | if(!isObject(value)){ 543 | return ; 544 | } 545 | var ob; 546 | if(hasOwn(value, '__ob__') && value.__ob__ instanceof Observer){ 547 | ob = value.__ob__; 548 | } 549 | // 如果没有__ob__属性 new一个 550 | else if( 551 | observerState.shouldConvert && !isServerRendering() && 552 | (Array.isArray(value) || isPlainObject(value)) && 553 | Object.isExtensible(value) && !value._isVue){ 554 | ob = new Observer(value); 555 | } 556 | if(asRootData && ob){ 557 | ob.vmCount++; 558 | } 559 | return ob; 560 | } 561 | ``` 562 | 563 | 564 | 565 | --- 566 | 567 | 568 | 569 | ### defineReactive$1 570 | 571 | 572 | 573 | ```javascript 574 | // 动态添加属性 575 | // 继续假设obj={msg:'Hello World'} key='msg' val='Hello World' 576 | function defineReactive$$1(obj,key,val,customSetter){ 577 | // 这里又生成一个新的 {id:1, sub:[]} 578 | var dep = new Dep(); 579 | // Object.getOwnPropertyDescriptor(obj,key)方法以对象形式返回对象的四个特殊属性 580 | var property = Object.getOwnPropertyDescriptor(obj,key); 581 | // 不存在或者不可更改 返回 582 | if(property && property.configurable === false){ 583 | return ; 584 | } 585 | // 获取值的get/set 586 | var getter = property && property.get; 587 | var setter = property && property.set; 588 | 589 | // val = 'Hello World' 590 | // 只接受对象 对子对象进行递归 591 | var childOb = observe(val); 592 | Object.defineProperty(obj,key,{ 593 | enumerable: true, 594 | configurable: true, 595 | get: function reactiveGetter(){ 596 | var value = getter ? getter.call(obj) : val; 597 | if(Dep.target){ 598 | // 添加依赖 599 | dep.depend(); 600 | // 子对象 601 | if(childOb){ 602 | childOb.dep.depend(); 603 | } 604 | // 数组值 605 | if(Array.isArray(value)){ 606 | dependArray(value); 607 | } 608 | } 609 | return value; 610 | }, 611 | set: function reactiveSetter(newVal){ 612 | var value = getter ? getter.call(obj) : val; 613 | if(newVal === value || (newVal !== newVal && value !== value)){ 614 | return ; 615 | } 616 | if('development' !== 'production' && customSetter){ 617 | customSetter(); 618 | } 619 | if(setter){ 620 | setter.call(obj,newVal); 621 | } 622 | else{ 623 | val = newVal; 624 | } 625 | childOb = observe(newVal); 626 | dep.notify(); 627 | } 628 | }); 629 | } 630 | ``` 631 | 632 | 633 | 634 | --- 635 | 636 | 637 | 638 | ### set$1 639 | 640 | 641 | 642 | ```javascript 643 | // 为一个对象设置一个属性 属性不存在时添加并触发监视器 644 | function set$1(obj,key,val){ 645 | // 数组 646 | if(Array.isArray(obj)){ 647 | // 1.如果obj比较长 替换key位置的value 648 | // 2.如果key比length长 649 | obj.length = Math.max(obj.length,key); 650 | obj.splice(key,1,val); 651 | return val; 652 | } 653 | if(hasOwn(obj,key)){ 654 | obj[key] = val; 655 | return ; 656 | } 657 | var ob = obj.__ob__; 658 | if(obj._isVue || (ob && ob.vmCount)){ 659 | 'development' !== 'production' && warn('Avoid adding reactive properties to a Vue instance or its root $data at runtime - declare it upfront in the data option.'); 660 | return ; 661 | } 662 | if(!ob){ 663 | obj[key] = val; 664 | return; 665 | } 666 | defineReactive$$1(ob.value,key,val); 667 | ob.dep.notify(); 668 | return val; 669 | } 670 | ``` 671 | 672 | 673 | 674 | --- 675 | 676 | 677 | 678 | ### del 679 | 680 | 681 | 682 | ```javascript 683 | function del(obj,key){ 684 | var ob = obj.__ob__; 685 | if(obj._isVue || (ob && ob.vmCount)){ 686 | 'development' !== 'prodution' && warn('Avoid deleting properties on a Vue instance or its root $data - just set it to null.'); 687 | return ; 688 | } 689 | if(!hasOwn(obj,key)){ 690 | return; 691 | } 692 | delete obj[key]; 693 | if(!ob){ 694 | return; 695 | } 696 | ob.dep.notify(); 697 | } 698 | ``` 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | -------------------------------------------------------------------------------- /03-strats.md: -------------------------------------------------------------------------------- 1 | # 不知道定什么标题 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ## strats 10 | 11 | 12 | 13 | ```javascript 14 | var strats = config.optionMergeStrategies; 15 | 16 | { 17 | strats.el = strats.propsData = function(parent, child, vm, key){ 18 | if(!vm){ 19 | warn( 20 | 'option /"' + key + "\" can only be used during instance " + 21 | 'creation with the `new` keyword.' 22 | ); 23 | } 24 | return defaultStart(parent, child); 25 | }; 26 | } 27 | ``` 28 | 29 | 30 | 31 | ### mergeData 32 | 33 | 34 | 35 | ```javascript 36 | // 两个对象 form => to 37 | function mergeData(to,from){ 38 | if(!from){return to} 39 | var key, toVal, fromVal; 40 | var keys = Object.keys(from); 41 | for(var i = 0; i < keys.length; i++){ 42 | key = keys[i]; 43 | toVal = to[key]; 44 | fromVal = from[key]; 45 | // hasOwnProperty.call(to,key) 46 | // 如果to中没有key键 动态添加属性 47 | if(!hasOwn(to,key)){ 48 | set$1(to, key, fromVal); 49 | } 50 | // 如果值是对象 递归解析 51 | else if(isPlainObject(toVal) && isPlainObject(fromVal)){ 52 | mergeData(toVal, fromVal); 53 | } 54 | } 55 | return to; 56 | } 57 | ``` 58 | 59 | 60 | 61 | ### strats.data 62 | 63 | 64 | 65 | ```javascript 66 | strats.data = function(parentVal, childVal, vm){ 67 | if(!vm){ 68 | if(!childVal){ 69 | return parentVal; 70 | } 71 | // 组件的data定义必须为函数 72 | // 否则所有组件共享一个对象 73 | if(typeof childVal !== 'function'){ 74 | 'development' !== 'production' && warn( 75 | 'The "data" option should be a function ' + 76 | 'that return a per-instance value in component ' + 77 | 'definitions.',vm); 78 | return parentVal; 79 | } 80 | // 未提供父元素值 81 | if(!parentVal){ 82 | return childVal; 83 | } 84 | 85 | // 两个都必须是函数 86 | return function mergeDataFn(){ 87 | return function margeData(childVal.call(this),parentVal.call(this)); 88 | } 89 | } 90 | 91 | // childVal => data:function(){return {...}} 92 | else if(parentVal || childVal){ 93 | return function mergedInstanceDataFn(){ 94 | var instanceData = typeof childVal === 'function' ? 95 | childVal.call(vm) : childVal; 96 | var defaultData = typeof parentVal === 'function' ? 97 | parentVal.call(vm) : "undefined"; 98 | if(instanceData){ 99 | return mergeData(instanceData,defaultData) 100 | } 101 | // 如果没有传props 默认data取父组件的 102 | else{ 103 | return defaultData; 104 | } 105 | } 106 | } 107 | }; 108 | ``` 109 | 110 | 111 | 112 | ### strats.watch 113 | 114 | 115 | 116 | ```javascript 117 | //观察者 118 | //观察者不应该被另一个重写,所以将他们合为数组 119 | strats.watch = function(parentVal, childVal){ 120 | //必须传满2个参数 121 | if(!childVal){return parentVal} 122 | if(!parentVal){return childVal} 123 | 124 | var ret = {}; 125 | //ret现在是父对象 126 | extend(ret, parentVal); 127 | //遍历子对象键 128 | for(var key in childVal){ 129 | // 130 | var parent = ret[key]; 131 | var child = childVal[key]; 132 | //父对象中如果有子对象属性 包装成对象 133 | if(parent && !Array.isArray(parent)){ 134 | parent = [parent]; 135 | } 136 | //拼接属性 137 | ret[key] = parent ? parent.concat(child) : [child]; 138 | } 139 | return ret; 140 | } 141 | ``` 142 | 143 | 144 | 145 | ### strats.props/methods/computed 146 | 147 | 148 | 149 | ```javascript 150 | strats.pros = 151 | strats.methods = 152 | strats.computed = 153 | function(parentVal, childVal){ 154 | if(!childVal){ return parentVal } 155 | if(!parentVal){ return childVal } 156 | var ret = Object.create(null); 157 | extend(ret, parentVal); 158 | extend(ret, childVal); 159 | return ret; 160 | } 161 | ``` 162 | 163 | 164 | 165 | ### defaultStrat 166 | 167 | 168 | 169 | ```javascript 170 | var defaultStrat = function(parentVal, childVal){ 171 | return childVal === undefined ? parentVal :childVal; 172 | } 173 | ``` 174 | 175 | 176 | 177 | ### checkComponents 178 | 179 | 180 | 181 | ```javascript 182 | //判断是否用内置指令或者标签作为组件ID 183 | function checkComponents(options){ 184 | for(var key in options.components){ 185 | var lower = key.toLowerCase(); 186 | if(isBuiltInTag(lower) || config.is ReservedTag(lower)){ 187 | warn('Do not use built-in or reserved HTML elements as component id: ' + key); 188 | } 189 | } 190 | } 191 | ``` 192 | 193 | 194 | 195 | ## merge 196 | 197 | 198 | 199 | ### mergeHook 200 | 201 | 202 | 203 | ```javascript 204 | // 返回数组形式的parentVal+childVal 205 | function mergeHook(parentVal, childVal){ 206 | return childVal ? (parentVal ? parentVal.concat(childVal) : Array.isArray(childVal) ? 207 | childVal : [childVal]) : parentVal 208 | } 209 | ``` 210 | 211 | 212 | 213 | ### mergeAssets 214 | 215 | 216 | 217 | ```javascript 218 | // 原型链 res => parentVal(null) 219 | // 返回 ChildVal 链=> parentVal 220 | function mergeAssets(parentVal, childVal){ 221 | var res = Object.create(parentVal || null); 222 | return childVal ? extend(res, childVal) : res 223 | } 224 | ``` 225 | 226 | 227 | 228 | ### mergeOptions 229 | 230 | 231 | 232 | ```javascript 233 | //合并参数 234 | function mergeOptions(parent, child, vm){ 235 | //检查是否规范 236 | checkComponents(child); 237 | normalizeProps(child); 238 | normalizeDirectives(child); 239 | //不知道这是个啥 240 | var extendsFrom = child.extends; 241 | if(extendsFrom){ 242 | //递归处理什么 暂时不动 243 | parent = typeof extendsFrom === 'function' ? 244 | mergeOptions(parent,extendsFrom.options,vm) : 245 | mergeOptions(parent,extendFrom,vm); 246 | } 247 | //混入 248 | if(child.mixins){ 249 | for(var i = 0, l = child.mixins.length; i < l; i++){ 250 | var mixin = child.mixins[i]; 251 | //Vue$3代表vue的构造函数 252 | if(mixin.prototype instanceof Vue$3){ 253 | mixin = mixin.options; 254 | } 255 | parent = mergeOptions(parent, mixin, vm); 256 | } 257 | } 258 | var option = {}; 259 | var key; 260 | for(key in parent){ 261 | mergeField(key); 262 | } 263 | for(key in child){ 264 | if(!hasOwn(parent, key)){ 265 | mergeField(key); 266 | } 267 | } 268 | 269 | function mergeField(key){ 270 | var strat = strats[key] || defaultStrat; 271 | options[key] = strat(parent[key], child[key], vm, key); 272 | } 273 | return options 274 | } 275 | ``` 276 | 277 | 278 | 279 | 280 | 281 | ## normalize 282 | 283 | 284 | 285 | ### normalizeProps 286 | 287 | 288 | 289 | ```javascript 290 | //确保options.props参数是规范的 291 | function normalizeProps(options){ 292 | var props = options.props; 293 | if(!props) {return } 294 | var res = {}; 295 | var i, val, name; 296 | //props是数组 297 | if(Array.isArray(props)){ 298 | i = props.length; 299 | while(i--){ 300 | val = props[i]; 301 | //值必须是字符串 302 | if(typeof val === 'string'){ 303 | name = camelize(val); 304 | res[name] = {type: null}; 305 | } 306 | else{ 307 | warn('props must be string when using array syntax.'); 308 | } 309 | } 310 | } 311 | //是对象 312 | else if(isPlainObject(props)){ 313 | for(var key in props){ 314 | val = props[key]; 315 | name = camelize(key); 316 | //值不是对象转换为对象{type:val} 317 | res[name] = isPlainObject(val) ? val : {type: val}; 318 | } 319 | } 320 | options.props = res; 321 | } 322 | ``` 323 | 324 | 325 | 326 | ### normalizeDirectives 327 | 328 | 329 | 330 | ```javascript 331 | //原生函数转换为对象的值 332 | function normalizeDirectives(options){ 333 | var dirs = options.directives; 334 | if(dirs){ 335 | for(var key in dirs){ 336 | //dirs = {key: fn} => dirs = {key:{bind:fn, update:fn}} 337 | var def = dirs[key]; 338 | if(typeof def === 'function'){ 339 | dirs[key] = {bind: def, update: def}; 340 | } 341 | } 342 | } 343 | } 344 | ``` 345 | 346 | 347 | 348 | ## prop 349 | 350 | 351 | 352 | ### resolveAsst 353 | 354 | 355 | 356 | ```javascript 357 | //决议(resolve)一个asset 358 | //子实例必须在assets的原型链上面 359 | function resolveAsset(options, type, id, warnMissing){ 360 | if(typeof id !== 'string'){ 361 | return 362 | } 363 | var assets = options[type]; 364 | //先查询本地属性 365 | if(hasOwn(assets, id)){return assets[id]} 366 | //驼峰处理 367 | var camelizedId = camelize(id); 368 | if(hasOwn(assets, camelizedId)){return assets[camelizedId]} 369 | //首字符大写处理 370 | var PascalCaseId = capitalize(camelizedId); 371 | if(hasOwn(assets, PascalCaseId)){return assets[PacalCaseId]} 372 | //遍历原型链 373 | var res = assets[id] || assets[camelizedId] || assets[PascalCaseId]; 374 | if('development' !== 'production' && warnMissing && !res){ 375 | warn('Failed to resolve' + type.slice(0,-1) + ': ' + id, options); 376 | } 377 | return res; 378 | } 379 | ``` 380 | 381 | 382 | 383 | ### validateProp 384 | 385 | 386 | 387 | ```javascript 388 | function validateProp(key, propOptions, propsData, vm){ 389 | var prop = propOptions[key]; 390 | var absent = !hasOwn(propsData, key); 391 | var value = propsData[key]; 392 | //处理props的布尔值 393 | if(isType(Boolean, prop.type)){ 394 | if(absent && !hasOwn(prop, 'default')){ 395 | value = false; 396 | } 397 | else if(!isType(String, prop.type) 398 | && (value === '' || value === hyphenate(key))){ 399 | value = true; 400 | } 401 | } 402 | if(value === undefined){ 403 | value = getPropDefaultValue(vm, prop, key); 404 | // 405 | var prevShouldConvert = observerState.shouldConvert; 406 | observerState.shouldConvert = true; 407 | observe(value); 408 | observerState.shouldConvert = prevShouldConvert; 409 | } 410 | assertProp(prop, key, value, vm, absent); 411 | return value; 412 | } 413 | ``` 414 | 415 | 416 | 417 | ### getPropDefaultValue 418 | 419 | 420 | 421 | ```javascript 422 | //获取默认的prop值 423 | function getPropDefaultValue(vm, prop, key){ 424 | //没有default直接返回undefined 425 | if(!hasOwn(prop, 'default')){ 426 | return undefined; 427 | } 428 | var def = prop.default; 429 | //警告没有默认值提供给对象&数组 430 | if('development' !== 'production' && isObject(def)){ 431 | warn('Invalid default value for prop "' + key + '": ' + 432 | 'Props with type Object/Array must use a factory function ' + 433 | 'to return the default value.'), vm); 434 | } 435 | // 436 | if(vm && vm.$options.propsData && 437 | vm.$options.propsData[key] === undefined && 438 | vm._props[key] !== undefined){ 439 | return vm._props[key]; 440 | } 441 | // 442 | return typeof def === 'function' && 443 | getType(prop.type) !== 'Function' ? def.call(vm) : def; 444 | } 445 | ``` 446 | 447 | 448 | 449 | ## assert 450 | 451 | 452 | 453 | ### assertProp 454 | 455 | 456 | 457 | ```javascript 458 | //判断prop是否有效 459 | function assertProp(prop, name, value, vm, absent){ 460 | if(prop.required && absent){ 461 | warn('Missing required prop: "' + name + '"', vm); 462 | return 463 | } 464 | if(value == null && !prop.required){ 465 | return 466 | } 467 | var type = prop.type; 468 | var valid = !type || type === true; 469 | var expectedTypes = []; 470 | if(type){ 471 | if(!Array.isArray(type)){ 472 | type = [type]; 473 | } 474 | for(var i = 0; i < type.length && !valud; i++){ 475 | var assertedType = assertType(value, type[i]); 476 | expectedTypes.push(assertedType.expectedType || ''); 477 | valid = assertedType.valid; 478 | } 479 | } 480 | if(!valid){ 481 | warn('Invalid prop: type check failed for prop "' + name '".' + 482 | ' Expected ' + expectedTypes.map(capitalize).join(',') + 483 | ', got ' + Object.prototype.toString.call(value).slice(8,-1) + '.', vm); 484 | return ; 485 | } 486 | var validator = prop.validator; 487 | if(validator){ 488 | if(!validator(value)){ 489 | warn('Invalid prop: custom validator check failed for prop "' + name '".', vm); 490 | } 491 | } 492 | } 493 | ``` 494 | 495 | 496 | 497 | ### assertType 498 | 499 | 500 | 501 | ```javascript 502 | //判断值类型 503 | function assertType(value, type){ 504 | var valid; 505 | var expectedType = getType(type); 506 | if(expectedType === 'String'){ 507 | valid = typeof value === (expectedType = 'string'); 508 | } else if(expectedType === 'Number'){ 509 | valid = typeof value === (expectedType = 'number'); 510 | } else if(expectedType === 'Boolean'){ 511 | valid = typeof value === (expectedType = 'boolean'); 512 | } else if(expectedType === 'Function'){ 513 | valid = typeof value === (expectedType = 'function'); 514 | } else if(expectedType === 'Object'){ 515 | valid = isPlainObject(value); 516 | } else if(expectedType === 'Array'){ 517 | valid = Array.isArray(value); 518 | } else{ 519 | valid = value instanceof type; 520 | } 521 | return { 522 | valid: valid, 523 | expectedType: expectedType 524 | } 525 | } 526 | ``` 527 | 528 | 529 | 530 | ### getType 531 | 532 | 533 | 534 | ```javascript 535 | //使用函数的toString方法判断是否是内置类型 536 | //返回函数名 537 | function getType(fn){ 538 | var match = fn && fn.toString().match(/^\s*function (\w+)/); 539 | return match && match[1]; 540 | } 541 | ``` 542 | 543 | 544 | 545 | ### isType 546 | 547 | 548 | 549 | ```javascript 550 | function isType(type, fn){ 551 | if(!Array.isArray(fn)){ 552 | return getType(fn) === getType(type) 553 | } 554 | for(var i = 0, len = fn.length; i < len; i++){ 555 | if(getType(fn[i]) === getType(Type)){ 556 | return true; 557 | } 558 | } 559 | return false; 560 | } 561 | ``` 562 | 563 | 564 | 565 | ### handleError 566 | 567 | 568 | 569 | ```javascript 570 | function handleError(err, vm, info){ 571 | if(config.errorHandler){ 572 | config.errorHandler.call(null, err, vm, info); 573 | } else { 574 | warn('Error in ' + info + ':', vm); 575 | } 576 | if(inBrowser && typeof console !== 'undefined'){ 577 | console.error(err); 578 | } else { 579 | throw err; 580 | } 581 | } 582 | ``` 583 | 584 | 585 | 586 | ## Proxy 587 | 588 | 589 | 590 | ```javascript 591 | var initProxy; 592 | //所有内置原生对象 593 | var allowedGlobals = makeMap( 594 | 'Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,' + 595 | 'decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' + 596 | 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Int1,' + 597 | 'require' //兼容webpack/Browserify 598 | ); 599 | //这地方真奇怪 之前都是用'"' 这里偏偏用"\""转义一下 600 | var warnNonPresent = function(target, key){ 601 | warn('Property or method "' + key + '" is not defined on the instance but ' + 602 | 'referenced during render. Make sure to declare reactive data ' + 603 | 'properties in the data option.', target); 604 | }; 605 | //是否支持Proxy 这TM是ES6的大家伙啊 606 | var hasProxy = typeof Proxy !== 'undefined' && Proxy.toString().match(/native code/); 607 | if(hasProxy){ 608 | //内置特殊按键属性 609 | var isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta'); 610 | config.keyCodes = new Proxy(condig.keyCodes, { 611 | set: function set(target, key, value){ 612 | //判断是否内置修饰符 613 | if(isBuiltInModifier(key)){ 614 | //这里加两个括号不知道为啥 615 | warn(('Avoid overwriting built-in modifier in config.keyCodes: .' + key)); 616 | return false 617 | } else { 618 | target[key] = value; 619 | return true; 620 | } 621 | } 622 | }); 623 | } 624 | 625 | var hasHandler = { 626 | has: function has(target, key){ 627 | var has = key in target; 628 | var isAllowed = allowedGlobals(key) || key.charAt(0) === '_'; 629 | if(!has && !isAllowed){ 630 | warnNonPresent(target, key); 631 | } 632 | return has || !isAllowed; 633 | } 634 | }; 635 | 636 | var getHandler = { 637 | get: function get(target, key){ 638 | if(typeof key === 'string' && !(key in target)){ 639 | warnNonPresent(target, key); 640 | } 641 | return target[key]; 642 | } 643 | }; 644 | //初始化 645 | initProxy = function initProxy(vm){ 646 | if(hasProxy){ 647 | var options = vm.$options; 648 | var handlers = option.render && 649 | options.render._withStripped ? getHandler : hasHandler; 650 | vm._renderProxy = new Proxy(vm, handlers); 651 | } else { 652 | vm._renderProxy = vm; 653 | } 654 | }; 655 | ``` 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | -------------------------------------------------------------------------------- /04-VNode.md: -------------------------------------------------------------------------------- 1 | # 虚拟DOM 2 | 3 | 4 | 5 | ## VNode 6 | 7 | 8 | 9 | ### 构造函数 10 | 11 | 12 | 13 | ```javascript 14 | var VNode = function(tag,data,children,text,elm,context,componentOptions){ 15 | this.tag = tag; 16 | this.data = data; 17 | this.children = children; 18 | this.text = text; 19 | this.elm = elm; 20 | this.ns = undefined; 21 | this.context = context; 22 | this.functionalContext = undefined; 23 | this.key = data && data.key; 24 | this.componentOptions = componentOptions; 25 | this.componentInstance = undefined; 26 | this.parent = undefined; 27 | this.raw = false; 28 | this.isStatic = false; 29 | this.isRootInsert = true; 30 | this.isComment = false; 31 | this.isCloned = false; 32 | this.isOnce = false; 33 | }; 34 | ``` 35 | 36 | 37 | 38 | ### prototypeAccessors 39 | 40 | 41 | 42 | ```javascript 43 | var prototypeAccessors = {child: {}}; 44 | //注释讲这里是为了向下兼容 45 | prototypeAccessors.child.get = function(){ 46 | return this.componentInstance; 47 | }; 48 | Object.defineProperties(VNode.prototype, prototypeAccessors); 49 | ``` 50 | 51 | 52 | 53 | ### createVNode 54 | 55 | 56 | 57 | #### createEmptyVNode 58 | 59 | 60 | 61 | ```javascript 62 | var createEmptyVNode = function(){ 63 | var node = new VNode(); 64 | node.text = ''; 65 | node.isComment = true; 66 | return node; 67 | } 68 | ``` 69 | 70 | 71 | 72 | #### createTextVNode 73 | 74 | 75 | 76 | ```javascript 77 | //仅传text参数 78 | function createTextVNode(val){ 79 | return new VNode(undefined, undefined, undefined, String(val)); 80 | } 81 | ``` 82 | 83 | 84 | 85 | #### cloneVNode 86 | 87 | 88 | 89 | ```javascript 90 | function cloneVNode(vnode){ 91 | var cloned = new VNode( 92 | vnode.tag, 93 | vnode.data, 94 | vnode.children, 95 | vnode.text, 96 | vnode,elm, 97 | vnode.context, 98 | vnode.componentOptions 99 | ); 100 | cloned.ns = vnode.ns; 101 | cloned.isStatic = vnode.isStatic; 102 | cloned.key = vnode.key; 103 | cloned.isCloned = true; 104 | return cloned; 105 | } 106 | 107 | //批量clone虚拟节点 包装在数组中 108 | function cloneVNodes(vnodes){ 109 | var len = vnodes.length; 110 | var res = new Array(len); 111 | for(var i = 0; i < len; i++){ 112 | res[i] = cloneVNode[vnodes[i]]; 113 | } 114 | return res; 115 | } 116 | ``` 117 | 118 | 119 | 120 | ### normalizeEvent 121 | 122 | 123 | 124 | ```javascript 125 | var normalizeEvent = cached(function(name){ 126 | //检查前缀 127 | var once$$1 = name.charAt(0) === '~'; 128 | //去掉~ 129 | name = once$$1 ? name.slice(1) : name; 130 | var capture = name.charAt(0) === '!'; 131 | name = capture ? name.slice(1) : name; 132 | return { 133 | name: name, 134 | once: once$$1, 135 | capture: capture 136 | } 137 | }) 138 | ``` 139 | 140 | 141 | 142 | ### createFnInvoker 143 | 144 | 145 | 146 | ```javascript 147 | //函数调用器 148 | function createFnInvoker(fns){ 149 | function invoker(){ 150 | //内部参数 151 | var arguments$1 = arguments; 152 | //外部参数 153 | var fns = invoker.fns; 154 | //外部参数为函数数组时 遍历依次执行 参数为内部参数 155 | if(Array.isArray(fns)){ 156 | for(var i = 0; i < fns.length: i++){ 157 | fns[i].apply(null, arguments$1); 158 | } 159 | } else { 160 | //外部参数为单一函数 161 | return fns.apply(null, arguments) 162 | } 163 | } 164 | //参数保存为函数属性 165 | invoker.fns = fns; 166 | return invoker; 167 | } 168 | ``` 169 | 170 | 171 | 172 | ### updateListeners 173 | 174 | 175 | 176 | ```javascript 177 | function updateListeners(on,oldOn,add,remove$$1,vm){ 178 | var name, cur, old, event; 179 | for(name in on){ 180 | cur = on[name]; 181 | old = oldOn[name]; 182 | event = normalizeEvent(name); 183 | if(!cur){ 184 | 'development' !== 'production' && 185 | warn('Invalid handler for event "' + event.name + '": got ' + String(cur), vm); 186 | } else if (!old) { 187 | if(!cur.fns){ 188 | cur = on[name] = createFnInvoker(cur); 189 | } 190 | add(event.name, cur, event.once, event.capture); 191 | } else if (cur !== old) { 192 | old.fns = cur; 193 | on[name] = old; 194 | } 195 | } 196 | for(name in oldOn){ 197 | if(!on[name]){ 198 | event = normalizeEvent(name); 199 | remove$$1(event.name, oldOn[name], event.capture); 200 | } 201 | } 202 | } 203 | ``` 204 | 205 | 206 | 207 | ### mergeVNodeHook 208 | 209 | 210 | 211 | ```javascript 212 | function mergeVNodeHook(def, hookKey, hook){ 213 | var invoker; 214 | var oldHook = def[hookKey]; 215 | 216 | function wrappedHook(){ 217 | hook.apply(this, arguments); 218 | //移除钩子保证只被调用一次 防止内存泄漏 219 | remove(invoker.fns, wrappedHook); 220 | } 221 | if(!oldHook){ 222 | //没有钩子 223 | FnInvoker([wrappedHook]); 224 | } else { 225 | if (oldHook.fns && oldHook.merged){ 226 | invoker = oldHook; 227 | invoker.fns.push(wrappedHook); 228 | } else { 229 | //空钩子 230 | invoker = createFnInvoker([oldHook, wrappedHook]); 231 | } 232 | } 233 | invoker.merged = true; 234 | def[hookKey] = invoker; 235 | } 236 | ``` 237 | 238 | 239 | 240 | ### NormalizeChildren 241 | 242 | 243 | 244 | #### simpleNormalizeChildren 245 | 246 | 247 | 248 | ```javascript 249 | //尤大是这样说的 250 | //一般情况下都是纯粹HTML标签 调用这个就行了 251 | function simpleNormalizeChildren(children){ 252 | for(var i = 0; i < children.length; i++){ 253 | if(Array.isArray(children[i])){ 254 | return Array.prototype.concat.apply([], children); 255 | } 256 | } 257 | return children; 258 | } 259 | ``` 260 | 261 | 262 | 263 | #### normalizeChildren 264 | 265 | 266 | 267 | ```javascript 268 | function normalizeChildren(children){ 269 | //简单解释下就是 : 270 | //[*] => createTextVNode(*) 271 | //基本类型 => [*] 272 | //数组 => 递归 => [*] 273 | //非数组非基本类型 => undefined 274 | return isPrimitive(children) ? [createTextVNode(children)] : 275 | Array.isArray(children) ? normalizeArrayChildren(children) : undefined; 276 | } 277 | ``` 278 | 279 | 280 | 281 | #### normalizeArrayChildren 282 | 283 | 284 | 285 | ```javascript 286 | //这TM 287 | function normalizeArrayChildren(children, nestedIndex){ 288 | var res = []; 289 | var i, c, last; 290 | for(i = 0; i < children.length; i++){ 291 | c = children[i]; 292 | if( c == null || typeof c === 'boolean'){continue} 293 | last = res[res.length-1]; 294 | //嵌套 295 | if(Array.isArray(c)){ 296 | res.push.apply(res, normalizeArrayChildren(c, ((nestedIndex || '') + '_' + i))); 297 | } else if(isPrimitive(c)){ 298 | if(last && last.text){ 299 | last.text += String(c); 300 | } else if(c !== ''){ 301 | //转换基本数据类型 302 | res.push(createTextVNode(c)); 303 | } 304 | } else { 305 | if(c.text && last && last.text){ 306 | res[res.length - 1] = createTextVNode(last.text + c.text); 307 | } else{ 308 | // 309 | if(c.tag && c.key == null && nestedIndex != null){ 310 | c.key = '__vlist' + nestedIndex + '_' + i + '__'; 311 | } 312 | res.push(c); 313 | } 314 | } 315 | } 316 | return res; 317 | } 318 | ``` 319 | 320 | 321 | 322 | #### getFirstComponentChild 323 | 324 | 325 | 326 | ```javascript 327 | function getFirstComponentChild(children){ 328 | return children && children.filter(function(c) {return c && c.componentOptions;})[0] 329 | } 330 | ``` 331 | 332 | 333 | 334 | ### Event 335 | 336 | 337 | 338 | #### initEvents 339 | 340 | 341 | 342 | ```javascript 343 | function initEvents(vm){ 344 | vm._events = Object.create(null); 345 | vm._hasHookEvent = false; 346 | //初始化父元素相关事件 347 | var listeners = vm.$options._parentListeners; 348 | if(listeners){ 349 | updateComponentListeners(vm, listeners); 350 | } 351 | } 352 | 353 | var target; 354 | //添加 355 | function add(event, fn, once$$1){ 356 | if(once$$!){ 357 | target.$once(event, fn); 358 | } else{ 359 | target.$on(event, fn); 360 | } 361 | } 362 | //删除 363 | function remove$1(event, fn){ 364 | target.$off(event, fn); 365 | } 366 | 367 | function updateComponentListeners(vm, listeners, oldListeners){ 368 | target = vm; 369 | updateListeners(listeners, oldListeners || {}, add, remove$1, vm); 370 | } 371 | ``` 372 | 373 | 374 | 375 | #### eventsMixin 376 | 377 | 378 | 379 | ```javascript 380 | function eventsMixin(Vue){ 381 | var hookRE = /^hook:/; 382 | //绑定事件 383 | Vue.prototype.$on = function(event, fn){ 384 | var this$1 = this; 385 | var vm = this; 386 | if(Array.isArray(event)){ 387 | for(var i = 0, l = event.length; i < l; i++){ 388 | this$1.$on(event[i], fn); 389 | } 390 | } else{ 391 | (vm._events[event] || (vm._events[event] = [])).push(fn); 392 | // 393 | if(hookRE.test(event)){ 394 | vm._hasHookEvent = true; 395 | } 396 | } 397 | return vm; 398 | }; 399 | //绑定一次事件 400 | Vue.prototype.$once = function(event, fn){ 401 | var vm = this; 402 | function on(){ 403 | vm.$off(event, on); 404 | fn.apply(vm, arguments); 405 | } 406 | on.fn = fn; 407 | vm.$on(event, on); 408 | return vm; 409 | }; 410 | //解绑事件 411 | Vue.prototype.$off = function(event, fn){ 412 | var this$1 = this; 413 | var vm = this; 414 | //all 415 | if(!arguments.length){ 416 | vm._events = Object.create(null); 417 | return vm; 418 | } 419 | //事件数组 420 | if(Array.isArray(event)){ 421 | //好恶心的命名 422 | for(var i$1 = 0, l = event.length; i$1 < l; i$1++){ 423 | this$1.$off(event[i$1], fn); 424 | } 425 | return vm; 426 | } 427 | //特定事件 428 | var cbs = vm._events[event]; 429 | if(!cbs){ 430 | return vm; 431 | } 432 | if(arguments.length === 1){ 433 | vm._events[event] = null; 434 | return vm; 435 | } 436 | //特定处理器 437 | var cb; 438 | var i = cbs.length; 439 | while(i--){ 440 | cb = cbs[i]; 441 | if(cb === fn || cb.fn){ 442 | cbs.splice(i, 1); 443 | break; 444 | } 445 | } 446 | return vm 447 | }; 448 | //触发事件 449 | Vue.prototype.$emit = function(event){ 450 | var vm = this; 451 | var cbs = vm._events[event]; 452 | if(cbs){ 453 | cbs = cbs.length > 1 ? toArray(cbs) : cbs; 454 | var args = toArray(arguments, 1); 455 | for(var i = 0, l = cbs.length; i < l; i++){ 456 | cbs[i].apply(vm, args); 457 | } 458 | } 459 | return vm; 460 | }; 461 | } 462 | ``` 463 | 464 | 465 | 466 | ### Slot 467 | 468 | 469 | 470 | #### resolveSlots 471 | 472 | 473 | 474 | ```javascript 475 | function resolveSlots(children,context){ 476 | var slots = {} 477 | if(!children){ 478 | return slots; 479 | } 480 | var defaultSlot = []; 481 | var name, child; 482 | for(var i = 0, l = children.length; i < l; i++){ 483 | child = children[i]; 484 | // 485 | if((child.context === context || child.functionalContext === context) && 486 | child.data && (name = child.data.slot)){ 487 | var slot = (slots[name] || (slots[name] = [])); 488 | if(child.tag === 'template'){ 489 | slot.push.apply(slot, child.children); 490 | } else{ 491 | slot.psuh(child); 492 | } 493 | } else{ 494 | defaultSlot.push(child); 495 | } 496 | } 497 | //忽略空格 498 | if(!defaultSlot.every(isWhitespace)){ 499 | slots.default = defaultSlot; 500 | } 501 | return slots; 502 | } 503 | ``` 504 | 505 | 506 | 507 | #### isWhitespace 508 | 509 | 510 | 511 | ```javascript 512 | function isWhitespace(node){ 513 | return node.isComment || node.text === ' '; 514 | } 515 | ``` 516 | 517 | 518 | 519 | #### resolveScopedSlots 520 | 521 | 522 | 523 | ```javascript 524 | function resolveScopedSlots(fns){ 525 | var res = {}; 526 | for(var i = 0; i < fns.length; i++){ 527 | res[fns[i][0]] = fns[i][1]; 528 | } 529 | return res; 530 | } 531 | ``` 532 | 533 | 534 | 535 | 536 | 537 | -------------------------------------------------------------------------------- /05-initLifecycle.md: -------------------------------------------------------------------------------- 1 | # Lifecycle 2 | 3 | 4 | 5 | ### initLifecycle 6 | 7 | ```javascript 8 | var activeInstance = null; 9 | // vue对象初始化的第一步 10 | function initLifecycle(vm){ 11 | // 这里的$options是{el:'',data:{}}等等 12 | var options = vm.$options; 13 | var parent = options.parent; 14 | if(parent && !options.abstract){ 15 | while(parent.$options.abstract && parent.$parent){ 16 | parent = parent.$parent; 17 | } 18 | parent.$children.push(vm); 19 | } 20 | 21 | vm.$parent = parent; 22 | vm.$root = parent ? parent.$root : vm; 23 | 24 | vm.$children = []; 25 | vm.$refs = {};; 26 | 27 | vm._watcher = null; 28 | vm._native = null; 29 | vm._directInactive = false; 30 | vm._isMounted = false; 31 | vm._isDestroyed = false; 32 | vm._isBeingDestroyed = false; 33 | } 34 | ``` 35 | 36 | 37 | 38 | ### lifecycleMixin 39 | 40 | 41 | 42 | ```javascript 43 | function lifecycleMixin(Vue){ 44 | // _update 45 | Vue.prototype._update = function(vnode, hydrating){ 46 | var vm = this; 47 | if(vm._isMounted){ 48 | callHook(vm, 'beforeUpdate'); 49 | } 50 | var prevEl = vm.$el; 51 | var prevVnode = vm._vnode; 52 | var prevActionInstance = activeInstance; 53 | activeInstance = vm; 54 | vm._vnode = vnode; 55 | 56 | if(!prevVnode){ 57 | // 初始化渲染 58 | vm.$el = vm.__patch__(vm.$el,vnode,hydrating,false,vm.$options._parentElm,vm.$options._refElm); 59 | } 60 | else{ 61 | vm.$el = vm.__patch__(prevVnode, vnode); 62 | } 63 | activeInstance = prevActiveInstance; 64 | // 65 | if(prevEl){ 66 | prevEl.__vue__ = null; 67 | } 68 | if(vm.$el){ 69 | vm.$el.__vue__ = vm; 70 | } 71 | // HOC是什么??? 72 | if(vm.$vnode && vm.$parent && (vm.$vnode === vm.$parent._vnode)){ 73 | vm.$parent.$el = vm.$el; 74 | } 75 | } 76 | 77 | // 强制更新 78 | // $forceUpdate 79 | Vue.prototype.$forceUpdate = function(){ 80 | var vm = this; 81 | if(vm._watcher){ 82 | vm._watcher.update(); 83 | } 84 | }; 85 | 86 | // 生命周期的最后阶段 销毁 87 | // $destroy 88 | Vue.prototype.$destroy = function(){ 89 | var vm = this; 90 | if(vm._isBeingDestroyed){ 91 | return 92 | } 93 | callHook(vm, 'beforeDestroy'); 94 | vm._isBeingDestroyed = true; 95 | // 从父元素分离 96 | var parent = vm.$parent; 97 | if(parent && !parent._isBeingDestroyed && !vm.$options.abstract){ 98 | remove(parent.$children, vm); 99 | } 100 | // 肢解watcher! 101 | if(vm._watcher){ 102 | vm._watcher.teardown(); 103 | } 104 | var i = vm._watchers.length; 105 | while(i--){ 106 | vm._watchers[i].teardown(); 107 | } 108 | // 109 | if(vm._data.__ob__){ 110 | vm._data_.__ob__.vmCount--; 111 | } 112 | // 调用最后一个钩子函数 113 | vm._isDestroyed = true; 114 | callHook(vm, 'destroyed'); 115 | // 移除所有实例监听者 116 | vm.$off(); 117 | // 移除vue引用 118 | if(vm.$el){ 119 | vm.$el.__vue__ = null; 120 | } 121 | } 122 | } 123 | ``` 124 | 125 | 126 | 127 | ### mountComponent 128 | 129 | 130 | 131 | ```javascript 132 | // 挂载组件 133 | function mountComponent(vm, el, hydrating){ 134 | vm.$el = el; 135 | if(!vm.$options.render){ 136 | vm.$options.render = createEmptyVNode; 137 | if((vm.$options.template && vm.$options.template.charAt(0) !== '#') || vm.$options.el || el){ 138 | warn('You are using the runtime-only build of Vue where the template ' + 139 | 'compiler is not available. Either pre-compiler the template into ' + 140 | 'render functions, or use the compiler-included build.', vm); 141 | } 142 | else{ 143 | warn('Failed to mount component: template or render function not defined.', vm); 144 | } 145 | } 146 | callHook(vm, 'beforeMount'); 147 | 148 | var updateComponent; 149 | if('development' !== 'production' && config.performance && perf){ 150 | updateComponent = function(){ 151 | var name = vm._name; 152 | var startTag = 'start' + name; 153 | var endTag = 'end' + name; 154 | perf.mark(startTag); 155 | var vnode = vm._render(); 156 | perf.mark(endTag); 157 | perf.measure((name + ' render'), startTag, endTag); 158 | perf.mark(startTag); 159 | vm._update(vnode, hydrating); 160 | perf.mark(endTag); 161 | perf.measure((name + ' patch'), startTag, endTag); 162 | }; 163 | } 164 | else{ 165 | updateComponent = function(){ 166 | vm._update(vm._render(), hydrating); 167 | }; 168 | } 169 | vm._watcher = new Watcher(vm, updateComponent, noop); 170 | hydrating = false; 171 | 172 | // 173 | if(vm.$vnode == null){ 174 | vm._isMounted = true; 175 | callHook(vm, 'mounted'); 176 | } 177 | return vm; 178 | } 179 | ``` 180 | 181 | 182 | 183 | ### updateChildComponent 184 | 185 | 186 | 187 | ```javascript 188 | function updateChildComponent(vm, propsData, listeners, parentVnode, renderChildren){ 189 | // 检测是否有slot标签 190 | var hasChildren = !!( 191 | renderChildren || 192 | vm.$options._renderChildren || 193 | parentVnode.data.scopedSlots || 194 | vm.$scopedSolts !== emptyObject 195 | ); 196 | 197 | vm.$options._parentVnodes = parentVnode; 198 | vm.$vnode = parentVnode; 199 | if(vm._vnode){ 200 | vm._vnode.parent = parentVnode; 201 | } 202 | vm.$options._renderChildren = renderChildren; 203 | // 更新props 204 | if(propsData && vm.$options.props){ 205 | observerState.shouldConvert = false; 206 | observerState.isSettingProps = true; 207 | var props = vm._props; 208 | var propKeys = vm.$options._propKeys || []; 209 | for(var i = 0; i < propKeys.length; i++){ 210 | var key = propKeys[i]; 211 | props[key] = validateProp(key, vm.$options.props, propsData, vm); 212 | } 213 | observerState.shouldConvert = true; 214 | observerState.isSettingProps = false; 215 | // 保存一份原始副本 216 | vm.$options.propsData = propsData; 217 | } 218 | if(listeners){ 219 | var oldListeners = vm.$options._parentListeners; 220 | vm.$options._parentListeners = listeners; 221 | updateComponentListeners(vm, listeners, oldListeners); 222 | } 223 | // 224 | if(hasChildren){ 225 | vm.$slots = resolveSlots(renderChildren, parentVnode.context); 226 | vm.$forceUpdate(); 227 | } 228 | } 229 | ``` 230 | 231 | 232 | 233 | ### callHook 234 | 235 | 236 | 237 | ```javascript 238 | function callHook(vm, hook){ 239 | var handlers = vm.$options[hook]; 240 | if(handlers){ 241 | for(var i = 0; j = handlers.length; i < j; i++){ 242 | try{ 243 | handlers[i].call(vm); 244 | } catch (e){ 245 | handleError(e, vm, (hook + 'hook')); 246 | } 247 | } 248 | } 249 | // 触发钩子事件 250 | if(vm._hasHookEvent){ 251 | vm.$emit('hook' + hook); 252 | } 253 | } 254 | ``` 255 | 256 | 257 | 258 | #### isInInactiveTree 259 | 260 | 261 | 262 | ```javascript 263 | // 是否存在vm.$parent._inactive 264 | function isInInactiveTree(vm){ 265 | while(vm && (vm = vm.$parent)){ 266 | if(vm._inactive){return true} 267 | } 268 | return false; 269 | } 270 | ``` 271 | 272 | 273 | 274 | #### activateChildComponent 275 | 276 | 277 | 278 | ```javascript 279 | // 触发activated钩子 280 | function activateChildComponent(vm, direct){ 281 | if(direct){ 282 | vm._directInactive = false; 283 | if(isInInactiveTree(vm)){ 284 | return; 285 | } 286 | } 287 | else if(vm._directInactive){ 288 | return; 289 | } 290 | if(vm._inactive || vm._inactive == null){ 291 | vm._inactive = false; 292 | for(var i = 0; i < vm.$children.length; i++){ 293 | activateChildComponent(vm.$children[i]); 294 | } 295 | callHook(vm, 'activated'); 296 | } 297 | } 298 | ``` 299 | 300 | 301 | 302 | #### deactivateChildComponent 303 | 304 | 305 | 306 | ```javascript 307 | // 触发deactivated钩子 308 | function deactivateChildComponent(vm, direct){ 309 | if(direct){ 310 | vm._directInactive = true; 311 | if(isInInactiveTree(vm)){ 312 | return; 313 | } 314 | } 315 | if(!vm._inactive){ 316 | vm._inactive = true; 317 | for(var i = 0; i < vm.$children.length; i++){ 318 | deactivateChildComponent(vm.$children[i]); 319 | } 320 | callHook(vm, 'deactivated'); 321 | } 322 | } 323 | ``` 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | -------------------------------------------------------------------------------- /06-Watcher.md: -------------------------------------------------------------------------------- 1 | # Watcher 2 | 3 | 4 | 5 | ## 初始化变量 6 | 7 | ```javascript 8 | var queue = []; 9 | var has = {}; 10 | var circular = {}; 11 | var waiting = false; 12 | var flushing = false; 13 | // 用来做循环的 14 | var index = 0; 15 | ``` 16 | 17 | 18 | 19 | ### resetSchedulerState 20 | 21 | ```javascript 22 | // 重置状态 23 | function resetSchedulerState(){ 24 | queue.length = 0; 25 | has = {}; 26 | { 27 | circular = {}; 28 | } 29 | waiting = flushing = false; 30 | } 31 | ``` 32 | 33 | 34 | 35 | ### flushSchedulerQueue 36 | 37 | ```javascript 38 | // flush队列并遍历执行watchers 39 | function flushSchedulerQueue(){ 40 | flushing = true; 41 | var watcher, id, vm; 42 | 43 | // 先进行排序 44 | queue.sort(function(a,b){return a.id - b.id; }); 45 | 46 | // 不缓存长度 执行中可能会有额外的watcher加入 47 | for(index = 0; index < queue.length; index++){ 48 | watcher = queue[index]; 49 | id = watcher.id; 50 | has[id] = null; 51 | watcher.run(); 52 | 53 | // 开发者模式 54 | if('development' !== 'production' && has[id] != null){ 55 | circular[id] = (circular[id] || 0) + 1; 56 | if(circular[id] > config._maxUpdateCount){ 57 | warn( 58 | 'You may have an infinite update loop' + ( 59 | watcher.user 60 | ? ('in watcher with expression "' + (watcher.expression) + '"') 61 | : 'in a component render function.' 62 | ),watcher.vm 63 | ); 64 | break; 65 | } 66 | } 67 | } 68 | 69 | // 更新钩子调用前先清空任务队列 70 | var oldQueue = queue.slice(); 71 | resetSchedulerState(); 72 | 73 | // 调用钩子 74 | index = oldQueue.length; 75 | while(index--){ 76 | watcher = oldQueue[index]; 77 | vm = watcher.vm; 78 | if(vm._watcher === watcher && vm._isMounted){ 79 | callHook(vm, 'updated'); 80 | } 81 | } 82 | 83 | // 开发者模式钩子 84 | if(devtools && config.devtools){ 85 | devtools.emit('flush'); 86 | } 87 | } 88 | ``` 89 | 90 | 91 | 92 | ### queueWatcher 93 | 94 | ```javascript 95 | // 将watcher推入队列 96 | function queueWatcher(watcher){ 97 | var id = watcher.id; 98 | if(has[id] == null){ 99 | has[id] = true; 100 | // 重复ID仅在未flush时加入 101 | if(!flushing){ 102 | queue.push(watcher); 103 | } 104 | // 如果处于flush状态 105 | else{ 106 | var i = queue.length - 1; 107 | while(i >= 0 && queue[i].id > watcher.id){ 108 | i--; 109 | } 110 | queue.splice(Math.max(i, index) + 1, 0, watcher); 111 | } 112 | 113 | // 如果不处于waiting状态 下一个microtask递归执行本函数 114 | if(!waiting){ 115 | waiting = true; 116 | nextTick(flushSchedulerQueue); 117 | } 118 | } 119 | } 120 | ``` 121 | 122 | 123 | 124 | --- 125 | 126 | 127 | 128 | ## Watcher 129 | 130 | 131 | 132 | ### uid$2 133 | 134 | ```javascript 135 | var uid$2 = 0; 136 | ``` 137 | 138 | 139 | 140 | ### Watcher 141 | 142 | ```javascript 143 | // construtor 144 | var Watcher = function Watcher(vm, expOrFn, cb, options){ 145 | this.vm = vm; 146 | vm._watchers.push(this); 147 | 148 | // 可选参数 149 | if(options){ 150 | this.deep = !!options.deep; 151 | this.user = !!options.user; 152 | this.lazy = !!options.lazy; 153 | this.sync = !!options.sync; 154 | } else{ 155 | this.deep = this.user = this.lazy = this.sync = false; 156 | } 157 | 158 | this.cb = cb; 159 | // batching/批量处理? 160 | this.id = ++uid$2; 161 | this.active = true; 162 | // lazy watchers??? 163 | this.dirty = this.lazy; 164 | this.deps = []; 165 | this.newDeps = []; 166 | this.depIds = new _Set(); 167 | this.newDepIds = new _Set(); 168 | this.expression = expOrFn.toString(); 169 | // 解析表达式for getter 170 | if(typeof expOrFn === 'function'){ 171 | this.getter = expOrFn; 172 | } else{ 173 | this.getter = parsePath(expOrFn); 174 | if(!this.getter){ 175 | this.getter = function(){}; 176 | 'development' !== 'production' && warn( 177 | 'Failed watching path: "' + expOrFn + '" ' + 178 | 'Watcher only accepts simple dot-delimited paths. ' + 179 | 'For full control, use a function instead.', 180 | vm 181 | ); 182 | } 183 | } 184 | this.value = this.lazy ? undefined : this.get(); 185 | }; 186 | ``` 187 | 188 | 189 | 190 | ### Watcher.prototype 191 | 192 | 193 | 194 | #### get 195 | 196 | ```javascript 197 | // 评估getter 重新收集依赖 198 | Watcher.prototype.get = function get(){ 199 | pushTarget(this); 200 | var value; 201 | var vm = this.vm; 202 | if(this.user){ 203 | try{ 204 | value = this.getter.call(vm, vm); 205 | } catch(e){ 206 | handleError(e, vm, ('getter for watcher "' + (this.expression) + '"')); 207 | } 208 | } else{ 209 | value = this.getter.call(vm, vm); 210 | } 211 | 212 | // touch每个值 213 | if(this.deep){ 214 | traverse(value); 215 | } 216 | 217 | popTarget(); 218 | this.cleanupDeps(); 219 | return value; 220 | } 221 | ``` 222 | 223 | 224 | 225 | #### addDep 226 | 227 | ```javascript 228 | // 给指令添加一个依赖 229 | Watcher.prototype.addDep = function addDep(dep){ 230 | var id = dep.id; 231 | if(!this.newDepIds.has(id)){ 232 | this.newDepIds.add(id); 233 | this.newDeps.push(dep); 234 | if(!this.depIds.has(id)){ 235 | dep.addSub(this); 236 | } 237 | } 238 | }; 239 | ``` 240 | 241 | 242 | 243 | #### cleanupDeps 244 | 245 | ```javascript 246 | // 依赖收集前的清空 247 | Watcher.prototype.cleanupDeps = function cleanupDeps(){ 248 | // 保存this引用 249 | var this$1 = this; 250 | var i = this.deps.length; 251 | while(i--){ 252 | var dep = this$1.deps[i]; 253 | if(!this$1.newDepIds.has(dep.id)){ 254 | dep.removeSub(this$1); 255 | } 256 | } 257 | 258 | // depIds <= newDepIds 259 | var tmp = this.depIds; 260 | this.depIds = this.newDepIds; 261 | this.newDepIds = tmp; 262 | // 清空所有成员 263 | this.newDepIds.clear(); 264 | tmp = this.deps; 265 | // deps <= newDeps 266 | this.deps = this.newDeps; 267 | this.newDeps = tmp; 268 | // 清空 269 | this.newDeps.length = 0; 270 | } 271 | ``` 272 | 273 | 274 | 275 | #### update 276 | 277 | ```javascript 278 | // 订阅者接口 279 | // 依赖变动时会被调用 280 | Watcher.prototype.update = function update(){ 281 | if(this.lazy){ 282 | this.dirty = true; 283 | } else if(this.sync){ 284 | this.run(); 285 | } else{ 286 | queueWatcher(this); 287 | } 288 | }; 289 | ``` 290 | 291 | 292 | 293 | #### run 294 | 295 | ```javascript 296 | // 任务接口 297 | Watcher.prototype.run = function run(){ 298 | if(this.active){ 299 | // 获取新值 300 | var value = this.get(); 301 | // 值改变、复杂数据类型、deep属性为true 302 | if(value !== this.value || isObject(value) || this.deep){ 303 | // 设置新值 304 | var oldValue = this.value; 305 | this.value = value; 306 | if(this.user){ 307 | try{ 308 | this.cb.call(this.vm, value, oldValue); 309 | } catch(e){ 310 | handleError(e, this.vm, ('callback for watcher "' + (this.expression) + '"')); 311 | } 312 | } else{ 313 | this.cb.call(this.vm, value, oldValue); 314 | } 315 | } 316 | } 317 | }; 318 | ``` 319 | 320 | 321 | 322 | #### evaluate 323 | 324 | ```javascript 325 | // 求值 326 | // 只针对lazy属性的watcher 327 | Watcher.prototype.evaluate = function evaluate(){ 328 | this.value = this.get(); 329 | this.dirty = false; 330 | }; 331 | ``` 332 | 333 | 334 | 335 | #### depend 336 | 337 | ```javascript 338 | Watcher.prototype.depend = function depend(){ 339 | var this$1 = this; 340 | var i = this.deps.length; 341 | while(i--){ 342 | this$1.deps[i].depend(); 343 | } 344 | }; 345 | ``` 346 | 347 | 348 | 349 | #### teardown 350 | 351 | ```javascript 352 | // 拆解依赖 353 | Watcher.prototype.teardown = function teardown(){ 354 | var this$1 = this; 355 | if(this.active){ 356 | // 从被观测列表中移除 357 | // 如果vm被摧毁则跳过 358 | if(!this.vm_isBeingDestroyed){ 359 | remove(this.vm._watchers, this); 360 | } 361 | var i = this.deps.length; 362 | while(i--){ 363 | this$1.deps[i].removeSub(this$1); 364 | } 365 | this.active = false; 366 | } 367 | }; 368 | ``` 369 | 370 | 371 | 372 | #### traverse 373 | 374 | ```javascript 375 | // 递归traverse一个对象 376 | // 保证对象中所有嵌套属性作为'deep'依赖被收集 377 | // 只对复杂数据类型操作 378 | var seenObjects = new _Set(); 379 | 380 | function traverse(val){ 381 | // 清空依赖集合 382 | seenObjects.clear(); 383 | _traverse(val, seenObject); 384 | } 385 | 386 | function _traverse(val, seen){ 387 | var i, keys; 388 | var isA = Array.isArray(val); 389 | // 非数组、对象或不可扩展 390 | if((!isA && !isObject(val)) || !Object.isExtensible(val)){ 391 | return ; 392 | } 393 | // 将id添加到空的Set对象中 394 | if(val.__ob__){ 395 | var depId = val.__ob__.dep.id; 396 | // 如果已有对应ID 返回 397 | if(seen.has(depId)){ 398 | return ; 399 | } 400 | seen.add(depId); 401 | } 402 | if(isA){ 403 | i = val.length; 404 | while(i--){_traverse(val[i], seen); } 405 | } else{ 406 | keys = Object.keys(val); 407 | i = keys.length; 408 | while(i--){_traverse(val[keys[i]], seen); } 409 | } 410 | } 411 | ``` 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | -------------------------------------------------------------------------------- /07-initState.md: -------------------------------------------------------------------------------- 1 | # init 2 | 3 | 4 | 5 | 6 | 7 | ## proxy 8 | 9 | ```javascript 10 | var sharedPropertyDefinition = { 11 | enumerable: true, 12 | configurable: true, 13 | get: noop, 14 | set: noop 15 | }; 16 | 17 | // Proxy是ES6的代理 18 | // 小写的不是关键字! 19 | function proxy(target, sourceKey, key){ 20 | sharedPropertyDefinition.get = function proxyGetter(){ 21 | return this[sourceKey][key]; 22 | }; 23 | sharedPropertyDefinition.set = function proxySetter(val){ 24 | this[sourceKey][key] = val; 25 | }; 26 | Object.defineProperty(target, key, sharedPropertyDefinition); 27 | } 28 | ``` 29 | 30 | 31 | 32 | --- 33 | 34 | 35 | 36 | ## initState 37 | 38 | ```javascript 39 | // 所有状态初始化 40 | function initState(vm){ 41 | vm._watchers = []; 42 | var opts = vm.$options; 43 | if(opts.props){ initProps(vm, opts.props); } 44 | if(opts.methods){ initMethods(vm, opts.methods); } 45 | if(opts.data){ 46 | initData(vm); 47 | } else{ 48 | observe(vm._data = {}, true); 49 | } 50 | if(opts.computed){ initComputed(vm, opts.computed); } 51 | if(opts.watch){ initWatch(vm, opts.watch); } 52 | } 53 | ``` 54 | 55 | 56 | 57 | --- 58 | 59 | 60 | 61 | ### initProps 62 | 63 | ```javascript 64 | // 保留字 65 | var isReservedProp = {key: 1, ref: 1, slot: 1}; 66 | 67 | // props初始化 68 | function initProps(vm, propsOptions){ 69 | var propsData = vm.$options.propsData || {}; 70 | var props = vm._props = {}; 71 | // 缓存prop keys 72 | var keys = vm.$options._propKeys = []; 73 | var isRoot = !vm.$parent; 74 | // 根实例props转换 75 | observerState.shouldConvert = isRoot; 76 | var loop = function(key){ 77 | keys.push(key); 78 | var value = validateProp(key, propsOptions, propData, vm); 79 | 80 | // ignore 81 | if(isReservedProp[key]){ 82 | warn( 83 | ('"' + key + '" is a reserved attribute and cannot be used as component prop.'), 84 | vm 85 | ); 86 | } 87 | defineReactive$$1(props, key, value, function(){ 88 | if(vm.$parent && !observerState.isSettingProps){ 89 | warn( 90 | 'Avoid mutating a prop directly since the value will be' + 91 | 'overwritten whenever the parent component re-renders.' + 92 | "Instead, use a data or computed property based on the prop's " + 93 | 'value. Prop being mutated: "' + key +'"', vm 94 | ); 95 | } 96 | }); 97 | 98 | // 99 | if(!(key in vm)){ 100 | proxy(vm, '_props', key); 101 | } 102 | }; 103 | for(var key in propsOptions){ 104 | loop(key); 105 | } 106 | observerState.shouldConvert = true; 107 | } 108 | ``` 109 | 110 | 111 | 112 | --- 113 | 114 | 115 | 116 | ### initData 117 | 118 | ```javascript 119 | function initData(vm){ 120 | var data = vm.$options.data; 121 | data = vm._data = typeof data === 'function' ? 122 | getData(data, vm) : data || {}; 123 | // data必须返回一个对象 124 | if(!isPlainObject(data)){ 125 | data = {}; 126 | 'development' !== 'production' && warn( 127 | 'data functions should return an object:\n' + 128 | 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function', 129 | vm 130 | ); 131 | } 132 | 133 | // 代理data属性 134 | var keys = Object.keys(data); 135 | var props = vm.$options.props; 136 | var i = keys.length; 137 | while(i--){ 138 | if(props && hasOwn(props, keys[i])){ 139 | 'development' !== 'production' && warn( 140 | 'The data property "' + (keys[i]) + '" is already declared as a prop. ' + 141 | 'Use prop default value instead.', 142 | vm 143 | ); 144 | } else if(!isReserved(keys[i])){ 145 | proxy(vm, '_data', keys[i]); 146 | } 147 | } 148 | // 观测 149 | observe(data, true); 150 | } 151 | ``` 152 | 153 | 154 | 155 | #### getData 156 | 157 | ```javascript 158 | function getData(data, vm){ 159 | try{ 160 | return data.call(vm); 161 | } catch(e){ 162 | handleError(e, vm, 'data()'); 163 | return {}; 164 | } 165 | } 166 | ``` 167 | 168 | 169 | 170 | --- 171 | 172 | 173 | 174 | ### initComputed 175 | 176 | ```javascript 177 | var computedWatcherOptions = {lazy: true }; 178 | 179 | function initComputed(vm, computed){ 180 | var watcher = vm._computedWatchers = Object.create(null); 181 | 182 | for(var key in computed){ 183 | var userDef = computed[key]; 184 | var getter = typeof userDef === 'function' ? userDef : userDef.get; 185 | if(getter === undefined){ 186 | warn( 187 | ('No getter function has been defined for computed property "' + key + '".'), 188 | vm 189 | ); 190 | getter = noop; 191 | } 192 | // 给computed属性创建内部watcher 193 | watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions); 194 | 195 | // 组件部分已经被定义 仅需要computed属性的实例化 196 | if(!(key in vm)){ 197 | defineComputed(vm, key, userDef); 198 | } 199 | } 200 | } 201 | ``` 202 | 203 | 204 | 205 | #### defineComputed 206 | 207 | ```javascript 208 | // computed属性实例化 209 | function defineComputed(target, key, userDef){ 210 | if(typeof userDef === 'function'){ 211 | // computed为被动更改 不需要set方法 212 | sharedPropertyDefinition.get = createComputedGetter(key); 213 | sharedPropertyDefinition.set = noop; 214 | } else{ 215 | // userDef.get不存在 => noop 216 | // userDef.get存在 => userDef.cache不存在 => userDef.get 217 | // userDef.get存在 => userDef.cache存在 => createComputedGetter(key) 218 | sharedPropertyDefinition.get = userDef.get ? 219 | userDef.cache !== false ? 220 | createComputedGetter(key) : 221 | userDef.get : noop; 222 | // userDef.set不存在 => noop 223 | sharedPropertyDefinition.set = userDef.set ? 224 | userDef.set : noop; 225 | } 226 | Object.defineProperty(target, key, sharedPropertyDefinition); 227 | } 228 | ``` 229 | 230 | 231 | 232 | #### createComputedGetter 233 | 234 | ```javascript 235 | function createComputedGetter(key){ 236 | return function computedGetter(){ 237 | var watcher = this._computedWatchers && this._computedWatchers[key]; 238 | if(watcher){ 239 | if(watcher.dirty){ 240 | watcher.evaluate(); 241 | } 242 | if(Dep.target){ 243 | watcher.depend(); 244 | } 245 | return watcher.value; 246 | } 247 | } 248 | } 249 | ``` 250 | 251 | 252 | 253 | --- 254 | 255 | 256 | 257 | ### initMethods 258 | 259 | ```javascript 260 | function initMethods(vm, methods){ 261 | var props = vm.$options.props; 262 | for(var key in methods){ 263 | vm[key] = method[key] == null ? noop : bind(methods[key], vm); 264 | // 265 | if(methods[key] == null){ 266 | warn( 267 | 'method "' + key + '" has an undefined value in the component definition. ' + 268 | 'Did you reference the function correctly?', 269 | vm 270 | ); 271 | } 272 | // 方法已经被定义 273 | if(props && hasOwn(props, key)){ 274 | warn( 275 | ('method "' + key + '" has already been defined as a prop'), 276 | vm 277 | ); 278 | } 279 | } 280 | } 281 | ``` 282 | 283 | 284 | 285 | --- 286 | 287 | 288 | 289 | ### initWatch 290 | 291 | ```javascript 292 | function initWatch(vm, watch){ 293 | for(var key in watch){ 294 | var handler = watch[key]; 295 | if(Array.isArray(handler)){ 296 | for(var i = 0; i < handler.length; i++){ 297 | createWatcher(vm, key, handler[i]); 298 | } 299 | } else{ 300 | createWatcher(vm, key, handler); 301 | } 302 | } 303 | } 304 | ``` 305 | 306 | 307 | 308 | #### createWatcher 309 | 310 | ```javascript 311 | function createWatcher(vm, key, handler){ 312 | var options; 313 | if(isPlainObject(handler)){ 314 | options = handler; 315 | handler = handler.handler; 316 | } 317 | if(typeof handler === 'string'){ 318 | handler = vm[handler]; 319 | } 320 | vm.$watch(key, handler, options); 321 | } 322 | ``` 323 | 324 | 325 | 326 | --- 327 | 328 | 329 | 330 | ### stateMixin 331 | 332 | ```javascript 333 | // state相关原型方法混入 334 | // 注释说Object.defineProperty直接定义会出问题 335 | function stateMixin(Vue){ 336 | // 定义get方法 337 | var dataDef = {}; 338 | dataDef.get = function(){ return this._data }; 339 | var propsDef = {}; 340 | propDef.get = function(){ return this._props }; 341 | 342 | // 调用set会触发警告 343 | dataDef.set = function(newData){ 344 | warn( 345 | 'Avoid replacing instance root $data. ' + 346 | 'Use nested data properties instead.', 347 | this 348 | ); 349 | }; 350 | propsDef.set = function(){ 351 | warn('$props is readonly.', this); 352 | } 353 | 354 | // 定义原型方法 355 | Object.defineProperty(Vue.prototype, '$data', dataDef); 356 | Object.defineProperty(Vue.prototype, '$props', propsDef); 357 | 358 | Vue.prototype.$set = set; 359 | Vue.prototype.$delete = del; 360 | 361 | Vue.prototype.$watch = function(expOrFn, cb, options){ 362 | var vm = this; 363 | options = options || {}; 364 | options.user = true; 365 | var watcher = new Watcher(vm, expOrFn, cb, options); 366 | if(options.immediate){ 367 | cb.call(vm,watcher.value); 368 | } 369 | return function unwatchFn(){ 370 | watcher.teardown(); 371 | } 372 | } 373 | } 374 | ``` 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | -------------------------------------------------------------------------------- /08-component&props.md: -------------------------------------------------------------------------------- 1 | # component&&props 2 | 3 | 4 | 5 | 6 | 7 | ## component 8 | 9 | 10 | 11 | ### componentVNodeHooks 12 | 13 | ```javascript 14 | var componentVNodeHooks = { 15 | // init 16 | init: function init(vnode, hydrating, parentElm, refElm){ 17 | if(!vnode.componentInstance || vnode.componentInstance._isDestroyed){ 18 | // initLifecycle之前 19 | // var activeInstance = null; 20 | // lifecycleMixin方法中 21 | // var prevActiveInstance = activeInstance; 22 | // activeInstance = vm; 23 | // activeInstance = prevActiveInstance; 24 | var child = vnode.componentInstance = createComponentInstanceForVnode( 25 | vnode, 26 | activeInstance, 27 | parentElm, 28 | refElm 29 | ); 30 | child.$mount(hydrating ? vnode.elm : undefined, hydrating); 31 | } else if(vnode.data.keepAlive){ 32 | // keep-alive组件 当做补丁 33 | var mountedNode = vnode; 34 | componentVNodeHooks.prepatch(mountedNode, mountedNode); 35 | } 36 | }, 37 | 38 | // prepatch 39 | prepatch: function prepatch(oldVnode, vnode){ 40 | var options = vnode.componentOptions; 41 | var child = vnode.componentInstance = oldVnode.componentInstance; 42 | 43 | // 更新子组件 44 | updateChildComponent( 45 | child, 46 | options.propsData, // 更新props 47 | options.listeners, // 更新监听者 48 | vnode, // new parent vnode 49 | options.children // new children 50 | ); 51 | }, 52 | 53 | // insert 54 | insert: function insert(vnode){ 55 | if(!vnode.componentInstance._isMounted){ 56 | vnode.componentInstance._isMounted = true; 57 | callHook(vnode.componentInstance, 'mounted'); 58 | } 59 | if(vnode.data.keepAlive){ 60 | activateChildComponent(vnode.componentInstance, true); 61 | } 62 | }, 63 | 64 | // destroy 65 | destroy: function destroy(vnode){ 66 | // 查看对应属性 67 | if(!vnode.componentInstance._isDestroyed){ 68 | if(!vnode.data.keepAlive){ 69 | vnode.componentInstance.$destroy(); 70 | } else{ 71 | deactivateChildComponent(vnode.componentInstance, true); 72 | } 73 | } 74 | } 75 | }; 76 | ``` 77 | 78 | 79 | 80 | ### createComponent 81 | 82 | ```javascript 83 | function createComponent(Ctor, data, context, children, tag){ 84 | if(!Ctor){ 85 | return; 86 | } 87 | 88 | var baseCtor = context.$options._base; 89 | if(isObject(Ctor)){ 90 | Ctor = baseCtor.extend(Ctor); 91 | } 92 | 93 | if(typeof Ctor !== 'function'){ 94 | warn(('Invalid Component definition: ' + (String(Ctor))), context); 95 | return ; 96 | } 97 | 98 | // 异步组件 99 | if(!Ctor.cid){ 100 | if(Ctor.resolved){ 101 | Ctor = Ctor.resolved; 102 | } else{ 103 | Ctor = resolveAsyncComponent(Ctor, baseCtor, function(){ 104 | context.$forceUpdate(); 105 | }); 106 | if(!Ctor){ 107 | return ; 108 | } 109 | } 110 | } 111 | 112 | resolveConstructorOptions(Ctor); 113 | 114 | data = data || {}; 115 | 116 | // 转换组件v-model 117 | if(data.model){ 118 | transformModel(Ctor.options, data); 119 | } 120 | 121 | // 提取props 122 | var propsData = extractProps(data, Ctor, tag); 123 | 124 | // 函数组件 125 | if(Ctor.options.functional){ 126 | return createFunctionalComponent(Ctor, propsData, data, context, children); 127 | } 128 | 129 | // 130 | var listeners = data.on; 131 | data.on = data.nativeOn; 132 | 133 | if(Ctor.options.abstract){ 134 | // 抽象组件没数据 135 | data = {}; 136 | } 137 | 138 | // 139 | mergeHooks(data); 140 | 141 | // 返回一个占位虚拟节点 142 | var name = Ctor.options.name || tag; 143 | var vnode = new VNode( 144 | ('Vue-component-' + (Ctor.cid) + (name ? ('-' + name) : '')), 145 | data, undefined, undefined, undefined, context, { Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children} 146 | ); 147 | return vnode; 148 | } 149 | ``` 150 | 151 | 152 | 153 | ### createFunctionalComponent 154 | 155 | ```javascript 156 | function createFunctionalComponent(Ctor, propsData, data, context, children){ 157 | var props = {}; 158 | var propOptions = Ctor.options.props; 159 | if(propOptions){ 160 | for(var key in propOptions){ 161 | props[key] = validateProp(key, propOptions, propsData); 162 | } 163 | } 164 | 165 | // 166 | var _context = Object.create(context); 167 | var h = function(a,b,c,d){ return createElement(_context, a, b, c, d, true); }; 168 | var vnode = Ctor.options.render.call(null, h, { 169 | props: props, 170 | data: data, 171 | parent: context, 172 | children: children, 173 | slots: function(){ return resolveSlots(children, context); } 174 | }); 175 | if(vnode instanceof VNode){ 176 | vnode.functionalContext = context; 177 | if(data.slot){ 178 | (vnode.data || (vnode.data = {})).slot = data.slot; 179 | } 180 | } 181 | return vnode; 182 | } 183 | ``` 184 | 185 | 186 | 187 | ### createComponentInstanceForVnode 188 | 189 | ```javascript 190 | function createComponentInstanceForVnode(vnode, parent, parentElm, refElm){ 191 | var vnodeComponentOptions = vnode.componentOptions; 192 | var options = { 193 | _isComponent: true, 194 | parent: parent, 195 | propsData: vnodeComponentOptions.propsData, 196 | _componentTag: vnodeComponentOptions.tag, 197 | _parentVnode: vnode, 198 | _parentListeners: vnodeComponentOptions.listeners, 199 | _renderChildren: vnodeComponentOptions.children, 200 | _parentElm: parentElm || null, 201 | _refElm: refElm || null 202 | }; 203 | 204 | // 检查内置模板渲染函数 205 | var inlineTemplate = vnode.data.inlineTemplate; 206 | if(inlineTemplate){ 207 | options.render = inlineTemplate.render; 208 | options.staticRenderFns = inlineTemplte.staticRenderFns; 209 | } 210 | return new vnodeComponentOptions.Ctor(options); 211 | } 212 | ``` 213 | 214 | 215 | 216 | ### resolveAsyncComponent 217 | 218 | ```javascript 219 | function resolveAsyncComponent(factory, baseCtor, cb){ 220 | if(factory.requested){ 221 | // 回调池 222 | factory.pendingCallbacks.push(cb); 223 | } else{ 224 | factory.requested = true; 225 | var cbs = factory.pendingCallbacks = [cb]; 226 | var sync = true; 227 | 228 | var resolve = function(res){ 229 | if(isObject(res)){ 230 | res = baseCtor.extend(res); 231 | } 232 | // 缓存resolved 233 | factory.resolved = res; 234 | // 非同步时调用回调 235 | if(!sync){ 236 | for(var i = 0, l = cbs.length; i < l; i++){ 237 | cbs[i](res); 238 | } 239 | } 240 | }; 241 | var res = factory(resolve, reject); 242 | 243 | // 处理promise 244 | if(res && typeof res.then === 'function' && !factory.resolved){ 245 | res.then(resolve, reject); 246 | } 247 | 248 | sync = false; 249 | 250 | return factory.resolved; 251 | } 252 | } 253 | ``` 254 | 255 | 256 | 257 | --- 258 | 259 | 260 | 261 | ## props 262 | 263 | 264 | 265 | ### extractProps 266 | 267 | ```javascript 268 | function extractProps(data, Ctor, tag){ 269 | // 仅提取原生值 270 | var propOptions = Ctor.options.props; 271 | if(!propOptions){ 272 | return; 273 | } 274 | var res = {}; 275 | var attrs = data.attrs; 276 | var props = data.props; 277 | var domProps = data.domProps; 278 | if(attrs || props || domProps){ 279 | for(var key in propOptions){ 280 | var altKey = hyphenate(key); 281 | 282 | var keyInLowerCase = key.toLowerCase(); 283 | if(key !== keyInLowerCase && attrs && attrs.hasOwnProperty(keyInLowerCase)){ 284 | tip( 285 | 'Prop "' + keyInLowerCase + '" is passed to component' + 286 | (formatComponentName(tag || Ctor)) + ', but the declared prop name is' + 287 | ' "' + key + '". ' + 288 | 'Note that HTML attributes are case-insensitive and camelCased ' + 289 | 'props need to use their kebab-case equivalents when using in-DOM ' + 290 | 'templates. You should probably use "' + altKey + '" instead of "' + key + '".' 291 | ); 292 | } 293 | checkProp(res, props, key, altKey, true) || 294 | checkProp(res, attrs, key, altKey) || 295 | checkProp(res, domProps, key, altKey); 296 | } 297 | } 298 | return res; 299 | } 300 | ``` 301 | 302 | 303 | 304 | ### checkProp 305 | 306 | ```javascript 307 | function checkProp(res, hash, key, altKey, preserve){ 308 | if(hash){ 309 | if(hasOwn(hash, key)){ 310 | res[key] = hash[key]; 311 | if(!preserve){ 312 | delete hash[key]; 313 | } 314 | return true; 315 | } 316 | } 317 | return false; 318 | } 319 | ``` 320 | 321 | 322 | 323 | ### mergeHooks 324 | 325 | ```javascript 326 | var hooksToMerge = Object.keys(componentVNodeHooks); 327 | 328 | function mergeHooks(data){ 329 | // 默认参数 330 | if(!data.hook){ 331 | data.hook = {}; 332 | } 333 | for(var i = 0; i < hooksToMerge.length; i++){ 334 | var key = hooksToMerge[i]; 335 | var fromParent = data.hook[key]; 336 | var ours = componentVNodeHooks[keys]; 337 | data.hook[key] = fromParent ? mergeHook$1(ours, fromParent) : ours; 338 | } 339 | } 340 | ``` 341 | 342 | 343 | 344 | ### mergeHook$1 345 | 346 | ```javascript 347 | // 这函数…… 348 | function mergeHook$1(one, two){ 349 | return function(a, b, c, d){ 350 | one(a, b, c, d); 351 | two(a, b, c, d); 352 | } 353 | } 354 | ``` 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | -------------------------------------------------------------------------------- /09-buildInComponent.md: -------------------------------------------------------------------------------- 1 | # 内置指令 2 | 3 | 4 | 5 | ## createElement 6 | 7 | 8 | 9 | ### createElement 10 | 11 | ```javascript 12 | var SIMPLE_NORMALIZE = 1; 13 | var ALWAYS_NORMALIZE = 2; 14 | 15 | // 包装函数提供更弹性的接口 16 | // 这函数就是用来做参数修正 17 | function createElement(context, tag, data, children, normalizationType, alwaysNormalize){ 18 | // data是数组或基本数据类型时 19 | // 3,4,5参数往后推 undefined => data, data => children, children => normalizationType 20 | if(Array.isArray(data) || isPrimitive(data)){ 21 | normalizationType = children; 22 | children = data; 23 | data = undefined; 24 | } 25 | // 如果提供最后一个参数 normalizationType = 2 26 | if(alwaysNormalize) { normalizationType = ALWAYS_NORMALIZE; } 27 | return _createElement(context, tag, data, children, normalizationType); 28 | } 29 | ``` 30 | 31 | 32 | 33 | ### _createElement 34 | 35 | ```javascript 36 | function _createElement(context, tag, data, children, normalizationType){ 37 | // data是响应式数据 38 | if(data && data.__ob__){ 39 | 'development' !== 'production' && warn( 40 | 'Avoid using observed data object as vnode data: ' + (JSON.stringify(data)) + '\n' + 41 | 'Always create fresh vnode data objects in each render!', 42 | context 43 | ); 44 | return createEmptyVNode(); 45 | } 46 | // 没有tag参数 47 | if(!tag){ 48 | return createEmptyVNode(); 49 | } 50 | // children是数组且第一个元素是函数 51 | if(Array.isArray(children) && typeof children[0] === 'function'){ 52 | data = data || {}; 53 | data.scopedSlots = { default: children[0] }; 54 | // 清空 55 | children.length = 0; 56 | } 57 | // 根据参数调用对应的函数 58 | if(normalizationType === ALWAYS_NORMALIZE){ 59 | children = normalizeChildren(children); 60 | } else if(normalizationType === SIMPLE_NORMALIZE){ 61 | children = simpleNormalizeChildren(children); 62 | } 63 | 64 | var vnode, ns; 65 | if(typeof tag === 'string'){ 66 | var Ctor; 67 | ns = config.getTagNamespace(tag); 68 | if(config.isReservedTag){ 69 | // 内置元素 70 | vnode = new VNode( 71 | config.parsePlatformTagName(tag), data, children, 72 | undefined, undefined, context 73 | ); 74 | } else if((Ctor = resolveAsset(context.$options, 'component', tag))){ 75 | // 组件 76 | vnode = createComponent(Ctor, data, context, children, tag); 77 | } else{ 78 | // 其他未知元素 79 | vnode = new VNode( 80 | tag, data, children, 81 | undefined, undefined, context 82 | ); 83 | } 84 | } else{ 85 | vnode = createComponent(tag, data, context, children); 86 | } 87 | if(vnode){ 88 | if(ns){ applyNS(vnode, ns); } 89 | return vnode; 90 | } else{ 91 | return createEmptyVNode(); 92 | } 93 | } 94 | ``` 95 | 96 | 97 | 98 | ### applyNS 99 | 100 | ```javascript 101 | function applyNS(vnode, ns){ 102 | vode.ns = ns; 103 | if(vnode.tag === 'foreignObject'){ 104 | return ; 105 | } 106 | if(vnode.children){ 107 | for(var i = 0; l = vnode.children.length; i < l; i++){ 108 | var child = vnode.children[i]; 109 | if(child.tag && !child.ns){ 110 | applyNS(child, ns); 111 | } 112 | } 113 | } 114 | } 115 | ``` 116 | 117 | 118 | 119 | --- 120 | 121 | 122 | 123 | ## v-model 124 | 125 | 126 | 127 | ### transformModel 128 | 129 | ```javascript 130 | // 分别将v-model信息转换成prop与event 131 | function transformModel(options, data){ 132 | // 默认 prop = value 133 | var prop = (options.model && options.model.prop) || 'value'; 134 | // 默认 event = input 135 | var event = (options.model && options.model.event) || 'input'; 136 | (data.props || (data.props = {}))[prop] = data.model.value; 137 | var on = data.on || (data.on = {}); 138 | if(on[event]){ 139 | on[event] = [data.model.callback].concat(on[event]); 140 | } else{ 141 | on[event] = data.model.callback; 142 | } 143 | } 144 | ``` 145 | 146 | 147 | 148 | --- 149 | 150 | 151 | 152 | ## v-for 153 | 154 | 155 | 156 | ### renderList 157 | 158 | ```javascript 159 | function renderList(val, render){ 160 | var ret, i, l, keys ,key; 161 | // 数组或字符串 162 | if(Array.isArray(val) || typeof val === 'string'){ 163 | ret = new Array(val.length); 164 | for(var i = 0, l = val.length; i < l; i++){ 165 | ret[i] = render(val[i], i); 166 | } 167 | } 168 | // 数字 169 | else if(typeof val === 'number'){ 170 | ret = new Array(val); 171 | for(var i = 0; i < val; i++){ 172 | ret[i] = render(i + 1, i); 173 | } 174 | } 175 | // 对象 176 | else if(isObject(val)){ 177 | keys = Object.keys(val); 178 | ret = new Array(keys.length); 179 | for(var i = 0, l = keys.length; i < l; i++){ 180 | key = keys[i]; 181 | ret[i] = render(val[key], key, i); 182 | } 183 | } 184 | return ret; 185 | } 186 | ``` 187 | 188 | 189 | 190 | --- 191 | 192 | 193 | 194 | ## 195 | 196 | 197 | 198 | ### renderSlot 199 | 200 | ```javascript 201 | function renderSlot(name, fallback, props, bindObject){ 202 | var scopedSlotFn = this.$scopedSlots[name]; 203 | // scoped属性的slot 204 | if(scopedSlotFn){ 205 | props = props || {}; 206 | if(bindObject){ 207 | extend(props, bindObject); 208 | } 209 | return scopedSlotFn(props) || fallback; 210 | } else{ 211 | var slotNodes = this.$slots[name]; 212 | // 警告重复slot使用 213 | if(slotNodes && 'development' !== 'production'){ 214 | slotNodes._rendered && warn( 215 | 'Duplicate presence of slot "' + name + '" found in the same render tree' + 216 | '- this will likely cause render errors.', 217 | this 218 | ); 219 | slotNodes._rendered = true; 220 | } 221 | return slotNodes || fallback; 222 | } 223 | } 224 | ``` 225 | 226 | 227 | 228 | --- 229 | 230 | 231 | 232 | ## filters 233 | 234 | 235 | 236 | ### resolveFilter 237 | 238 | ```javascript 239 | function resolveFilter(id){ 240 | return resolveAsset(this.$options, 'filters', id, true) || identity; 241 | } 242 | ``` 243 | 244 | 245 | 246 | --- 247 | 248 | 249 | 250 | ## keyCodes 251 | 252 | 253 | 254 | ### checkKeyCodes 255 | 256 | ```javascript 257 | // 判断是否内置键盘指令别名 258 | function checkKeyCodes(eventKeyCode, key, builtInAlias){ 259 | var keyCodes = config.keyCodes[key] || builtInAlias; 260 | if(Array.isArray(keyCodes)){ 261 | return keyCodes.indexOf(eventKeyCode) === -1; 262 | } else{ 263 | return keyCodes !== eventKeyCode; 264 | } 265 | } 266 | ``` 267 | 268 | 269 | 270 | --- 271 | 272 | 273 | 274 | ## v-bind 275 | 276 | 277 | 278 | ### bindObjectProps 279 | 280 | ```javascript 281 | function bindObjectProps(data, tag, value, asProp){ 282 | if(value){ 283 | if(!isObject(value)){ 284 | 'development' !== 'production' && warn( 285 | 'v-bind without argument expects an Object or Array value', 286 | this 287 | ); 288 | } else{ 289 | // 将数组转换成对象统一处理 290 | if(Array.isArray(value)){ 291 | value = toObject(value); 292 | } 293 | var hash; 294 | for(var key in value){ 295 | if(key === 'class' || key === 'style'){ 296 | hash = data; 297 | } else{ 298 | var type = data.attrs && data.attrs.type; 299 | hash = asProp || config.mustUseProp(tag, type, key) ? 300 | data.domProps || (data.domProps = {}) : 301 | data.attrs || (data.attrs = {}); 302 | } 303 | // value的key与hash同步 304 | if(!(key in hash)){ 305 | hash[key] = value[key]; 306 | } 307 | } 308 | } 309 | } 310 | return data; 311 | } 312 | ``` 313 | 314 | 315 | 316 | ---- 317 | 318 | 319 | 320 | ## static-trees 321 | 322 | 323 | 324 | ### renderStatic 325 | 326 | ```javascript 327 | function renderStatic(index, isInFor){ 328 | var tree = this._staticTrees[index]; 329 | // 非v-for情况下重复利用静态树 330 | if(tree && !isInFor){ 331 | return Array.isArray(tree) ? 332 | cloneVNodes(tree) : cloneVNode(tree); 333 | } 334 | // 否则渲染一个新树 335 | tree = this._staticTrees[index] = 336 | this.$options.staticRenderFns[index].call(this._renderProxy); 337 | markStatic(tree, ('__static__' + index), false); 338 | return tree; 339 | } 340 | ``` 341 | 342 | 343 | 344 | --- 345 | 346 | 347 | 348 | ## v-once 349 | 350 | 351 | 352 | ### markOnce 353 | 354 | ```javascript 355 | function markOnce(tree, index, key){ 356 | // 有key markStatic(tree,__once__ + index + _ + key,true) 357 | // 无key markStatic(tree,__once__ + index,true) 358 | markStatic(tree, ('__once__' + index + (key ? ('_' + key) : '')), true); 359 | return tree; 360 | } 361 | ``` 362 | 363 | 364 | 365 | --- 366 | 367 | 368 | 369 | ## markStatic 370 | 371 | ```javascript 372 | function markStatic(tree, key, isOnce){ 373 | if(Array.isArray(tree)){ 374 | for(var i = 0; i < tree.length; i++){ 375 | if(tree[i] && typeof tree[i] !== 'string'){ 376 | markStaticNode(tree[i], (key + '_' + i), isOnce); 377 | } 378 | } 379 | } else{ 380 | markStaticNode(tree, key, isOnce); 381 | } 382 | } 383 | ``` 384 | 385 | 386 | 387 | ## markStaticNode 388 | 389 | ```javascript 390 | // 添加属性的方法 391 | function markStaticNode(node, key, isOnce){ 392 | node.isStatic = true; 393 | node.key = key; 394 | node.isOnce = isOnce; 395 | } 396 | ``` 397 | 398 | 399 | 400 | --- 401 | 402 | 403 | 404 | 405 | 406 | ## initRender 407 | 408 | ```javascript 409 | function initRender(vm){ 410 | vm.$vnode = null; 411 | vm._vnode = null; 412 | vm._staticTrees = null; 413 | var parentVnode = vm.$options._parentVnode; 414 | var renderContext = parentVnode && parentVnode.context; 415 | vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext); 416 | vm.$scopedSlots = emptyObject; 417 | // 418 | vm._c = function(a, b, c, d) { return createElement(vm, a, b, c, d, false); }; 419 | // 420 | vm.$createElement = function(a, b, c, d) { return createElement(vm, a, b, c, d, true); }; 421 | } 422 | ``` 423 | 424 | 425 | 426 | ## renderMixin 427 | 428 | ```javascript 429 | function renderMixin(Vue){ 430 | // 几百年前定义的方法在这里混入了 431 | Vue.prototype.$nectTick = function(fn){ 432 | return nextTick(fn, this); 433 | } 434 | 435 | // 436 | Vue.prototype._render = function(){ 437 | var vm = this; 438 | var ref = vm.$options; 439 | var render = ref.render; 440 | var staticRenderFns = ref.staticRenderFns; 441 | var _parentVnode = ref._parentVnode; 442 | 443 | if(vm._isMounted){ 444 | // 克隆slot节点便于重复渲染 445 | for(var key in vm.$slots){ 446 | vm.$slots[key] = cloneVNodes(vm.$slots[key]); 447 | } 448 | } 449 | 450 | vm.$scopedSlots = (_parentVnode && _parentVnode.data.scopedSlots) || emptyObject; 451 | 452 | if(staticRenderFns && !vm._staticTrees){ 453 | vm._staticTrees = []; 454 | } 455 | // 设置父节点 456 | vm.$vnode = _parentVnode; 457 | 458 | var vnode; 459 | try{ 460 | vnode = render.call(vm._renderProxy, vm.$createElement); 461 | } catch(e){ 462 | handleError(e, vm, 'render function'); 463 | // 返回错误渲染结果 464 | vnode = vm.$options.renderError ? 465 | vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e) : 466 | vm._vnode; 467 | } 468 | 469 | // 当返回错误的渲染结果 470 | if(!(vnode instanceof VNode)){ 471 | if('development' !== 'production' && Array.isArray(vnode)){ 472 | warn( 473 | 'Multiple root nodes returned from render function. Render function' + 474 | 'should return a single root node.', 475 | vm 476 | ); 477 | } 478 | vnode = createEmptyVNode(); 479 | } 480 | // 设置parent 481 | vnode.parent = _parentVnode; 482 | return vnode; 483 | }; 484 | 485 | // 内部方法缩写 486 | 487 | // v-once 488 | Vue.prototype._o = markOnce; 489 | // parseFloat 490 | Vue.prototype._n = toNumber; 491 | // String()或JSON.stringify() 492 | Vue.prototype._s = _toString; 493 | // v-for 494 | Vue.prototype._l = renderList; 495 | // slot 496 | Vue.prototype._t = renderSlot; 497 | // _toString后判断是否全等 498 | Vue.prototype._q = looseEqual; 499 | // looseEqual判断索引是否存在 500 | Vue.prototype._i = looseIndexOf; 501 | // 静态树 502 | Vue.prototype._m = renderStatic; 503 | // filters 504 | Vue.prototype._f = resolveFilter; 505 | // keyCode 506 | Vue.prototype._k = checkKeyCodes; 507 | // v-bind 508 | Vue.prototype._b = bindObjectProps; 509 | // 纯文本虚拟节点 510 | Vue.prototype._v = createTextVNode; 511 | // 空虚拟节点 512 | Vue.prototype._e = createEmptyVNode; 513 | Vue.prototype._u = resolveScopedSlots; 514 | } 515 | ``` 516 | 517 | 518 | 519 | -------------------------------------------------------------------------------- /10-_init.md: -------------------------------------------------------------------------------- 1 | # 初始化 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ## initProvide 10 | 11 | ```javascript 12 | function initProvide(vm){ 13 | var provide = vm.$options.provide; 14 | if(provide){ 15 | vm._provided = typeof provide === 'function' ? 16 | provide.call(vm) : provide; 17 | } 18 | } 19 | ``` 20 | 21 | 22 | 23 | --- 24 | 25 | 26 | 27 | ## initInjections 28 | 29 | ```javascript 30 | function initInjections(vm){ 31 | var inject = vm.$options.inject; 32 | if(inject){ 33 | var isArray = Array.isArray(inject); 34 | // inject是数组 => inject 35 | // inject不是数组 && 支持ES6 => Reflect.ownKeys(inject) => 返回inject自身属性(包括symbol) 36 | // inject不是数组 && 不支持ES6 => Object.keys(inject) 37 | var keys = isArray ? 38 | inject : hasSymbol ? 39 | Reflect.ownKeys(inject) : Object.keys(inject); 40 | 41 | var loop = function(i){ 42 | var key = keys[i]; 43 | var provideKey = isArray ? key : inject[key]; 44 | var source = vm; 45 | while(source){ 46 | if(source._provided && provideKey in source._provided){ 47 | defineReactive$$1(vm, key, source._provided[provideKey], function(){ 48 | warn( 49 | 'Avoid mutating an injected value directly since the changes will be ' + 50 | 'overwritten whenever the provided component re-renders. ' + 51 | 'injection being mutated: "' + key + '"', 52 | vm 53 | ); 54 | }); 55 | break; 56 | } 57 | source = source.$parent; 58 | } 59 | }; // loop 60 | for(var i = 0; i < keys.length; i++) loop(i); 61 | } 62 | } 63 | ``` 64 | 65 | 66 | 67 | --- 68 | 69 | 70 | 71 | ## initMixin 72 | 73 | ```javascript 74 | var uid = 0; // 记录vue实例个数 75 | function initMixin(Vue){ 76 | Vue.prototype._init = function(options){ 77 | var vm = this; 78 | vm._uid = uid++; 79 | 80 | var startTag, endTag; 81 | if('development' !== 'production' && config.performance && mark){ 82 | startTag = 'vue-pref-init:' + (vm._uid); 83 | endTag = 'vue-pref-end:' + (vm._uid); 84 | mark(startTag); 85 | } 86 | 87 | // 表示是vue实例 避免被observe 88 | vm._isVue = true; 89 | // 合并参数 90 | if(options && options._isComponent){ 91 | // 优化内部组件实例化 92 | initInternalComponent(vm, options); 93 | } else{ 94 | vm.$options = mergeOptions( 95 | resolveConstructorOptions(vm.constuctor), 96 | options || {}, 97 | vm 98 | ); 99 | } 100 | 101 | // 小分支 102 | initProxy(vm); 103 | 104 | // 属性对自身的引用 105 | vm._self = vm; 106 | 107 | // 初始化 108 | initLifecycle(vm); 109 | initEvents(vm); 110 | initRender(vm); 111 | callHook(vm, 'beforeCreate'); 112 | initInjections(vm); 113 | initState(vm); 114 | initProvide(vm); 115 | callHook(vm, 'created'); 116 | 117 | if('development' !== 'production' && config.performance && mark){ 118 | vm._name = formatComponentName(vm, false); 119 | mark(endTag); 120 | measure(((vm._name) + 'init'), startTag, endTag); 121 | } 122 | 123 | if(vm.$options.el){ 124 | vm.$mount(vm.$options.el); 125 | } 126 | }; 127 | } 128 | ``` 129 | 130 | 131 | 132 | --- 133 | 134 | 135 | 136 | ## initInternalComponent 137 | 138 | ```javascript 139 | function initInternalComponent(vm, options){ 140 | // 这个原型对象真是长又长 141 | var opts = vm.$options = Object.create(vm.constructor.options); 142 | // 比动态枚举快 143 | // 应该是指的for-in 144 | opts.parent = options.parent; 145 | opts.propsData = options.propsData; 146 | opts._parentVnode = options._parentVnode; 147 | opts._parentListeners = options._parentListeners; 148 | opts._renderChildren = options._renderChildren; 149 | opts._componentTag = options._componentTag; 150 | opts._parentElm = options._parentElm; 151 | opts._refElm = options._refElm; 152 | if(options.render){ 153 | opts.render = options.render; 154 | opts.staticRenderFns = options.staticRenderFns; 155 | } 156 | } 157 | ``` 158 | 159 | 160 | 161 | --- 162 | 163 | 164 | 165 | ## resolveConstructorOptions 166 | 167 | ```javascript 168 | function resolveConstrutorOptions(Ctor){ 169 | var modified; 170 | var latest = Ctor.options; 171 | // 密封 172 | var sealed = Ctor.sealedOptions; 173 | for(var key in latest){ 174 | if(latest[key] !== sealed[key]){ 175 | if(!modified) { modified = {}; } 176 | modified[key] = dedupe(latest[key], sealed[key]); 177 | } 178 | } 179 | return modified; 180 | } 181 | ``` 182 | 183 | 184 | 185 | ### dudepe 186 | 187 | ```javascript 188 | function dudepe(latest, sealed){ 189 | // 比较两者防止lifeCycle钩子不会重复调用 190 | if(Array.isArray(latest)){ 191 | var res = []; 192 | sealed = Array.isArray(sealed) ? sealed : [sealed]; 193 | for(var i = 0; i < latest.length; i++){ 194 | if(sealed.indexOf(latest[i]) < 0){ 195 | res.push(latest[i]); 196 | } 197 | } 198 | return res; 199 | } else{ 200 | return latest; 201 | } 202 | } 203 | ``` 204 | 205 | 206 | 207 | --- 208 | 209 | 210 | 211 | ## vue$3 212 | 213 | ```javascript 214 | function Vue$3(options){ 215 | if('development' !== 'production' && !(this instanceof Vue$3)){ 216 | warn('Vue is a constructor and should be called with the "new" keyword'); 217 | } 218 | this._init(options); 219 | } 220 | ``` 221 | 222 | 223 | 224 | --- 225 | 226 | 227 | 228 | ## 原型方法混入 229 | 230 | ```javascript 231 | initMixin(Vue$3); 232 | stateMixin(Vue$3); 233 | eventsMixin(Vue$3); 234 | lifecycleMixin(vue$3); 235 | renderMixin(Vue$3); 236 | ``` 237 | 238 | 239 | 240 | --- 241 | 242 | 243 | 244 | ## initUse 245 | 246 | ```javascript 247 | // 第三方插件 248 | function initUse(Vue){ 249 | Vue.use = function(plugin){ 250 | if(plugin.installed){ 251 | return ; 252 | } 253 | // 参数处理 254 | var args = toArray(arguments, 1); 255 | args = unshift(this); 256 | if(typeof plugin.install === 'function'){ 257 | plugin.install.apply(plugin, args); 258 | } else if(typeof plugin === 'function'){ 259 | plugin.apply(null, args); 260 | } 261 | plugin.installed = true; 262 | return this; 263 | } 264 | } 265 | ``` 266 | 267 | 268 | 269 | --- 270 | 271 | 272 | 273 | ## initMixin$1 274 | 275 | ```javascript 276 | function initMixin$1(Vue){ 277 | Vue.mixin = function(mixin){ 278 | this.options = mergeOptions(this.options, mixin); 279 | }; 280 | } 281 | ``` 282 | 283 | 284 | 285 | --- 286 | 287 | 288 | 289 | ## initExtend 290 | 291 | ```javascript 292 | function initExtend(Vue){ 293 | Vue.cid = 0; 294 | var cid = 1; 295 | 296 | // 类继承 297 | Vue.extend = function(extendOptions){ 298 | extendOptions = exntendOptions || {}; 299 | var Super = this; 300 | var SuperId = Super.cid; 301 | var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {}); 302 | if(cachedCtors[SuperId]){ 303 | return cachedCtors[SuperId]; 304 | } 305 | 306 | var name = extendOptions.name || Super.options.name; 307 | // 命名检测 308 | if(!/^[a-zA-Z][\w-]*$/.test(name)){ 309 | warn( 310 | 'Invalid component name: "' + name + '". Component names' + 311 | 'can only contain alphanumeric characters and the hyphen, ' + 312 | 'and must start with a letter.' 313 | ); 314 | } 315 | 316 | var Sub = function VueComponent(options){ 317 | this._init(options); 318 | }; 319 | Sub.prototype = Object.create(Super.prototype); 320 | Sub.prototype.constructor = Sub; 321 | Sub.cid = cid++; 322 | Sub.options = mergeOptions( 323 | Super.options, 324 | extendOptions 325 | ); 326 | Sub['super'] = Super; 327 | 328 | // 329 | if(Sub.options.props){ 330 | initProps$1(Sub); 331 | } 332 | if(Sub.options.computed){ 333 | initComputed$1(Sub); 334 | } 335 | 336 | // 扩展 337 | Sub.extend = Super.extend; 338 | Sub.mixin = Super.mixin; 339 | Sub.use = Super.use; 340 | 341 | // 创建私有值 342 | config._assetTypes.forEach(function(type){ 343 | Sub[type] = Super[type]; 344 | }); 345 | 346 | // 自检查递归 347 | if(name){ 348 | Sub.options.components[name] = Sub; 349 | } 350 | 351 | // 352 | Sub.superOptions = Super.options; 353 | Sub.extendOptions = extendOptions; 354 | Sub.sealedOptions = extend({}, Sub.options); 355 | 356 | // 缓存构造函数 357 | cachedCtors[SuperId] = Sub; 358 | return Sub; 359 | }; 360 | } 361 | ``` 362 | 363 | 364 | 365 | ### initProps$1 366 | 367 | ```javascript 368 | function initProps$1(Comp){ 369 | var props = Comp.options.props; 370 | for(var key in props){ 371 | proxy(Comp.prototype, '_props_', key); 372 | } 373 | } 374 | ``` 375 | 376 | 377 | 378 | ### initComputed$1 379 | 380 | ```javascript 381 | function initComputed$1(Comp){ 382 | var computed = Comp.options.computed; 383 | for(var key in computed){ 384 | defineComputed(Comp.prototype, key, computed[key]); 385 | } 386 | } 387 | ``` 388 | 389 | 390 | 391 | --- 392 | 393 | 394 | 395 | ## initAssetRegisters 396 | 397 | ```javascript 398 | function initAssetRegisters(Vue){ 399 | config._assetTypes.forEach(function(type){ 400 | Vue[type] = function(id, definition){ 401 | if(!definition){ 402 | return this.options[type + 's'][id]; 403 | } else{ 404 | // 不要用内置或保留HTML标签做组件ID 405 | if(type === 'component' && config.isReservedTag(id)){ 406 | warn( 407 | 'Do not use bult-in or reserved HTML elements as component ' + 408 | 'id: ' + id 409 | ); 410 | } 411 | if(type === 'component' && isPlainObject(definition)){ 412 | definition.name = definition.name || id; 413 | definition = this.options._base.extend(definition); 414 | } 415 | if(type === 'directive' && typeof definition === 'function'){ 416 | definition = { bind: definition, update: definition }; 417 | } 418 | this.options[type + 's'][id] = definition; 419 | return definition; 420 | } 421 | }; 422 | }); 423 | } 424 | ``` 425 | 426 | 427 | 428 | --- 429 | 430 | 431 | 432 | ## patternTypes 433 | 434 | ```javascript 435 | var patternTypes = [String, RegExp]; 436 | ``` 437 | 438 | 439 | 440 | --- 441 | 442 | 443 | 444 | ## getComponentName 445 | 446 | ```javascript 447 | function getComponentName(opts){ 448 | return opts && (opts.Ctor.options.name || opts.tag); 449 | } 450 | ``` 451 | 452 | 453 | 454 | --- 455 | 456 | 457 | 458 | ## matches 459 | 460 | ```javascript 461 | function matches(pattern, name){ 462 | // 测试对应pattern是否包含name 463 | if(typeof pattern === 'string'){ 464 | return pattern.split(',').indexOf(name) > -1; 465 | } else if(pattern instanceof RegExp){ 466 | return pattern.test(name); 467 | } 468 | return false; 469 | } 470 | ``` 471 | 472 | 473 | 474 | --- 475 | 476 | 477 | 478 | ## pruneCache 479 | 480 | ```javascript 481 | function pruneCache(cache, filter){ 482 | for(var key in cache){ 483 | var cachedNode = cache[key]; 484 | if(cachedNode){ 485 | var name = getComponentName(cachedNode.componentOptions); 486 | if(name && !filter(name)){ 487 | pruneCacheEntry(cachedNode); 488 | cache[key] = null; 489 | } 490 | } 491 | } 492 | } 493 | ``` 494 | 495 | 496 | 497 | --- 498 | 499 | 500 | 501 | ## pruneCacheEntry 502 | 503 | ```javascript 504 | function pruneCacheEntry(vnode){ 505 | if(vnode){ 506 | if(!vnode.componentInstance._inactive){ 507 | callHook(vnode.componentInstance, 'deactivated'); 508 | } 509 | vnode.componentInstance.$destroy(); 510 | } 511 | } 512 | ``` 513 | 514 | 515 | 516 | --- 517 | 518 | 519 | 520 | ## KeepAlive 521 | 522 | ```javascript 523 | var KeepAlive = { 524 | name:'keep-alive', 525 | abstract:true, 526 | 527 | props:{ 528 | include: patternTypes, 529 | exclude: patternTypes 530 | }, 531 | 532 | created: function created(){ 533 | this.cache = Object.create(null); 534 | }, 535 | 536 | destroyed: function destroyed(){ 537 | var this$1 = this; 538 | for(var key in this$1.cache[key]){ 539 | pruneCacheEntry(this$1.cache[key]); 540 | } 541 | }, 542 | 543 | watch: { 544 | include: function include(val){ 545 | pruneCache(this.cache, function(name){ return matches(val, name); }); 546 | }, 547 | exclude: function exclude(val){ 548 | pruneCache(this.cache, function(name){ return !matches(val, name); }); 549 | } 550 | }, 551 | 552 | render: function render(){ 553 | var vnode = getFirstComponentChild(this.$slot.default); 554 | var componentOptions = vnode && vnode.componentOptions; 555 | if(componentOptions){ 556 | // 检查类型 557 | var name = getComponentName(componentOptions); 558 | if(name && ( 559 | (this.include && !matches(this.include, name)) || 560 | (this.exclude && matches(this.exclude, name)) 561 | )){ 562 | return vnode; 563 | } 564 | // 3种情况 565 | // 1.vnode.key 566 | // 2.componentOptions.Ctor.cid::componentOptions.tag 567 | // 3.componentOptions.Ctor.cid 568 | var key = vnode.key == null ? 569 | componentOptions.Ctor.cid + (componentOptions.tag ? 570 | ('::' + (componentOptions.tag)) : '') : vnode.key; 571 | if(this.cache[key]){ 572 | vnode.componentInstance = this.cache[key].componentInstance; 573 | } else{ 574 | this.cache[key] = vnode; 575 | } 576 | vnode.data.keepAlive = true; 577 | } 578 | return vnode; 579 | } 580 | }; 581 | ``` 582 | 583 | 584 | 585 | --- 586 | 587 | 588 | 589 | ## builtInComponents 590 | 591 | ```javascript 592 | var builtInComponents = { 593 | KeepAlive: KeepAlive 594 | }; 595 | ``` 596 | 597 | 598 | 599 | --- 600 | 601 | 602 | 603 | ## initGlobalAPI 604 | 605 | ```javascript 606 | function initGlobalAPI(Vue){ 607 | var configDef = {}; 608 | configDef.get = function(){ return config; }; 609 | // Vue.config对象不可更改 610 | configDef.set = function(){ 611 | warn( 612 | 'Do not replace the Vue.config object, set individual fields instead.' 613 | ); 614 | }; 615 | Object.defineProperty(Vue, 'config', configDef); 616 | 617 | // 暴露通用方法 618 | // 非公开API 禁用 619 | Vue.util = { 620 | warn: warn, 621 | extend: extend, 622 | mergeOptions: mergeOptions, 623 | defineReactive: defineReactive$$1 624 | }; 625 | 626 | // 原型方法 627 | Vue.set = set; 628 | Vue.delete = del; 629 | Vue.nextTick = nextTick; 630 | 631 | Vue.options = Object.create(null); 632 | config._assetTypes.forEach(function(type){ 633 | Vue.options[type + 's'] = Object.create(null); 634 | }); 635 | 636 | // Vue.options._base.set = Vue.$set 637 | // ... 638 | Vue.options._base = Vue; 639 | 640 | extend(Vue.options.components, buildInComponents); 641 | 642 | initUse(Vue); 643 | initMixin$1(Vue); 644 | initExtend(Vue); 645 | initAssetRegisters(Vue); 646 | } 647 | 648 | // 初始化全局API 649 | initGlobalAPI(vue$3); 650 | 651 | Object.defineProperty(Vue$3.prototype, 'isServer', { 652 | get: isServerRendering 653 | }); 654 | 655 | vue$3.version = '2.2.6'; 656 | ``` 657 | 658 | 659 | 660 | --- 661 | 662 | 663 | 664 | 665 | 666 | -------------------------------------------------------------------------------- /11-check&opts.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ## makeMap 10 | 11 | ```javascript 12 | var acceptValue = makeMap('input,textarea,option,select'); 13 | 14 | // 标签名、属性是否对应 15 | var mustUseProp = function(tag, type, attr){ 16 | return ( 17 | (attr === 'value' && acceptValue(tag)) && type !== 'button' || 18 | (attr === 'selected' && tag === 'option') || 19 | (attr === 'checked' && tag === 'input') || 20 | (attr === 'muted' && tag === 'video') 21 | ) 22 | }; 23 | 24 | // 枚举属性 25 | var isEnumeratedAttr = makeMap('contenteditable,draggable,spellcheck'); 26 | 27 | // 布尔值的属性 28 | var isBooleanAttr = makeMap( 29 | 'allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,' + 30 | 'default,defaultchecked,defaultmuted,defaultselected,defer,disabled,' + 31 | 'enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,' + 32 | 'muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,' + 33 | 'required,reversed,scoped,seamless,selected,sortable,translate,' + 34 | 'truespeed,typemustmatch,visible' 35 | ); 36 | 37 | var xlinkNS = 'http://www.w3.org/1999/xlink'; 38 | ``` 39 | 40 | 41 | 42 | --- 43 | 44 | 45 | 46 | ## Xlink 47 | 48 | 49 | 50 | ### isXlink 51 | 52 | ```javascript 53 | // 字符串形式为 => xlink: 54 | var isXlink = function(name){ 55 | return name.charAt(5) === ':' && name.slice(0,5) === 'xlink'; 56 | } 57 | ``` 58 | 59 | 60 | 61 | --- 62 | 63 | 64 | 65 | ### getXlinkProp 66 | 67 | ```javascript 68 | // 冒号后面的内容 69 | var getXlinkProp = function(name){ 70 | return isXlink(name) ? name.slice(6, name.length) : ''; 71 | } 72 | ``` 73 | 74 | 75 | 76 | --- 77 | 78 | 79 | 80 | ### isFalsyAttrValue 81 | 82 | ```javascript 83 | // val是null、undefined、false 84 | var isFalsyAttrValue = function(val){ 85 | return val == null || val == false; 86 | } 87 | ``` 88 | 89 | 90 | 91 | --- 92 | 93 | 94 | 95 | ## genClassForVnode 96 | 97 | ```javascript 98 | // 给虚拟节点生成class 99 | function genClassForVnode(vnode){ 100 | var data = vnode.data; 101 | var parentNode = vnode; 102 | var childNode = vnode; 103 | while(childNode.componentInstance){ 104 | childNode = childNode.componentInstance._vnode; 105 | if(childNode.data){ 106 | data = mergeClassData(childNode.data, data); 107 | } 108 | } 109 | while((parentNode = parentNode.parent)){ 110 | if(parentNode.data){ 111 | data = mergeClassData(data, parentNode.data); 112 | } 113 | } 114 | return genClassFromData(data); 115 | } 116 | ``` 117 | 118 | 119 | 120 | ### mergeClassData 121 | 122 | ```javascript 123 | // 返回一个对象 124 | function mergeClassData(child, parent){ 125 | return { 126 | staticClass: concat(child.staticClass, parent.staticClass), 127 | class: child.class ? [child.class, parent.class] : parent.class 128 | } 129 | } 130 | ``` 131 | 132 | 133 | 134 | ### genClassFromData 135 | 136 | ```javascript 137 | function genClassFromData(data){ 138 | var dynamicClass = data.class; 139 | var staticClass = data.staticClass; 140 | if(staticClass || dynamicClass){ 141 | return concat(staticClass, stringifyClass(dynamicClass)); 142 | } 143 | return ''; 144 | } 145 | ``` 146 | 147 | 148 | 149 | ### concat 150 | 151 | ```javascript 152 | // a,b => 'a b' 153 | // a => a 154 | // b => b 155 | // !a,!b => '' 156 | function concat(a, b){ 157 | return a ? b ? (a + ' ' + b) : a : (b || ''); 158 | } 159 | ``` 160 | 161 | 162 | 163 | ### stringifyClass 164 | 165 | ```javascript 166 | function stringifyClass(value){ 167 | var res = ''; 168 | if(!value){ 169 | return res; 170 | } 171 | // 字符串直接返回 172 | if(typeof value === 'string'){ 173 | return value; 174 | } 175 | // 数组 176 | if(Array.isArray(value)){ 177 | var stringified; 178 | for(var i = 0; l = value.length; i < l; i++){ 179 | if(value[i]){ 180 | // 递归解析 181 | if((stringified = stringifyClass(value[i]))){ 182 | res += stringified + ' '; 183 | } 184 | } 185 | } 186 | // 去掉末尾的空格 187 | return res.slice(0,-1); 188 | } 189 | // 对象 190 | if(isObject(value)){ 191 | for(var key in value){ 192 | // 键拼接 193 | if(value[key]) { res += key + ' '; } 194 | } 195 | return res.slice(0, -1); 196 | } 197 | return res; 198 | } 199 | ``` 200 | 201 | 202 | 203 | --- 204 | 205 | ## is 206 | 207 | 208 | 209 | ### namespaceMap 210 | 211 | ```javascript 212 | var namespaceMap = { 213 | svg: 'http://www.w3.org/2000/svg', 214 | math: 'http://www/w3/org/1998/Math/MathML' 215 | } 216 | ``` 217 | 218 | 219 | 220 | ### isHTMLTag 221 | 222 | ```javascript 223 | var isHTMLTag = makeMap( 224 | 'html,body,base,head,link,meta,style,title,' + 225 | 'address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,' + 226 | 'div,dd,dl,dt,figcaption,figure,hr,img,li,main,ol,p,pre,ul,' + 227 | 'a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,' + 228 | 's,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,' + 229 | 'embed,object,param,source,canvas,script,noscript,del,ins,' + 230 | 'caption,col,colgroup,table,thead,tbody,td,th,tr,' + 231 | 'button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,' + 232 | 'output,progress,select,textarea,' + 233 | 'details,dialog,menu,menuitem,summary,' + 234 | 'content,element,shadow,template' 235 | ); 236 | ``` 237 | 238 | 239 | 240 | ### isSVG 241 | 242 | ```javascript 243 | var isSVG = makeMap( 244 | 'svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font-face,' + 245 | 'foreignObject,g,glyph,image,line,marker,mask,missing-glyph,path,pattern,' + 246 | 'polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view',true 247 | ); 248 | ``` 249 | 250 | 251 | 252 | ### isPreTag 253 | 254 | ```javascript 255 | var isPreTag = function(tag) { return tag === 'pre'; }; 256 | ``` 257 | 258 | 259 | 260 | ### isReservedTag 261 | 262 | ```javascript 263 | // 内置tag 264 | var isReservedTag = function(tag){ 265 | return isHTMLTag(tag) || isSVG(tag); 266 | }; 267 | ``` 268 | 269 | 270 | 271 | ### getTagNamespace 272 | 273 | ```javascript 274 | function getTagNamespace(tag){ 275 | if(isSVG(tag)){ 276 | return 'svg'; 277 | } 278 | // 279 | if(tag === 'math'){ 280 | return 'math'; 281 | } 282 | } 283 | ``` 284 | 285 | 286 | 287 | ### unknownElementCache 288 | 289 | ```javascript 290 | var unknownElementCache = Object.create(null); 291 | ``` 292 | 293 | 294 | 295 | ### isUnknownElement 296 | 297 | ```javascript 298 | function isUnknownElement(tag){ 299 | if(!inBrower){ 300 | return true; 301 | } 302 | if(isReservedTag){ 303 | return false; 304 | } 305 | tag = tag.toLowerCase(); 306 | // 取缓存 307 | if(unknownElementCache[tag] != null){ 308 | return unknownElementCache[tag]; 309 | } 310 | var el = document.createElement(tag); 311 | // 包含'-' 312 | if(tag.indexOf('-') > -1){ 313 | return (unknownElementCache[tag] = ( 314 | el.constructor === window.HTMLUnknownElement || 315 | el.constructor === window.HTMLElement 316 | )); 317 | } else{ 318 | return (unknownElementCache[tag] = /HTMLUnknownElement/.test(el.toString())); 319 | } 320 | } 321 | ``` 322 | 323 | 324 | 325 | ## query 326 | 327 | ```javascript 328 | // 返回el选中的DOM节点 329 | function query(el){ 330 | if(typeof el === 'string'){ 331 | var selected = document.querySelector(el); 332 | if(!selected){ 333 | 'development' !== 'production' && warn( 334 | 'Cannot find element: ' + el 335 | ); 336 | return document.createElement('div'); 337 | } 338 | return selected; 339 | } else{ 340 | return el; 341 | } 342 | } 343 | ``` 344 | 345 | 346 | 347 | --- 348 | 349 | 350 | 351 | ## nodeOps 352 | 353 | 354 | 355 | ```javascript 356 | var nodeOps = Object.freeze({ 357 | createElement: createElement$1, 358 | createElementNS: createElementNS, 359 | createTextNode: createTextNode, 360 | createComment: createComment, 361 | insertBefore: insertBefore, 362 | removeChild: removeChild, 363 | appendChild: appendChild, 364 | parentNode: parentNode, 365 | nextSibling: nextSibling, 366 | tagName: tagName, 367 | setTextContent: setTextContent, 368 | setAttribute: setAttribute 369 | }) 370 | ``` 371 | 372 | 373 | 374 | ### createElement$1 375 | 376 | ```javascript 377 | function createElement$1(tagName, vnode){ 378 | var elm = document.createElement(tagName); 379 | if(tagName !== 'select'){ 380 | return elm; 381 | } 382 | if(vnode.data && vnode.data.attrs && vnode.data.attrs.multiple !== undefined){ 383 | elm.setAttribute('multiple','multiple'); 384 | } 385 | return elm; 386 | } 387 | ``` 388 | 389 | 390 | 391 | ### createElementNS 392 | 393 | ```javascript 394 | function createElementNS(namespace, tagName){ 395 | return document.createElementNS(namespaceMap[namespace], tagName); 396 | } 397 | ``` 398 | 399 | 400 | 401 | ### createTextNode 402 | 403 | ```javascript 404 | function createTextNode(text){ 405 | return document.createTextNode(text); 406 | } 407 | ``` 408 | 409 | 410 | 411 | ### createComment 412 | 413 | ```javascript 414 | function createComment(text){ 415 | return document.createComment(text); 416 | } 417 | ``` 418 | 419 | 420 | 421 | ### insertBefore 422 | 423 | ```javascript 424 | function insertBefore(parentNode, newNode, referenceNode){ 425 | parentNode.insertBefore(newNode, referenceNode); 426 | } 427 | ``` 428 | 429 | 430 | 431 | ### removeChild 432 | 433 | ```javascript 434 | function removeChild(node, child){ 435 | node.removeChild(child); 436 | } 437 | ``` 438 | 439 | 440 | 441 | ### appendChild 442 | 443 | ```javascript 444 | function appendChild(node, child){ 445 | node.appendChild(child); 446 | } 447 | ``` 448 | 449 | 450 | 451 | ### parentNode 452 | 453 | ```javascript 454 | function parentNode(node){ 455 | return node.parentNode; 456 | } 457 | ``` 458 | 459 | 460 | 461 | ### nextSibling 462 | 463 | ```javascript 464 | function nextSibling(node){ 465 | return node.parentNode; 466 | } 467 | ``` 468 | 469 | 470 | 471 | ### tagName 472 | 473 | ```javascript 474 | function tagName(node){ 475 | return node.tagName; 476 | } 477 | ``` 478 | 479 | 480 | 481 | ### setTextContent 482 | 483 | ```javascript 484 | function setTextContent(node, text){ 485 | node.textContent = text; 486 | } 487 | ``` 488 | 489 | 490 | 491 | ### setAttribute 492 | 493 | ```javascript 494 | function setAttribute(node, key, val){ 495 | node.setAttribute(key, val); 496 | } 497 | ``` 498 | 499 | 500 | 501 | ## ref 502 | 503 | 504 | 505 | ```javascript 506 | var ref = { 507 | create: function create(_, vnode){ 508 | registerRef(vnode); 509 | }, 510 | update: function update(oldVnode, vnode){ 511 | if(oldVnode.data.ref !== vnode.data.ref){ 512 | registerRef(oldVnode, true); 513 | registerRef(vnode); 514 | } 515 | }, 516 | destroy: function destroy(vnode){ 517 | registerRef(vnode, true); 518 | } 519 | }; 520 | ``` 521 | 522 | 523 | 524 | ### registerRef 525 | 526 | ```javascript 527 | function registerRef(vnode, isRemoval){ 528 | var key = vnode.data.ref; 529 | if(!key) { return } 530 | var vm = vnode.context; 531 | var ref = vnode.componentInstance || vnode.elm; 532 | var refs = vm.$refs; 533 | if(isRemoval){ 534 | if(Array.isArray(refs[key])){ 535 | remove(refs[key], ref); 536 | } else if(refs[key] === ref){ 537 | refs[key] = undefined; 538 | } 539 | } else{ 540 | if(vnode.data.refInFor){ 541 | if(Array.isArray(refs[key]) && refs[key].indexOf(ref) < 0){ 542 | refs[key].push(ref); 543 | } else{ 544 | refs[key] = [ref]; 545 | } 546 | } else{ 547 | refs[key] = ref; 548 | } 549 | } 550 | } 551 | ``` 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | -------------------------------------------------------------------------------- /12-VirtualDom_algorithm.md: -------------------------------------------------------------------------------- 1 | # Virtual DOM patching algorithm 2 | 3 | 4 | 5 | 6 | 7 | ```javascript 8 | var emptyNode = new VNode('', {}, []); 9 | 10 | var hooks = ['create', 'activate', 'update', 'remove', 'destroy']; 11 | 12 | function isUndef(v){ 13 | return v === undefined || v === null; 14 | } 15 | 16 | function isDef(v){ 17 | return v !== undefined && v !== null; 18 | } 19 | 20 | function isTrue(v){ 21 | return v === true; 22 | } 23 | ``` 24 | 25 | 26 | 27 | ### sameVnode 28 | 29 | ```javascript 30 | function sameVnode(a, b){ 31 | return ( 32 | a.key === b.key && 33 | a.tag === b.tag && 34 | a.isComment === b.isComment && 35 | isDef(a.data) === isDef(b.data) && 36 | sameInputType(a, b) 37 | ); 38 | } 39 | ``` 40 | 41 | 42 | 43 | ### sameInputType 44 | 45 | ```javascript 46 | function sameInputType(a, b){ 47 | if(a.tag !== 'input'){ return true } 48 | var i; 49 | // a.data.attrs.types 50 | var typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type; 51 | var typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type; 52 | return typeA === typeB; 53 | } 54 | ``` 55 | 56 | 57 | 58 | ### createKeyToOldIdx 59 | 60 | ```javascript 61 | function createKeyToOldIdx(children, beginIdx, endIdx){ 62 | var i, key; 63 | var map = {}; 64 | for(i = beginIdx; i < endIdx; ++i){ 65 | key = children[i].key; 66 | if(isDef(key)){ map[key] = i; } 67 | } 68 | return map; 69 | } 70 | ``` 71 | 72 | 73 | 74 | ### createPatchFunction 75 | 76 | ```javascript 77 | function createPatchFunction(backend){ 78 | var i, j; 79 | var cbs = {}; 80 | 81 | var modules = backend.modules; 82 | var nodeOps = backend.nodeOps; 83 | 84 | for(var i = 0; i < hooks.length; ++i){ 85 | cbs[hook[i]] = []; 86 | for(j = 0; j < modules.length; ++j){ 87 | if(isDef(modules[j][hooks[i]])){ 88 | cbs[hooks[i]].push(modules[j][hooks[i]]); 89 | } 90 | } 91 | } 92 | 93 | // emptyNodeAt 94 | function emptyNodeAt(elm){ 95 | return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm); 96 | } 97 | 98 | // createRmcb 99 | function createRmcb(childElm, listeners){ 100 | function remove$$1(){ 101 | if(--remove$$1.listeners === 0){ 102 | removeNode(childElm); 103 | } 104 | } 105 | remove$$1.listeners = listeners; 106 | return remove$$1; 107 | } 108 | 109 | // 获取父元素移除该DOM节点 110 | function removeNode(el){ 111 | var parent = nodeOps.parentNode(el); 112 | if(isDef(parent)){ 113 | nodeOps.removeChild(parent, el); 114 | } 115 | } 116 | 117 | var inPre = 0; 118 | 119 | // createElm 120 | function createElm(vnode, insertedVnodeQueue, parentElm, refElm, nested){ 121 | vnode.isRootInsert = !nested; 122 | if(createComponent(vnode, insertedVnodeQueue, parentElm, refElm)){ 123 | return ; 124 | } 125 | 126 | var data = vnode.data; 127 | var children = vnode.children; 128 | var tag = vnode.tag; 129 | if(isDef(tag)){ 130 | if(data && data.pre){ 131 | inPre++; 132 | } 133 | if(!inPre && !vnode.ns && 134 | !(config.ignoredElements.length && config.ignoredElements.indexOf(tag) > -1) && 135 | config.isUnknownElement(tag) 136 | ){ 137 | warn( 138 | 'Unknown custom element: <' + tag + '> - did you ' + 139 | 'register the component correctly? For recursive components, ' + 140 | 'make sure to provide the "name" option.', 141 | vnode.context 142 | ); 143 | } 144 | vnode.elm = vnode.ns ? 145 | nodeOps.createElementNS(vnode.ns, tag) : 146 | nodeOps.createElement(tag, vnode); 147 | setScope(vnode); 148 | 149 | createChildren(vnode, children, insertedVnodeQueue); 150 | if(isDef(data)){ 151 | invokeCreateHooks(vnode, insertedVnodeQueue); 152 | } 153 | insert(parentElm, vnode.elm, refElm); 154 | 155 | if('development' !== 'production' && data && data.pre){ 156 | inPre--; 157 | } 158 | } else if(isTrue(vnode.isComment)){ 159 | vnode.elm = nodeOps.createComment(vnode.text); 160 | insert(parentElm, vnode.elm, refElm); 161 | } else{ 162 | vnode.elm = nodeOps.createTextNode(vnode.text); 163 | insert(parentElm, vnode.elm, refElm); 164 | } 165 | } 166 | 167 | // createComponent 168 | function createComponent(vnode, insertedVnodeQueue, parentElm, refElm){ 169 | var i = vnode.data; 170 | if(isDef(i)){ 171 | var isReactivated = isDef(vnode.componentInstance) && i.keepAlive; 172 | if(isDef(i = i.hook) && isDef(i = i.init)){ 173 | i(vnode, false, parentElm, refElm); 174 | } 175 | 176 | // 177 | if(isDef(vnode.componentInstance)){ 178 | initComponent(vnode, insertedVnodeQueue); 179 | if(isTrue(isReactivated)){ 180 | reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm); 181 | } 182 | return true; 183 | } 184 | } 185 | } 186 | 187 | // initComponent 188 | function initComponent(vnode, insertedVnodeQueue){ 189 | if(isDef(vnode.data.pendingInsert)){ 190 | insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert); 191 | } 192 | vnode.elm = vnode.componentInstance.$el; 193 | if(isPatchable(vnode)){ 194 | invokeCreateHooks(vnode, insertedVnodeQueue); 195 | setScope(vnode); 196 | } else{ 197 | registerRef(vnode); 198 | insertedVnodeQueue.push(vnode); 199 | } 200 | } 201 | 202 | // reactivateComponent 203 | function reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm){ 204 | var i; 205 | // 206 | var innerNode = vnode; 207 | while(innerNode.componentInstance){ 208 | innerNode = innerNode.componentInstance._vnode; 209 | if(isDef(i = innerNode.data) && isDef(i = i.transition)){ 210 | for(i = 0; i < cbs.activate.length; ++i){ 211 | cbs.activate[i](emptyNode, innerNode); 212 | } 213 | insertedVnodeQueue.push(innerNode); 214 | break; 215 | } 216 | } 217 | insert(parentElm, vnode.elm, refElm); 218 | } 219 | 220 | // insert 221 | function insert(parent, elm, ref){ 222 | if(isDef(parent)){ 223 | if(isDef(ref)){ 224 | nodeOps.insertBefore(parent, elm, ref); 225 | } else{ 226 | nodeOps.appendChild(parent, elm); 227 | } 228 | } 229 | } 230 | 231 | // createChildren 232 | function createChildren(vnode, children, insertedVnodeQueue){ 233 | if(Array.isArray(children)){ 234 | for(var i = 0; i < children.length; ++i){ 235 | createElm(children[i], insertedVnodeQueue, vnode.elm, null, true); 236 | } 237 | } else if(isPrimitive(vnode.text)){ 238 | nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(vnode.text)); 239 | } 240 | } 241 | 242 | // isPatchable 243 | function isPatchable(vnode){ 244 | while(vnode.componentInstance){ 245 | vnode = vnode.componentInstance._vnode; 246 | } 247 | return isDef(vnode.tag); 248 | } 249 | 250 | // invokeCreateHooks 251 | function invokeCreateHooks(vnode, insertedVnodeQueue){ 252 | for(var i$1 = 0; i$1 < cbs.create.length; ++i$1){ 253 | cbs.create[i$1](emptyNode, vnode); 254 | } 255 | i = vnode.data.hook; 256 | if(isDef(i)){ 257 | if(isDef(i.create)){ i.create(empty, vnode); } 258 | if(isDef(i.insert)){ insertedVnodeQueue.push(vnode); } 259 | } 260 | } 261 | 262 | // setScope 263 | function setScope(vnode){ 264 | var i; 265 | var ancestor = vnode; 266 | while(ancestor){ 267 | if(isDef(i = ancestor.context) && isDef(i = i.$options._scopeId)){ 268 | nodeOps.setAttribute(vnode.elm, i, ''); 269 | } 270 | ancestor = ancestor.parent; 271 | } 272 | 273 | // slot的内容也必须含有scopeId 274 | if(isDef(i = activeInstance) && i !== vnode.context && isDef(i = i.$options._scopeId)){ 275 | nodeOps.setAttribute(vnode.elm, i, ''); 276 | } 277 | } 278 | 279 | // addVnodes 280 | function addVnodes(parentElm, refElm, vnodes, startIdx, endIdx, insertedVnodeQueue){ 281 | for(; startIdx <= endIdx; ++startIdx){ 282 | createElm(vnodes[startIdx], insertedVnodeQueue, parentElm, refElm); 283 | } 284 | } 285 | 286 | // invokeDestroyHook 287 | function invokeDestroyHook(vnode){ 288 | var i, j; 289 | var data = vnode.data; 290 | if(isDef(data)){ 291 | if(isDef(i = data.hook) && isDef(i = i.destroy)) { i(vnode); } 292 | for(i = 0; i < cbs.destroy.length; ++i){ cbs.destroy[i](vnode); } 293 | } 294 | if(isDef(i = vnode.children)){ 295 | for(j = 0; j < vnode.children.length; ++j){ 296 | invokeDestroyHook(vnode.children[j]); 297 | } 298 | } 299 | } 300 | 301 | // removeVnodes 302 | function removeVnodes(parentElm, vnodes, startIdx, endIdx){ 303 | for(; startIdx <= endIdx; ++startIdx){ 304 | var ch = vnodes[startIdx]; 305 | if(isDef(ch)){ 306 | if(isDef(ch.tag)){ 307 | removeAndInvokeRemoveHook(ch); 308 | invokeDestroyHook(ch); 309 | } else{ 310 | removeNode(ch.elm); 311 | } 312 | } 313 | } 314 | } 315 | 316 | // removeAndInvokeRemoveHook 317 | function removeAndInvokeRemoveHook(vnode, rm){ 318 | if(isDef(rm) || isDef(vnode.data)){ 319 | var listeners = cbs.remove.length + 1; 320 | if(isDef(rm)){ 321 | rm.listeners += listeners; 322 | } else{ 323 | rm = createRmcb(vnode.elm, listeners); 324 | } 325 | // 递归调用子组件 326 | if(isDef(i = vnode.componentInstance) && isDef(i = i._vnode) && isDef(i.data)){ 327 | removeAndInvokeRemoveHook(i, rm); 328 | } 329 | for(i = 0; i < cbs.remove.length; ++i){ 330 | cbs.remove[i](vnode, rm); 331 | } 332 | if(isDef(i = vnode.data.hook) && isDef(i = i.remove)){ 333 | i(vnode, rm); 334 | } else{ 335 | rm(); 336 | } 337 | } else{ 338 | removeNode(vnode.elm); 339 | } 340 | } 341 | 342 | // updateChildren 343 | function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly){ 344 | var oldStartIdx = 0; 345 | var newStartIdx = 0; 346 | var oldEndIdx = oldCh.length - 1; 347 | var oldStartVnode = oldCh[0]; 348 | var oldEndVnode = oldCh[oldEndIdx]; 349 | var newEndIdx = newCh.length - 1; 350 | var newStartVnode = newCh[0]; 351 | var newEndVnode = newCh[newEndIdx]; 352 | var oldKeyToIdx, idxInOld, elmToMove, refElm; 353 | 354 | // 维持transition-group动画时的定位属性 355 | var canMove = !removeOnly; 356 | while(oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx){ 357 | if(isUndef(oldStartVnode)){ 358 | oldStartVnode = oldCh[++oldStartIdx]; 359 | } else if(isUndef(oldEndVnode)){ 360 | oldEndVnode = oldCh[--oldEndIdx]; 361 | } else if(sameVnode(oldStartVnode, newStartVnode)){ 362 | patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue); 363 | oldStartVnode = oldCh[++oldStartIdx]; 364 | newStartVnode = newCh[++newStartIdx]; 365 | } else if(sameVnode(oldEndVnode, newEndVnode)){ 366 | patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue); 367 | oldEndVnode = oldCh[--oldEndIdx]; 368 | newEndVnode = newCh[--newEndIdx]; 369 | } else if(sameVnode(oldStartVnode, newEndVnode)){ 370 | patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue); 371 | canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps, nextSibling(oldEndVnode, elm)); 372 | oldStartVnode = oldCh[++oldStartIdx]; 373 | newEndIdx = newCh[--newEndIdx]; 374 | } else if(sameVnode(oldEndVnode, newStartVnode)){ 375 | patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue); 376 | canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm); 377 | oldEndVnode = oldCh[--oldEndIdx]; 378 | newStartVnode = newCh[++newStartIdx]; 379 | } else{ 380 | if(isUndef(oldKeyToIdx)){ oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); } 381 | idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : null; 382 | if(isUndef(idxInOld)){ 383 | createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm); 384 | newStartVnode = newCh[++newStartIdx]; 385 | } else{ 386 | elmToMove = oldCh[idxInOld]; 387 | // dev-mode 388 | if('development' !== 'production' && !elmToMove){ 389 | warn( 390 | 'It seems there are duplicate keys that is causing an update error. ' + 391 | 'Make sure each v-for item has a unique key.' 392 | ); 393 | } 394 | if(sameVnode(elmToMove, newStartVnode)){ 395 | patchVnode(elmToMove, newStartVnode, insertedVnodeQueue); 396 | oldCh[idxInOld] = undefined; 397 | canMove && nodeOps.insertBefore(parentElm, newStartVnode.elm, oldStartVnode.elm); 398 | newStartVnode = newCh[++newStartIdx]; 399 | } else{ 400 | createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm); 401 | newStartVnode = newCh[++newStartIdx]; 402 | } 403 | } 404 | } 405 | } 406 | if(oldStartIdx > oldEndIdx){ 407 | refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm; 408 | addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue); 409 | } else if(newStartIdx > newEndIdx){ 410 | removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx); 411 | } 412 | } 413 | 414 | // patchVnode 415 | function patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly){ 416 | if(oldVnode === vnode){ 417 | return ; 418 | } 419 | // 420 | if(isTrue(vnode.isStatic) && 421 | isTrue(oldVnode.isStatic) && 422 | vnode.key === oldVnode.key && 423 | (isTrue(vnode.isCloned) || isTrue(vnode.isOnce)) 424 | ){ 425 | vnode.elm = oldVnode.elm; 426 | vnode.componentInstance = oldVnode.componentInstance; 427 | return ; 428 | } 429 | var i; 430 | var data = vnode.data; 431 | if(isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)){ 432 | i(oldVnode, vnode); 433 | } 434 | var elm = vnode.elm = oldVnode.elm; 435 | var oldCh = oldVnode.children; 436 | var ch = vnode.children; 437 | if(isDef(data) && isPatchable(vnode)){ 438 | for(i = 0; i < cbs.update.length; ++i) { cbs.update[i](oldVnode, vnode); } 439 | if(isDef(i = data.hook) && isDef(i = i.update)){ i(oldVnode, vnode); } 440 | } 441 | if(isUndef(vnode.text)){ 442 | if(isDef(oldCh) && isDef(ch)){ 443 | if(oldCh !== ch) { updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); } 444 | } else if(isDef(ch)){ 445 | if(isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ''); } 446 | addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue); 447 | } else if(isDef(oldCh)){ 448 | removeVnodes(elm, oldCh, 0, oldCh.length - 1); 449 | } else if(isDef(oldVnode.text)){ 450 | nodeOps.setTextContent(elm, ''); 451 | } 452 | } else if(oldVnode.text !== vnode.text){ 453 | nodeOps.setTextContent(elm, vnode.text); 454 | } 455 | if(isDef(data)){ 456 | if(isDef(i = data.hook) && isDef(i = i.postpatch)) { i(oldValue, vnode); } 457 | } 458 | } 459 | 460 | // 461 | function invokeInsertHook(vnode, queue, initial){ 462 | if(isTrue(initial) && isDef(vnode.parent)){ 463 | vnode.parent.data.pendingInsert = queue; 464 | } else{ 465 | for(var i = 0; i < queue.length; ++i){ 466 | queue[i].data.hook.insert(queue[i]); 467 | } 468 | } 469 | } 470 | 471 | // 472 | var bailed = false; 473 | // 已渲染模块 474 | var isRenderedModule = makeMap('attrs,style,class,staticClass,staticStyle,key'); 475 | 476 | // 仅在浏览器执行的函数 477 | function hydrate(elm, vnode, insertedVnodeQueue){ 478 | if(!assertNodeMatch(elm, vnode)){ 479 | return false; 480 | } 481 | vnode.elm = elm; 482 | var tag = vnode.tag; 483 | var data = vnode.data; 484 | var children = vnode.children; 485 | if(isDef(data)){ 486 | if(isDef(i = data.hook) && isDef(i = i.init)) { i(vnode, true); } 487 | if(isDef(i = vnode.componentInstance)){ 488 | initComponent(vnode, insertedVnodeQueue); 489 | return true; 490 | } 491 | } 492 | if(isDef(tag)){ 493 | if(isDef(children)){ 494 | if(!elm.hasChildNodes()){ 495 | createChildren(vnode, children, insertedVnodeQueue); 496 | } else{ 497 | var childrenMatch = true; 498 | var childNode = elm.firstChild; 499 | for(var i$1 = 0; i$1 < children.length; i$1++){ 500 | if(!childNode || !hydrate(childNode, children[i$1], insertedVnodeQueue)){ 501 | childrenMatch = false; 502 | break; 503 | } 504 | childNode = childNode.nextSibling; 505 | } 506 | // warning 507 | if(!childrenMatch || childNode){ 508 | if('development' !== 'production' && 509 | typeof console !== 'undefined' && 510 | !bailed 511 | ){ 512 | bailed = true; 513 | console.warn('Parent: ', elm); 514 | console.warn('Mismatching childNodes vs. VNodes: ', elm.childNodes, children); 515 | } 516 | return false; 517 | } 518 | } 519 | } 520 | if(isDef(data)){ 521 | for(var key in data){ 522 | if(!isRenderedModule(key)){ 523 | invokeCreateHooks(vnode, insertedVnodeQueue); 524 | break; 525 | } 526 | } 527 | } 528 | } else if(elm.data !== vnode.text){ 529 | elm.data = vnode.text; 530 | } 531 | return true; 532 | } 533 | 534 | // assertNodeMatch 535 | function assertNodeMatch(node, vnode){ 536 | if(isDef(vnode.tag)){ 537 | return ( 538 | vnode.tag.indexOf('vue-component') === 0 || 539 | vnode.tag.toLowerCase() === (node.tagName && node.tagName.toLowerCase()) 540 | ); 541 | } else{ 542 | // 8 => comment 543 | // 3 => text 544 | return node.nodeType === (vnode.isComment ? 8 : 3); 545 | } 546 | } 547 | 548 | // 549 | return function patch(oldVnode, vnode, hydrating, removeOnly, parentElm, refElm){ 550 | if(isUndef(vnode)){ 551 | if(isDef(oldVnode)){ invokeDestroyHook(oldVnode); } 552 | return ; 553 | } 554 | var isInitialPatch = false; 555 | var insertedVnodeQueue = []; 556 | 557 | if(isUndef(oldVnode)){ 558 | isInitialPatch = true; 559 | createElm(vnode, insertedVnodeQueue, parentElm, refElm); 560 | } else{ 561 | var isRealElement = isDef(oldVnode.nodeType); 562 | if(!isRealElement && sameVnode(oldVnode, vnode)){ 563 | patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly); 564 | } else{ 565 | if(isRealElement){ 566 | if(oldVnode.nodeType === 1 && oldVnode.hasAttribute('server-rendered')){ 567 | oldVnode.removeAttribute('server-rendered'); 568 | hydrating = true; 569 | } 570 | if(isTrue(hydrating)){ 571 | if(hydrate(oldVnode, vnode, insertedVnodeQueue)){ 572 | invokeInsertHook(vnode, insertedVnodeQueue, true); 573 | return oldVnode; 574 | } else{ 575 | warn( 576 | 'The client-side rendered virtual DOM tree is not matching ' + 577 | 'server-rendered content. This is likely caused by incorrect ' + 578 | 'HTML markup, for example nesting block-level elements inside ' + 579 | '

, or missing . Bailing hydration and performing ' + 580 | 'full client-side render.' 581 | ); 582 | } 583 | } 584 | // 585 | oldVnode = emptyNodeAt(oldVnode); 586 | } 587 | // 588 | var oldElm = oldVnode.elm; 589 | var parentElm$1 = nodeOps.parentNode(oldElm); 590 | createElm( 591 | vnode, 592 | insertedVnodeQueue, 593 | // 594 | oldElm._leaveCb ? null : parentElm$1, 595 | nodeOps.nextSibling(oldElm) 596 | ); 597 | 598 | if(isDef(vnode.parent)){ 599 | var ancestor = vnode.parent; 600 | while(ancestor){ 601 | ancestor.elm = vnode.elm; 602 | ancestor = ancestor.parent; 603 | } 604 | if(isPatchable(vnode)){ 605 | for(var i = 0; i < cbs.create.length; ++i){ 606 | cbs.create[i](emptyNode, vnode.parent); 607 | } 608 | } 609 | } 610 | if(isDef(parentElm$1)){ 611 | removeVnodes(parentElm$1, [oldVnode], 0, 0); 612 | } else if(isDef(oldVnode.tag)){ 613 | invokeDestroyHook(oldVnode); 614 | } 615 | } 616 | } 617 | invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch); 618 | return vnode.elm; 619 | } 620 | } 621 | ``` 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | -------------------------------------------------------------------------------- /13-directives.md: -------------------------------------------------------------------------------- 1 | # directives 2 | 3 | 4 | 5 | 6 | 7 | ### directives 8 | 9 | ```javascript 10 | var directives = { 11 | create: updateDirectives, 12 | update: updateDirectives, 13 | destroy: function unbindDirectives(vnode){ 14 | updateDirectives(vnode, emptyNode); 15 | } 16 | }; 17 | ``` 18 | 19 | 20 | 21 | ### updateDirectives 22 | 23 | ```javascript 24 | function updateDirectives(oldVnode, vnode){ 25 | if(oldVnode.data.directives || vnode.data.directives){ 26 | _update(oldVnode, vnode); 27 | } 28 | } 29 | ``` 30 | 31 | 32 | 33 | ### _update 34 | 35 | ```javascript 36 | function _update(oldVnode, vnode){ 37 | var isCreate = oldVnode = emptyNode; 38 | var isDestroy = vnode = emptyNode; 39 | var oldDirs = normalizeDirectives$1(oldVnode.data.directives, oldVnode.context); 40 | var newDirs = normalizeDirectives$1(vnode.data.directives, vnode.context); 41 | 42 | var dirsWithInsert = []; 43 | var dirsWithPostpatch = []; 44 | 45 | var key, oldDir, dir; 46 | for(key in newDirs){ 47 | oldDir = oldDirs[key]; 48 | dir = newDirs[key]; 49 | if(!oldDir){ 50 | // 绑定新指令 51 | callHook$1(dir, 'bind', vnode, oldVnode); 52 | if(dir.def && dir.def.inserted){ 53 | dirsWithInsert.push(dir); 54 | } 55 | } else{ 56 | // 更新已存在的指令 57 | dir.oldValue = oldDir.value; 58 | callHook$1(dir, 'update', vnode, oldVnode); 59 | if(dir.def && dir.def.componentUpdated){ 60 | dirsWithPostpatch.push(dir); 61 | } 62 | } 63 | } 64 | if(dirsWithinsert.length){ 65 | var callInsert = function(){ 66 | for(var i = 0; i < dirsWithInsert.length; i++){ 67 | callHook$1(dirsWithInsert[i], 'inserted', vnode, oldVnode); 68 | } 69 | }; 70 | if(isCreate){ 71 | mergeVNodeHook(vnode.data.hook || (vnode.data.hook = {}), 'insert', callInsert); 72 | } else{ 73 | callInsert(); 74 | } 75 | } 76 | if(dirsWithPostpatch.length){ 77 | mergeVNodeHook(vnode.data.hook || (vnode.data.hook = {}), 'postpatch', function(){ 78 | for(var i = 0; i < dirsWithPostpatch.length; i++){ 79 | callHook$1(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode); 80 | } 81 | }); 82 | } 83 | 84 | if(!isCreate){ 85 | for(key in oldDirs){ 86 | if(!newDirs[key]){ 87 | callHook$1(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy); 88 | } 89 | } 90 | } 91 | } 92 | ``` 93 | 94 | 95 | 96 | ### normalizeDirectives$1 97 | 98 | ```javascript 99 | var emptyModifiers = Object.create(null); 100 | 101 | function normalizeDirectives$1(dirs, vm){ 102 | var res = Object.create(null); 103 | if(!dirs){ 104 | return res; 105 | } 106 | var i, dir; 107 | for(i = 0; i < dirs.length; i++){ 108 | dir = dirs[i]; 109 | if(!dir.modifiers){ 110 | dir.modifiers = emptyModifiers; 111 | } 112 | res[getRawDirName(dir)] = dir; 113 | dir.def = resolveAsset(vm.$options, 'directives', dir.name, true); 114 | } 115 | return res; 116 | } 117 | ``` 118 | 119 | 120 | 121 | #### getRawDirName 122 | 123 | ```javascript 124 | function getRawDirName(dir){ 125 | return dir.rawName || ((dir.name) + '.' + (Object.keys(dir.modifiers || {}).join('.'))) 126 | } 127 | ``` 128 | 129 | 130 | 131 | #### callHook$1 132 | 133 | ```javascript 134 | function callHook$1(dir, hook, vnode, oldVnode, isDestroy){ 135 | var fn = dir.def && dir.def[hook]; 136 | if(fn){ 137 | fn(vnode.elm, dir, vnode, oldVnode, isDestroy); 138 | } 139 | } 140 | ``` 141 | 142 | 143 | 144 | ### baseModules 145 | 146 | ```javascript 147 | var baseModules = [res, directives]; 148 | ``` 149 | 150 | 151 | 152 | ### updateAttrs 153 | 154 | ```javascript 155 | function updateAttrs(oldVnode, vnode){ 156 | if(!oldVnode.data.attrs && !vnode.data.attrs){ 157 | return ; 158 | } 159 | var key, cur, old; 160 | var elm = vnode.elm; 161 | var oldAttrs = oldVnode.data.attrs || {}; 162 | var attrs = vnode.data.attrs || {}; 163 | // 克隆观测对象 164 | if(attrs.__ob__){ 165 | attrs = vnode.data.attrs = extend({}, attrs); 166 | } 167 | 168 | for(key in attrs){ 169 | cur = attrs[key]; 170 | old = oldAttrs[key]; 171 | if(old !== cur){ 172 | setAttr(elm, key, cur); 173 | } 174 | } 175 | 176 | // IE9中设置type会重置type=radio的值 177 | if(isIE9 && attrs.value !== oldAttrs.value){ 178 | setAttr(elm, 'value', attrs.value); 179 | } 180 | for(key in oldAttrs){ 181 | if(attrs[key] == null){ 182 | if(isXlink(key)){ 183 | elm.removeAttributeNS(xlinkNS, getXlinkProp(key)); 184 | } else if(!isEnumeratedAttr(key)){ 185 | elm.removeAttribute(key); 186 | } 187 | } 188 | } 189 | } 190 | ``` 191 | 192 | 193 | 194 | #### setAttr 195 | 196 | ```javascript 197 | function setAttr(el, key, value){ 198 | if(isBooleanAttr(key)){ 199 | // 无值属性 200 | // 例如disabled 201 | if(isFalsyAttrValue(value)){ 202 | el.removeAttribute(key); 203 | } else{ 204 | el.setAttribute(key, key); 205 | } 206 | } else if(isEnumeratedAttr(key)){ 207 | el.setAttribute(key, isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true'); 208 | } else if(isXlink(key)){ 209 | if(isFalsyAttrValue(value)){ 210 | el.removeAttributeNS(xlinkNS, getXlinkProp(key)); 211 | } else{ 212 | el.setAttributeNS(xlinkNS, key, value); 213 | } 214 | } else{ 215 | if(isFalsyAttrValue(value)){ 216 | el.removeAttribute(key); 217 | } else{ 218 | el.setAttribute(key, value); 219 | } 220 | } 221 | } 222 | ``` 223 | 224 | 225 | 226 | ### attrs 227 | 228 | ```javascript 229 | var attrs = { 230 | create: updateAttrs, 231 | update: updateAttrs 232 | }; 233 | ``` 234 | 235 | 236 | 237 | --- 238 | 239 | 240 | 241 | ### updateClass 242 | 243 | ```javascript 244 | function updateClass(oldVnode, vnode){ 245 | var el = vnode.elm; 246 | var data = vnode.data; 247 | var oldData = oldVnode.data; 248 | if(!data.staticClass && !data.class && 249 | (!oldData || (!oldData.staticClass && !oldData.class))){ 250 | return ; 251 | } 252 | 253 | var cls = genClassForVnode(vnode); 254 | 255 | // transitionClass 256 | var transitionClass = el._transitionClass; 257 | if(transitionClass){ 258 | cls = concat(cls, stringifyClass(transitionClass)); 259 | } 260 | 261 | // 262 | if(cls !== el._prevClass){ 263 | el.setAttribute('class', cls); 264 | el._prevClass = cls; 265 | } 266 | } 267 | ``` 268 | 269 | 270 | 271 | ### klass 272 | 273 | ```javascript 274 | var klass = { 275 | create: updateClass, 276 | update: updateClass 277 | }; 278 | ``` 279 | 280 | 281 | 282 | ### validDivisionCharRE 283 | 284 | ```javascript 285 | var validDivisionCharRe = /[\w).+\-_$]]/; 286 | ``` 287 | 288 | 289 | 290 | ### parseFilters 291 | 292 | ```javascript 293 | function parseFilters(exp){ 294 | var inSingle = false; 295 | var inDouble = false; 296 | var inTemplateString = false; 297 | var inRegex = false; 298 | var curly = 0; 299 | var square = 0; 300 | var paren = 0; 301 | var lastFilterIndex = 0; 302 | var c, prev, i, expression, filters; 303 | 304 | for(i = 0; i < exp.length; i++){ 305 | prev = c; 306 | c = exp.charCodeAt(i); 307 | // 特殊符号前没有转义符号代表非特殊 308 | if(inSingle){ 309 | // 0x5C => 92 => \ 310 | // 39 => ' 311 | if(c === 0x27 && prev !== 0x5C) { inSingle = false; } 312 | } else if(inDouble){ 313 | // 34 => " 314 | if(c === 0x22 && prev !== 0x5C) { inDouble = false; } 315 | } else if(inTemplateString){ 316 | // 96 => ` 317 | if(c === 0x60 && prev !== 0x5C) { inTemplateString = false; } 318 | } else if(inRegex){ 319 | // 47 => / 320 | if(c === 0x2f && prev !== 0x5C) { inRegex = false; } 321 | } else if( 322 | // 124 => | 323 | c === 0x7C && 324 | exp.charCodeAt(i + 1) !== 0x7C && 325 | exp.charCodeAt(i - 1) !== 0x7C && 326 | !curly && !square && !paren 327 | ){ 328 | if(expression === undefined){ 329 | lastFilterIndex = i + 1; 330 | expression = exp.slice(0, i).trim(); 331 | } else{ 332 | pushFilter(); 333 | } 334 | } else{ 335 | // 数值对应符号参考上面的 336 | switch(c){ 337 | case 0x22: // " 338 | inDouble = true; 339 | break; 340 | case 0x27: // ' 341 | inSingle = true; 342 | break; 343 | case 0x60: // ` 344 | inTemplateString = true; 345 | break; 346 | case 0x28: // ( 347 | paren++; 348 | break; 349 | case 0x29: // ) 350 | paren--; 351 | break; 352 | case 0x5B: // [ 353 | square++; 354 | break; 355 | case 0x5D: // ] 356 | square--; 357 | break; 358 | case 0x7B: // { 359 | curly++; 360 | break; 361 | case 0x7D: // } 362 | curly--; 363 | break; 364 | } 365 | // / 366 | if(c === 0x2f){ 367 | var j = i - 1; 368 | // undefined 369 | var p = (void 0); 370 | // 找到前面第一个非空格 371 | for(; j >= 0; j--){ 372 | p = exp.charAt(j); 373 | if(p !== ' ') { break; } 374 | } 375 | if(!p || !validDivisionCharRE.test(p)){ 376 | inRegex = true; 377 | } 378 | } 379 | } 380 | } 381 | 382 | if(expression === undefined){ 383 | expression = exp.slice(0, i).trim(); 384 | } else if(lastFilterIndex !== 0){ 385 | pushFilter(); 386 | } 387 | 388 | function pushFilter(){ 389 | (filters || (filters = [])).push(exp.slice(lastFilterIndex, i).trim()); 390 | lastFilterIndex = i + 1; 391 | } 392 | 393 | if(filters){ 394 | for(i = 0; i < filters.length; i++){ 395 | expression = wrapFilter(expression, filters[i]); 396 | } 397 | } 398 | 399 | return expression; 400 | } 401 | ``` 402 | 403 | 404 | 405 | ### wrapFilter 406 | 407 | ```javascript 408 | function wrapFilter(exp, filter){ 409 | var i = filter.indexOf('('); 410 | // 找不到左括号 411 | if(i < 0){ 412 | // `_f("{filter}")({exp})` 413 | return ('_f("' + filter + '")(' + exp + ')'); 414 | } else{ 415 | var name = filter.slice(0, i); 416 | var args = filter.slice(i + 1); 417 | // 418 | return ('_f("' + name + '")(' + exp + ',' + args); 419 | } 420 | } 421 | ``` 422 | 423 | 424 | 425 | ### baseWarn 426 | 427 | ```javascript 428 | function baseWarn(msg){ 429 | console.error(("[Vue compiler]: " + msg)); 430 | } 431 | ``` 432 | 433 | 434 | 435 | ### pluckModuleFunction 436 | 437 | ```javascript{ 438 | function pluckModuleFunction(modules, key){ 439 | // 返回[modules[0][key],modules[1][key],...] 440 | // 后面过滤null,undefined等假值 441 | return modules ? modules.map(function(m){ return m[key]; }).filter(function(_){ return _; }) : []; 442 | } 443 | ``` 444 | 445 | 446 | 447 | ### addProp 448 | 449 | ```javascript 450 | function addProp(el, name, value){ 451 | (el.props || (el.props = [])).push({ name: name, value: value }); 452 | } 453 | ``` 454 | 455 | 456 | 457 | ### addAttr 458 | 459 | ```javascript 460 | function addAttr(el, name, value){ 461 | (el.attrs || (el.attrs = [])).push({ name: name, value: value }); 462 | } 463 | ``` 464 | 465 | 466 | 467 | ### addDirective 468 | 469 | ```javascript 470 | function addDirective(el, name, rawName, value, arg, modifiers){ 471 | (el.directives || (el.directives = [])).push({ name: name, rawName: rawName, value: value, arg: arg, modifiers: modifiers}); 472 | } 473 | ``` 474 | 475 | 476 | 477 | ### addHandler 478 | 479 | ```javascript 480 | function addHandler(el, name, value, modifiers, important){ 481 | if(modifiers && modifiers.capture){ 482 | delete modifiers.capture; 483 | // 标记captured事件 484 | name = '!' + name; 485 | } 486 | if(modifiers && modifiers.once){ 487 | delete modifiers.once; 488 | // 标记once事件 489 | name = '~' + name; 490 | } 491 | var events; 492 | if(modifiers && modifiers.native){ 493 | delete modifiers.native; 494 | events = el.nativeEvents || (el.nativeEvents = {}); 495 | } else{ 496 | events = el.events || (el.events = {}); 497 | } 498 | var newHandler = { value: value, modifiers: modifiers}; 499 | var handlers = events[name]; 500 | 501 | if(Array.isArray(handlers)){ 502 | important ? handlers.unshift(newHandler) : handlers.push(newHandler); 503 | } else if(handlers){ 504 | events[name] = important ? [newHandler, handlers] : [handlers, newHandler]; 505 | } else{ 506 | events[name] = newHandler; 507 | } 508 | } 509 | ``` 510 | 511 | 512 | 513 | ### getBindingAttr 514 | 515 | ```javascript 516 | function getBindingAttr(el, name, getStatic){ 517 | // 简写 518 | var dynamicValue = getAndRemoveAttr(el, ':' + name) || getAndRemoveAttr(el, 'v-bind' + name); 519 | if(dynamicValue != null){ 520 | return parseFilters(dynamicValue); 521 | } else if(getStatic !== false){ 522 | var staticValue = getAndRemoveAttr(el ,name); 523 | if(staticValue != null){ 524 | return JSON.stringify(staticValue); 525 | } 526 | } 527 | } 528 | ``` 529 | 530 | 531 | 532 | ### getAndRemoveAttr 533 | 534 | ```javascript 535 | function getAndRemoveAttr(el, name){ 536 | var val; 537 | if((val = el.attrsMap[name]) != null){ 538 | var list = el.attrsList; 539 | for(var i = 0; l = list.length; i < l; i++){ 540 | if(list[i].name === name){ 541 | list.splice(i, 1); 542 | break; 543 | } 544 | } 545 | } 546 | return val; 547 | } 548 | ``` 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | -------------------------------------------------------------------------------- /14-gen.md: -------------------------------------------------------------------------------- 1 | # parse model 2 | 3 | 4 | 5 | 6 | 7 | ## gen 8 | 9 | 10 | 11 | gen: 产生 12 | 13 | 14 | 15 | ### genComponentModel 16 | 17 | ```javascript 18 | // for v-model 19 | function genComponentModel(el, value, modifiers){ 20 | var ref = modifiers || {}; 21 | var number = ref.number; 22 | var trim = ref.trim; 23 | 24 | // v = b = '$$v' 25 | var baseValueExpression = '$$v'; 26 | var valueExpression = baseValueExpression; 27 | // ref.trim存在 28 | // v = (typeof b === string ? b.trim() : b) 29 | if(trim){ 30 | valueExpression = 31 | '(typeof ' + baseValueExpression + ' === "string"' + 32 | '? ' + baseValueExpression + '.trim()' + 33 | ': ' + baseValueExpression + ')'; 34 | } 35 | // ref.number存在 36 | // v = _n(v) 37 | if(number){ 38 | valueExpression = '_n(' + valueExpression + ')'; 39 | } 40 | var assignment = genAssignmentCode(value, valueExpression); 41 | 42 | // el.model = {value:(value),expression:"value",callback:function(b){a}} 43 | el.model = { 44 | value: ('(' + value + ')'), 45 | expression: ('"' + value + '"'), 46 | callback: ('function (' + baseValueExpression + ') {' + assignment + '}') 47 | }; 48 | } 49 | ``` 50 | 51 | 52 | 53 | ### genAssignmentCode 54 | 55 | ```javascript 56 | function genAssignmentCode(value, assignment){ 57 | var modelRs = parseModel(value); 58 | if(modelRs.idx === null){ 59 | // 括号不影响输出 纯粹美观 60 | // 'v = a' 61 | return (value + '=' + assignment) 62 | } else{ 63 | // var $$exp = m.e,$$idx = m.i; 64 | // if(!Array.isArray($$exp)){ v = a; } 65 | // else{ $$exp.splice($$idx, 1, a); } 66 | return 'var $$exp = ' + (modelRs.exp) + ', $$idx = ' + (modelRs.idx) + ';' + 67 | 'if (!Array.isArray($$exp)){' + 68 | value + '=' + assignment + '}' + 69 | 'else{$$exp.splice($$idx, 1, ' + assignment + ')}' 70 | } 71 | } 72 | ``` 73 | 74 | 75 | 76 | --- 77 | 78 | 79 | 80 | ## parse 81 | 82 | 83 | 84 | ### parseModel 85 | 86 | ```javascript 87 | // 88 | var len; 89 | var str; 90 | var chr; 91 | var index$1; 92 | var expressionPos; 93 | var expressionEndPos; 94 | function parseModel(val){ 95 | str = val; 96 | len = str.length; 97 | index$1 = expressionPos = expressionEndPos = 0; 98 | 99 | if(val.indexOf('[') < 0 || val.lastIndexOf(']') < len - 1){ 100 | return { 101 | exp: val, 102 | idx: null 103 | } 104 | } 105 | while(!eof){ 106 | chr = next(); 107 | if(isStringStart(chr)){ 108 | parseString(chr); 109 | // 0x5B =>91 => [ 110 | } else if(chr === 0x5B){ 111 | parseBracket(chr); 112 | } 113 | } 114 | 115 | return { 116 | exp: val.substring(0, expressionPos), 117 | idx: val.substring(expression + 1, expressionEndPos) 118 | } 119 | } 120 | ``` 121 | 122 | 123 | 124 | ### parseBracket 125 | 126 | ```javascript 127 | function parseBracket(chr){ 128 | var inBracket = 1; 129 | expressionPos = index$1; 130 | while(!eof){ 131 | chr = next(); 132 | if(isStringStart(chr)){ 133 | parseString(chr); 134 | continue; 135 | } 136 | if(chr === 0x5B) { inBracket++; } 137 | if(chr === 0x5D) { inBracket--; } 138 | if(inBracket === 0){ 139 | expressionEndPos = index$1; 140 | break; 141 | } 142 | } 143 | } 144 | ``` 145 | 146 | 147 | 148 | #### parseString 149 | 150 | ```javascript 151 | function parseString(chr){ 152 | var stringQuote = chr; 153 | while(!eof()){ 154 | chr = next(); 155 | if(chr === stringQuote){ 156 | break; 157 | } 158 | } 159 | } 160 | ``` 161 | 162 | 163 | 164 | #### next 165 | 166 | ```javascript 167 | // 对应的ASCII码 168 | function next(){ 169 | return str.charCodeAt(++index$1); 170 | } 171 | ``` 172 | 173 | 174 | 175 | #### eof 176 | 177 | ```javascript 178 | // 索引大于长度 179 | function eof(){ 180 | return index$1 >= len 181 | } 182 | ``` 183 | 184 | 185 | 186 | #### isStringStart 187 | 188 | ```javascript 189 | // 单双引号 => ' " 190 | function isStringStart(chr){ 191 | return chr === 0x22 || chr === 0x27; 192 | } 193 | ``` 194 | 195 | 196 | 197 | --- 198 | 199 | 200 | 201 | ## model 202 | 203 | 204 | 205 | 206 | 207 | ### model 208 | 209 | ```javascript 210 | var warn$1; 211 | var RANGE_TOKEN = '__r'; 212 | var CHECKBOX_RADIO_TOKEN = '__c'; 213 | 214 | function model(el, dir, _warn){ 215 | warn$1 = _warn; 216 | var value = dir.value; 217 | var modifiers = dir.modifiers; 218 | var tag = el.tag; 219 | var type = el.attrsMap.type; 220 | 221 | // 动态类型是啥… 222 | var dynamicType = el.attrsMap['v-bind:type'] || el.attrsMap[':type']; 223 | if(tag === 'input' && dynamicType){ 224 | warn$1( 225 | ':\n' + 226 | 'v-model does not support dynamic input types. Use v-if branches instead.' 227 | ); 228 | } 229 | // type = 'file'是只读 无法修改 230 | if(tag === 'input' && type === 'file'){ 231 | warn$1( 232 | '<' + (el.tag) + ' v-model="' + value + '" type="file">:\n' + 233 | 'File inputs are read only. Use a v-on:change listener instead.' 234 | ); 235 | } 236 | 237 | if(tag === 'select'){ 238 | genSelect(el, value, modifiers); 239 | } else if(tag === 'input' && type === 'checkbox'){ 240 | genCheckboxModel(el, value, modifiers); 241 | } else if(tag === 'input' && type === 'radio'){ 242 | genRadioModel(el, value, modifiers); 243 | } else if(tag === 'input' || tag === 'textarea'){ 244 | genDefaultModel(el, value, modifiers); 245 | } else if(!config.isReservedTag(tag)){ 246 | genComponentModel(el, value, modifiers); 247 | return false; 248 | } else{ 249 | warn$1( 250 | '<' + (el.tag) + ' v-model="' + value + '">: ' + 251 | 'v-model is not supported on this element type. ' + 252 | 'If you are working with contenteditable, it\'s recommended to ' + 253 | 'wrap a library dedicated for that purpose inside a custom component.' 254 | ); 255 | } 256 | return true; 257 | } 258 | ``` 259 | 260 | 261 | 262 | #### genCheckboxModel 263 | 264 | ```javascript 265 | function genCheckboxModel(el, value, modifiers){ 266 | var number = modifiers && modifiers.number; 267 | var valueBinding = getBindingAttr(el, 'value') || 'null'; 268 | var trueValueBinding = getBindingAttr(el, 'true-value') || 'true'; 269 | var falseValueBinding = getBindingAttr(el, 'false-value') || 'false'; 270 | // Array.isArray(v) ? _i(v,vB) > -1 (tVB === 'true' ? ':("v")' : ':_q("v,tVB")') 271 | addProp(el, 'checked', 272 | 'Array.isArray(' + value + ')' + 273 | '?_i(' + value + ',' + valueBinding + ')>-1' + ( 274 | trueValueBinding === 'true' ? (':(' + value + ')') : (':_q(' + value + ',' + trueValueBinding + ')')) 275 | ); 276 | addHandler(el, CHECKBOX_RADIO_TOKEN, 277 | 'var $$a=' + value + ',' + 278 | '$$el=$event.target,' + 279 | '$$c=$$el.checked?(' + trueValueBinding + '):(' + falseValueBinding + ');' + 280 | 'if(Array.isArray($$a)){' + 281 | 'var $$v=' + (number ? '_n(' + valueBinding + ')' : valueBinding) + ',' + 282 | '$$i=_i($$a,$$v);' + 283 | 'if($$c){$$i<0&&(' + value + '=$$a.concat($$V))}' + 284 | 'else{$$i>-1&&(' + value + '=$$a.slice(0,$$i).concat($$a.slice($$i+1)))}' + 285 | '}else{' + value + '=$$c}', 286 | null, true 287 | ); 288 | } 289 | ``` 290 | 291 | 292 | 293 | #### genRadioModel 294 | 295 | ```javascript 296 | function genRadioModel(el, value, modifiers){ 297 | var number = modifiers && modifiers.number; 298 | var valueBinding = getBindingAttr(el, 'value') || 'null'; 299 | // _n(valueBinding) 300 | valueBinding = number ? ('_n(' + valueBinding + ')') : valueBinding; 301 | // _q(value, valueBinding) 302 | addProp(el, 'checked', ('_q(' + value + ',' + valueBinding + ')')); 303 | addHandler(el, CHECKBOX_RADIO_TOKEN, genAssignmentCode(value, valueBinding), null, true); 304 | } 305 | ``` 306 | 307 | 308 | 309 | #### genSelect 310 | 311 | ```javascript 312 | function genSelect(el, value, modifiers){ 313 | var number = modifiers && modifiers.number; 314 | // Array.prototype.filter.call($event.target.options,function(o){return o.selected};) 315 | // .map(function(o){var val = '_value' in o ? o._value : o.value; return number ? _n(val) : val}) 316 | var selectedVal = 'Array.prototype.filter' + 317 | '.call($event.target.options,function(o){return o.selected})' + 318 | '.map(function(o){var val = "_value" in o ? o._value : o.value;' + 319 | 'return ' + (number ? '_n(val)' : 'val') + '})'; 320 | var assignment = '$event.target.multiple ? $$selectedVal : $$selectedVal[0]'; 321 | var code = 'var $$selectedVal = ' + selectedVal + ';'; 322 | code = code + ' ' + (genAssignmentCode(value, assignment)); 323 | addHandler(el, 'change', code, null, true); 324 | } 325 | ``` 326 | 327 | 328 | 329 | #### genDefaultModel 330 | 331 | ```javascript 332 | function genDefaultModel(el, value, modifiers){ 333 | var type = el.attrsMap.type; 334 | var ref = modifiers || {}; 335 | var lazy = ref.lazy; 336 | var number = ref.number; 337 | var trim = ref.trim; 338 | var needCompositionGuard = !lazy && type !== 'range'; 339 | // 340 | var event = lazy ? 'change' : type === 'range' ? RANGE_TOKEN : 'input'; 341 | var valueExpression = '$event.target.value'; 342 | if(trim){ 343 | valueExpression = '$event.target.value.trim()'; 344 | } 345 | if(number){ 346 | valueExpression = '_n(' + valueExpression + ')'; 347 | } 348 | var code = genAssignmentCode(value, valueExpression); 349 | if(needCompositionGuard){ 350 | code = 'if($event.target.composition)return;' + code; 351 | } 352 | addProp(el, 'value', ('(' + value + ')')); 353 | addHandler(el, event, code, null, true); 354 | if(trim || number || type === 'number'){ 355 | addHandler(el, 'blur', '$forceUpdate()'); 356 | } 357 | } 358 | ``` 359 | 360 | 361 | 362 | --- 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | -------------------------------------------------------------------------------- /15-event&style&class.md: -------------------------------------------------------------------------------- 1 | # event/domProps/style/class 2 | 3 | 4 | 5 | 6 | 7 | ### Event 8 | 9 | 10 | 11 | ```javascript 12 | // 两个一样的搞毛 13 | var events = { 14 | create: updateDOMlisteners, 15 | update: updateDOMlisteners 16 | } 17 | ``` 18 | 19 | 20 | 21 | #### normalizeEvents 22 | 23 | ```javascript 24 | function normalizeEvents(on){ 25 | var event; 26 | if(on[RANGE_TOKEN]){ 27 | // IE的input[type=range]仅支持change事件 28 | event = isIE ? 'change' : 'input'; 29 | on[event] = [].concat(on[RANGE_TOKEN], on[event] || []); 30 | delete on[RANGE_TOKEN]; 31 | } 32 | if(on[CHECKBOX_RADIO_TOKEN]){ 33 | event = isChrome ? 'click' : 'change'; 34 | on[event] = [].concat(on[CHECKBOX_RADIO_TOKEN], on[event] || []); 35 | delete on[CHECKBOX_RADIO_TOKEN]; 36 | } 37 | } 38 | ``` 39 | 40 | 41 | 42 | ```javascript 43 | var target$1; 44 | ``` 45 | 46 | 47 | 48 | #### add$1 49 | 50 | ```javascript 51 | function add$1(event, handler, once, capture){ 52 | if(once){ 53 | var oldHandler = handler; 54 | var _target = target$1; 55 | handler = function(ev){ 56 | var res = arguments.length === 1 ? oldHandler(ev) : oldHandler.apply(null, arguments); 57 | if(res !== null){ 58 | remove$2(event, handler, capture, _target); 59 | } 60 | }; 61 | } 62 | target$1.addEventListener(event, handler, capture); 63 | } 64 | ``` 65 | 66 | 67 | 68 | 69 | 70 | #### remove$2 71 | 72 | ```javascript 73 | function remove$2(event, handler, capture, _target){ 74 | (_target || target$1).removeEventListener(event, handler, capture); 75 | } 76 | ``` 77 | 78 | 79 | 80 | #### updateDOMlisteners 81 | 82 | ```javascript 83 | function updateDOMlisteners(oldVnode, vnode){ 84 | if(!oldVnode.data.on && !vnode.data.on){ 85 | return ; 86 | } 87 | var on = vnode.data.on || {}; 88 | var oldOn = oldVnode.data.on || {}; 89 | target$1 = vnode.elm; 90 | normalizeEvents(on); 91 | updatelisteners(on, oldOn, add$1, remove$2, vnode, context); 92 | } 93 | ``` 94 | 95 | 96 | 97 | ------ 98 | 99 | 100 | 101 | ### domProps 102 | 103 | 104 | 105 | ```javascript 106 | var domProps = { 107 | create: updateDOMProps, 108 | update: updateDOMProps 109 | } 110 | ``` 111 | 112 | 113 | 114 | #### updateDOMProps 115 | 116 | ```javascript 117 | function updateDOMProps(oldVnode, vnode){ 118 | if(!oldVnode.data.domProps && !vnode.data.domProps){ 119 | return ; 120 | } 121 | var key, cur; 122 | var elm = vnode.elm; 123 | var oldProps = oldVnode.data.domProps || {}; 124 | var props = vnode.data.domProps || {}; 125 | // 复制观测对象 126 | if(props.__ob__){ 127 | props = vnode.data.domProps = extend({}, props); 128 | } 129 | 130 | for(key in oldProps){ 131 | if(props[key] == null){ 132 | elm[key] = ''; 133 | } 134 | } 135 | for(key in props){ 136 | cur = props[key]; 137 | // 判断子元素是否有内容 138 | if(key === 'textContent' || key === 'innerHTML'){ 139 | if(vnode.children) { vnode.children.length = 0; } 140 | if(cur === oldProps[key]) { continue; } 141 | } 142 | if(key === 'value'){ 143 | // 将value保存为_value属性 144 | elm._value = cur; 145 | // 避免当值一样时重置鼠标位置 146 | var strCur = cur = null ? '' : String(cur); 147 | if(shouldUpdateValue(elm, vnode, strCur)){ 148 | elm.value = strCur; 149 | } 150 | } else{ 151 | elm[key] = cur; 152 | } 153 | } 154 | } 155 | ``` 156 | 157 | 158 | 159 | #### shouldUpdateValue 160 | 161 | ```javascript 162 | function shouldUpdateValue(elm, vnode, checkVal){ 163 | return (!elm.composing && ( 164 | vnode.tag === 'option' || 165 | isDirty(elm, checkVal) || 166 | isInputChanged(elm, checkVal) 167 | )); 168 | } 169 | ``` 170 | 171 | 172 | 173 | #### isDirty 174 | 175 | ```javascript 176 | function isDirty(elm, checkVal){ 177 | // 当输入框失去焦点且值不一致时返回true 178 | return document.activeElement !== elm && elm.value !== checkVal; 179 | } 180 | ``` 181 | 182 | 183 | 184 | #### isInputChanged 185 | 186 | ```javascript 187 | function isInputChanged(elm, newVal){ 188 | var value = elm.value; 189 | var modifiers = elm._vModifiers; 190 | // 比较数字s 191 | if((modifiers && modifiers.number) || elm.type === 'number'){ 192 | return toNumber(value) !== toNumber(newVal); 193 | } 194 | // trim() 195 | if(modifiers && modifiers.trim){ 196 | return value.trim() !== newVal.trim(); 197 | } 198 | return value !== newVal; 199 | } 200 | ``` 201 | 202 | 203 | 204 | ------ 205 | 206 | 207 | 208 | ### Style 209 | 210 | 211 | 212 | ```javascript 213 | var style = { 214 | create: updateStyle, 215 | update: updateStyle 216 | } 217 | ``` 218 | 219 | 220 | 221 | #### parseStyleText 222 | 223 | ```javascript 224 | var parseStyleText = cached(function(cssText){ 225 | var res = {}; 226 | // 227 | var listDelimiter = /;(?![^(]*\))/g; 228 | // 匹配':'后面1个或多个元素 229 | var propertyDelimiter = /:(.+)/; 230 | cssText.split(listDelimiter).forEach(function(item){ 231 | if(item){ 232 | var tmp = item.split(propertyDelimiter); 233 | tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim()); 234 | } 235 | }); 236 | return res; 237 | }) 238 | ``` 239 | 240 | 241 | 242 | #### normalizeStyleData 243 | 244 | ```javascript 245 | // 合并style 246 | function normalizeStyleData(data){ 247 | var style = normalizeStyleBinding(data.style); 248 | // 249 | return data.staticStyle ? 250 | extend(data.staticStyle, style) : 251 | style 252 | } 253 | ``` 254 | 255 | 256 | 257 | #### normalizeStyleBinding 258 | 259 | ```javascript 260 | function normalizeStyleBinding(bindingStyle){ 261 | if(Array.isArray(bindingStyle)){ 262 | return toObject(bindingStyle); 263 | } 264 | if(typeof bindingStyle === 'string'){ 265 | return parseStyleText(bindingStyle); 266 | } 267 | return bindingStyle; 268 | } 269 | ``` 270 | 271 | 272 | 273 | #### getStyle 274 | 275 | ```javascript 276 | function getStyle(vnode, checkChild){ 277 | var res = {}; 278 | var styleData; 279 | 280 | if(checkChild){ 281 | var childNode = vnode; 282 | while(childNode.componentInstance){ 283 | childNode = childNode.componentInstance._vnode; 284 | if(childNode.data && (styleData = normalizeStyleData(childNode.data))){ 285 | extend(res, styleData); 286 | } 287 | } 288 | } 289 | 290 | if((styleData = normalizeStyleData(vnode.data))){ 291 | extend(res, styleData); 292 | } 293 | 294 | var parentNode = vnode; 295 | while((parentNode = parentNode.parent)){ 296 | if(parentNode.data && (styleData = normalizeStyleData(parentNode.data))){ 297 | extend(res, styleData); 298 | } 299 | } 300 | return res; 301 | } 302 | ``` 303 | 304 | 305 | 306 | #### setProp 307 | 308 | ```javascript 309 | var cssVarRE = /^--/; 310 | var importantRE = /\s*!important$/; 311 | var setProp = function(el, name, val){ 312 | if(cssVarRE.test(name)){ 313 | el.style.setProperty(name, val); 314 | } else if(importantRE.test(val)){ 315 | el.style.setProperty(name, val.replace(important, ''), 'important'); 316 | } else{ 317 | el.style[normalize(name)] = val; 318 | } 319 | } 320 | ``` 321 | 322 | 323 | 324 | #### normalize 325 | 326 | ```javascript 327 | var prefixes = ['Webkit','Moz','ms']; 328 | 329 | var testEl; 330 | var normalize = cached(function(prop){ 331 | testEl = testEl || document.createElement('div'); 332 | prop = camelize(prop); 333 | if(prop !== 'filter' && (prop in testEl.style)){ 334 | return prop; 335 | } 336 | // 首字母大写 337 | var upper = prop.charAt(0).toUppercase() + prop.slice(1); 338 | for(var i = 0; i < prefixes.length; i++){ 339 | var prefixed = prefixes[i] + upper; 340 | if(prefixed in testEl.style){ 341 | return prefixed; 342 | } 343 | } 344 | }) 345 | ``` 346 | 347 | 348 | 349 | #### updateStyle 350 | 351 | ```javascript 352 | function updateStyle(oldVnode, vnode){ 353 | var data = vnode.data; 354 | var oldData = oldVnode.data; 355 | if(!data.staticStyle && !data.style && !oldData.staticStyle && !oldData.style){ 356 | return ; 357 | } 358 | 359 | var cur = name; 360 | var el = vnode.elm; 361 | var oldStaticStyle = oldVnode.data.staticStyle; 362 | var oldStyleBinding = oldVnode.data.style || {}; 363 | 364 | var oldStyle = oldStaticStyle || oldStyleBinding; 365 | var style = normalizeStyleBinding(vnode.data.style) || {}; 366 | 367 | vnode.data.style = style.__ob__ ? extend({}, style) : style; 368 | var newStyle = getStyle(vnode, true); 369 | 370 | for(name in oldStyle){ 371 | if(newStyle[name] == null){ 372 | setProp(el, name, ''); 373 | } 374 | } 375 | for(name in newStyle){ 376 | cur = newStyle[name]; 377 | if(cur !== oldStyle[name]){ 378 | setProp(el, name, cur == null ? '' : cur); 379 | } 380 | } 381 | } 382 | ``` 383 | 384 | 385 | 386 | ------ 387 | 388 | 389 | 390 | ### Class 391 | 392 | 393 | 394 | #### addClass 395 | 396 | ```javascript 397 | function addClass(el, cls){ 398 | if(!cls || !(cls = cls.trim())){ 399 | return; 400 | } 401 | // classList兼容IE10+ 还是部分兼容 402 | if(el.classList){ 403 | if(cls.indexOf(' ') > -1){ 404 | // 空白分割添加到类中 405 | cls.split(/\s+/).forEach(function(c){ return el.classList.add(c); }); 406 | } else{ 407 | el.classList.add(cls); 408 | } 409 | } else{ 410 | var cur = ' ' + (el.getAttribute('class') || '') + ' '; 411 | if(cur.indexOf(' ' + cls + ' ') < 0){ 412 | el.setAttribute('class', (cur + cls).trim()); 413 | } 414 | } 415 | } 416 | ``` 417 | 418 | 419 | 420 | 421 | 422 | #### removeClass 423 | 424 | ```javascript 425 | // DOM节点与要删除的类 426 | function removeClass(el, cls){ 427 | // 这里已经对cls调用了trim()方法 428 | if(!cls || !cls = cls.trim()){ 429 | return ; 430 | } 431 | // classList兼容IE10+ 432 | // 如果支持 直接按空格分割字符串并调用remove方法删除类 433 | if(el.classList){ 434 | if(cls.indexOf(' ') > -1){ 435 | cls.split(/\s+/).forEach(function(c){ return el.classList.remove(c); }); 436 | } else{ 437 | el.classList.remove(cls); 438 | } 439 | } 440 | // 对IE10以下做兼容 不知道为啥不用className 441 | else{ 442 | // 修正类集合字符串 两边加空白 443 | // 'a b' => ' a b ' 444 | var cur = ' ' + (el.getAttribute('class') || '') + ' '; 445 | // 修正目标类 'a' => ' a ' 446 | var tar = ' ' + cls + ' '; 447 | // 进行替换 ' a b ' => ' b ' 448 | while(cur.indexOf(tar) >= 0){ 449 | cur = cur.replace(tar, ' '); 450 | } 451 | // 再调用trim()操作 ' b ' => 'b' 452 | el.setAttribute('class', cur.trim()); 453 | } 454 | } 455 | ``` 456 | 457 | 458 | 459 | ------ 460 | 461 | -------------------------------------------------------------------------------- /18-walkAST.md: -------------------------------------------------------------------------------- 1 | # walkAST 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | --- 10 | 11 | 12 | 13 | ### optimize 14 | 15 | ```javascript 16 | var isStaticKey; 17 | var isPlatformReservedTag; 18 | 19 | var genStaticKeysCached = cached(genStaticKey$1); 20 | 21 | // 优化:将部分不会变的DOM节点移除虚拟树 22 | function optimize(root, options){ 23 | if(!root){ return } 24 | isStaticKey = genStaticKeysCached(options.staticKeys || ''); 25 | isPlatformReservedTag = options.isReservedTag || no; 26 | // 1、标记所有非静态节点 27 | markStatic$1(root); 28 | // 2、标记静态节点 29 | markStaticRoots(root, false); 30 | } 31 | ``` 32 | 33 | 34 | 35 | ### genStaticKey$1 36 | 37 | ```javascript 38 | function genStaticKey$1(keys){ 39 | return makeMap( 40 | 'type,tag,attrsList,attrsMap,plain,parent,children,attrs' + 41 | (keys ? ',' +keys : '') 42 | ); 43 | } 44 | ``` 45 | 46 | 47 | 48 | ### markStatic$1 49 | 50 | ```javascript 51 | function markStatic$1(node){ 52 | node.static = isStatic(node); 53 | if(node.type === 1){ 54 | if(!isPlatformReservedTag(node.tag) && node.tag !== 'slot' node.attrsMap['inline-template'] == null){ return; } 55 | for(var i = 0; l = node.children.length; i < l; i++){ 56 | var child = node.children[i]; 57 | markStatic$1(child); 58 | if(!child.static){ 59 | node.static = false; 60 | } 61 | } 62 | } 63 | } 64 | ``` 65 | 66 | 67 | 68 | ### markStaticRoots 69 | 70 | ```javascript 71 | function markStaticRoots(node, isInFor){ 72 | if(node.type === 1){ 73 | if(node.static || node.once){ 74 | node.staticInFor = isInFor; 75 | } 76 | if(node.static && node.children.length && !(node.children.length === 1 && node.children[0].type === 3)){ 77 | node.staticRoot = true; 78 | return ; 79 | } else{ 80 | node.staticRoot = false; 81 | } 82 | if(node.children){ 83 | for(var i = 0; l = node.children.length; i < l; i++){ 84 | markStaticRoots(node.children[i], isInFor || !!node.for); 85 | } 86 | } 87 | if(node.ifConditions){ 88 | walkThroughConditionsBlocks(node.ifConditions, isInFor); 89 | } 90 | } 91 | } 92 | ``` 93 | 94 | 95 | 96 | ### walkThroughConditionsBlocks 97 | 98 | ```javascript 99 | function walkThroughConditionsBlocks(conditionBlocks, isInFor){ 100 | for(var i = 1, len = conditionBlocks.length; i < len; i++){ 101 | markStaticRoots(conditionBlocks[i].block, isInFor); 102 | } 103 | } 104 | ``` 105 | 106 | 107 | 108 | ### isStatic 109 | 110 | ```javascript 111 | function isStatic(node){ 112 | if(node.type === 2){ 113 | return false; 114 | } 115 | if(node.type === 3){ 116 | return true; 117 | } 118 | return !!(node.pre || 119 | (!node.hasBindings && 120 | !node.if && !node.for && 121 | !isBuiltInTag(node.tag) && 122 | isPlatformReservedTag(node.tag) && 123 | !isDirectChildOfTemplateFor(node) && 124 | Object.keys(node).every(isStaticKey) 125 | )); 126 | } 127 | ``` 128 | 129 | 130 | 131 | ### isDirectChildOfTemplateFor 132 | 133 | ```javascript 134 | function isDirectChildOfTemplateFor(node){ 135 | while(node.parent){ 136 | node = node.parent; 137 | if(node.tag !== 'template'){ 138 | return false; 139 | } 140 | if(node.for){ 141 | return true; 142 | } 143 | } 144 | return false; 145 | } 146 | ``` 147 | 148 | 149 | 150 | 151 | 152 | --- 153 | 154 | 155 | 156 | 157 | 158 | ## gen 159 | 160 | 161 | 162 | 163 | 164 | ### utils 165 | 166 | ```javascript 167 | var fnExpRE = /^\s*([\w$_]+|\([^)]*?\))\s*=>|^function\s*\(/; 168 | var simplePathRE = /^\s*[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['.*?']|\[".*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*\s*$/; 169 | 170 | // 键盘事件别名 171 | var keyCodes = { 172 | esc: 27, 173 | tab: 9, 174 | enter: 13, 175 | space: 32, 176 | up: 38, 177 | left: 37, 178 | right: 39, 179 | down: 40, 180 | 'delete': [8, 46] 181 | }; 182 | ``` 183 | 184 | 185 | 186 | ### genGuard 187 | 188 | ```javascript 189 | var genGuard = function(condition) { return ('if(' + condition + ')return null;'); }; 190 | ``` 191 | 192 | 193 | 194 | ### modifierCode 195 | 196 | ```javascript 197 | // 事件小尾巴 198 | var modifierCode = { 199 | stop: '$event.stopPropagation();', 200 | prevent: '$event.preventDefault();', 201 | self: genGuard('$event.target !== $event.currentTarget'), 202 | ctrl: genGuard('!$event.ctrlKey'), 203 | shift: genGuard('!$event.shiftKey'), 204 | alt: genGuard('!$event.altKey'), 205 | meta: genGuard('!$event.metaKey'), 206 | left: genGuard('"button" in $event && $event.button !== 0'), 207 | middle: genGuard('"button" in $event && $event.button !== 1'), 208 | right: genGuard('"button" in $event && $event.button !== 2') 209 | }; 210 | ``` 211 | 212 | 213 | 214 | ### genHandlers 215 | 216 | ```javascript 217 | function genHandlers(events, native, warn){ 218 | var res = native ? 'nativeOn:{' : 'on:{'; 219 | for(var name in events){ 220 | var handler = events[name]; 221 | // 右键点击没卵用 222 | if('development' !== 'production' && name === 'click' && handler && handler.modifiers && handler.modifiers.right){ 223 | warn( 224 | 'Use "contextmenu" instead of "click.right" since right clicks ' + 225 | 'do not actually fire "click" events.' 226 | ); 227 | } 228 | res += '"' + name + '":' + (genHandler(name. handler)) + ','; 229 | } 230 | return res.slice(0, -1) + '}' 231 | } 232 | ``` 233 | 234 | 235 | 236 | ### genHandler 237 | 238 | ```javascript 239 | function genHandler(name, handler){ 240 | if(!handler) { return 'function(){}'; } 241 | if(Array.isArray(handler)){ 242 | return ('[' + (handler.map(function(handler) { return genHandler(name, handler); }).join(',')) + ']'); 243 | } 244 | 245 | var isMethodPath = simplePathRE.test(handler.value); 246 | var isFunctionExpression = fnExpRE.test(handler.value); 247 | 248 | if(!handler.modifiers){ 249 | return isMethodPath || isFunctionExpression ? handler.value : ('function($event){' + (handler.value) + '}'); 250 | } else{ 251 | var code = ''; 252 | var genModifierCode = ''; 253 | var keys = []; 254 | for(var key in handler.modifiers){ 255 | if(modifierCode[key]){ 256 | genModifierCode += modifierCode[key]; 257 | // 左右 258 | if(keyCodes[key]){ 259 | keys.push(key); 260 | } 261 | } else{ 262 | keys.push(key); 263 | } 264 | } 265 | if(keys.length){ 266 | code += genKeyFilter(keys); 267 | } 268 | 269 | if(genModifierCode){ 270 | code += genModifierCode; 271 | } 272 | var handlerCode = isMethodPath ? handler.value + '($event)' : isFunctionExpression ? 273 | ('(' + (handler.value) + ')($event)') : handler.value; 274 | return ('function($event){' + code + handlerCode + '}'); 275 | } 276 | } 277 | ``` 278 | 279 | 280 | 281 | ### genKeyFilter 282 | 283 | ```javascript 284 | function genKeyFilter(keys){ 285 | return ('if(!("button" in $event)&&' + (keys.map(genFilterCode).join('&&')) + ')return null;'); 286 | } 287 | ``` 288 | 289 | 290 | 291 | ### genFilterCode 292 | 293 | ```javascript 294 | function genFilterCode(key){ 295 | var keyVal = parseInt(key, 10); 296 | if(keyVal){ 297 | return ('$event.keyCode!==' + keyVal); 298 | } 299 | var alias = keyCodes[key]; 300 | return ('_k($event.keyCode,' + (JSON.stringify(key)) + (alias ? ',' + JSON.stringify(alias) : '') + ')'); 301 | } 302 | ``` 303 | 304 | 305 | 306 | ### bind$1 307 | 308 | ```javascript 309 | function bind$1(el, dir){ 310 | el.wrapData = function(code){ 311 | return ('_b(' + code + ',"' + (el.tag) + '",' + (dir.value) + (dir.modifiers && dir.modifiers.prop ? ',true' : '') + ')'); 312 | }; 313 | } 314 | ``` 315 | 316 | 317 | 318 | ### baseDirectives 319 | 320 | ```javascript 321 | var baseDirectives = { 322 | bind: bind$1, 323 | cloak: noop 324 | } 325 | ``` 326 | 327 | 328 | 329 | 330 | 331 | --- 332 | 333 | 334 | 335 | 336 | 337 | ## configurable 338 | 339 | 340 | 341 | 342 | 343 | ```javascript 344 | var warn$3; 345 | var transforms$1; 346 | var dataGenFns; 347 | var platformDirectives$1; 348 | var isPlatformReservedTag$1; 349 | var staticRenderFns; 350 | var onceCount; 351 | var currentOptions; 352 | ``` 353 | 354 | 355 | 356 | 357 | 358 | ### generate 359 | 360 | ```javascript 361 | function generate(ast, options){ 362 | // 缓存之前的AST 363 | var prevStaticRenderFns = staticRenderFns; 364 | var currentStaticRenderFns = staticRenderFns = []; 365 | var prevOnceCount = onceCount; 366 | onceCount = 0; 367 | currentOptions = options; 368 | warn$3 = options.warn || baseWarn; 369 | transforms$1 = pluckModuleFunction(options.modules, 'transformCode'); 370 | dataGenFns = pluckModuleFunction(options.modules, 'genData'); 371 | platformDirectives$1 = options.directives || {}; 372 | isPlatformReservedTag$1 = options.isReservedTag || no; 373 | var code = ast ? genElement(ast) : '_c("div")'; 374 | staticRenderFns = prevStaticRenderFns; 375 | onceCount = prevOnceCount; 376 | 377 | return { 378 | render: ('with(this){return ' + code + '}'), 379 | staticRenderFns: currentStaticRenderFns 380 | } 381 | } 382 | ``` 383 | 384 | 385 | 386 | ### genElement 387 | 388 | ```javascript 389 | function genElement(el){ 390 | if(el.staticRoot && !el.staticProcessed){ 391 | return genStatic(el); 392 | } else if(el.once && !el.onceProcessed){ 393 | return genOnce(el); 394 | } else if(el.for && !el.forProcessed){ 395 | return genFor(el); 396 | } else if(el.if && !el.ifProcessed){ 397 | return genIf(el); 398 | } else if(el.tag === 'template' && !el.slotTarget){ 399 | return genChildren(el) || 'void 0'; 400 | } else if(el.tag === 'slot'){ 401 | return genSlot(el); 402 | } else{ 403 | // 组件 404 | var code; 405 | if(el.component){ 406 | code = genComponent(el.component, el); 407 | } else{ 408 | var data = el.plain ? undefined : genData(el); 409 | var children = el.inlineTemplate ? null : genChildren(el, true); 410 | code = '_c("' + (el.tag) + '"' + (data ? (',' + data) : '') + (children ? (',' + children) : '') + ')'; 411 | } 412 | for(var i = 0; i < transforms$1.length; i++){ 413 | code = transforms$1[i](el, code); 414 | } 415 | return code; 416 | } 417 | } 418 | ``` 419 | 420 | 421 | 422 | ### genStatic 423 | 424 | ```javascript 425 | function genStatic(el){ 426 | el.staticProcessed = true; 427 | staticRenderFns.push(('with(this){return ' + (genElement(el)) + '}')); 428 | return ('_m(' + (staticRenderFns.length - 1) + (el.staticInFor ? ',true' : '') + ')'); 429 | } 430 | ``` 431 | 432 | 433 | 434 | ### genOnce 435 | 436 | ```javascript 437 | function genOnce(el){ 438 | el.onceProcessed = true; 439 | if(el.if && !el.ifProcessed){ 440 | return genIf(el); 441 | } else if(el.staticInFor){ 442 | var key = ''; 443 | var parent = el.parent; 444 | while(parent){ 445 | if(parent.for){ 446 | key = parent.key; 447 | break; 448 | } 449 | parent = parent.parent; 450 | } 451 | if(!key){ 452 | 'development' !== 'production' && warn$3( 453 | 'v-once can only be used inside v-for that is keyed. ' 454 | ); 455 | return genElement(el); 456 | } 457 | return ('_o(' + (genElement(el)) + ',' + (onceCount++) + (key ? (',' + key) : '') + ')'); 458 | } else{ 459 | return genStatic(el); 460 | } 461 | } 462 | ``` 463 | 464 | 465 | 466 | ### genIf 467 | 468 | ```javascript 469 | function genIf(el){ 470 | // 防止递归 471 | el.ifProcessed = true; 472 | return genIfConditions(el.ifConditions.slice()); 473 | } 474 | ``` 475 | 476 | 477 | 478 | ### genIfConditions 479 | 480 | ```javascript 481 | function genIfConditions(conditions){ 482 | if(!conditions.length){ 483 | return '_e()'; 484 | } 485 | 486 | var condition = conditions.shift(); 487 | if(condition.exp){ 488 | return ('(' + (condition.exp) + ')?' + (genTernaryExp(condition.block)) + ':' + (genIfConditions(conditions))) 489 | } else{ 490 | return ('' + (genTernaryExp(condition.block))); 491 | } 492 | 493 | function genTernaryExp(el){ 494 | return el.once ? genOnce(el) : genElement(el); 495 | } 496 | } 497 | ``` 498 | 499 | 500 | 501 | ### genFor 502 | 503 | ```javascript 504 | function genFor(el){ 505 | var exp = el.for; 506 | var alias = el.alias; 507 | var iterator1 = el.iterator1 ? (',' + (el.iterator1)) : ''; 508 | var iterator2 = el.iterator2 ? (',' + (el.iterator2)) : ''; 509 | 510 | if('development' !== 'production' && maybeComponent(el) && el.tag !== 'slot' && el.tag !== 'template' && !el.key){ 511 | warn$3( 512 | '<' + (el.tag) + ' v-for="' + alias + ' in ' + exp + '">: component lists rendered with ' + 513 | 'v-for should have explicit keys. ' + 514 | 'See https://vuejs.org/guide/list.html#key for more info.', 515 | true 516 | ); 517 | } 518 | // 防递归 519 | el.forProcessed = true; 520 | return '_l((' + exp + '),' + 521 | 'function(' + alias + iterator1 + iterator2 + '){' + 522 | 'return ' + (genElement(el)) + 523 | '})'; 524 | } 525 | ``` 526 | 527 | 528 | 529 | ### genData 530 | 531 | ```javascript 532 | function genData(el){ 533 | var data = '{'; 534 | 535 | // 指令 536 | var dirs = genDirectives(el); 537 | if(dirs) { data += dirs + ','; } 538 | 539 | // key 540 | if(el.key){ 541 | data += 'key:' + (el.key) + ','; 542 | } 543 | // ref 544 | if(el.ref){ 545 | data += 'ref:' + (el.ref) + ','; 546 | } 547 | if(el.refInFor){ 548 | data += 'refInForL:true;'; 549 | } 550 | // pre 551 | if(el.pre){ 552 | data += 'pre:true,'; 553 | } 554 | // 555 | if(el.component){ 556 | data += 'tag:"' + (el.tag) + '",'; 557 | } 558 | // 559 | for(var i = 0; i < dataGenFns.length; i++){ 560 | data += dataGenFns[i](el); 561 | } 562 | // attr 563 | if(el.attrs){ 564 | data += 'attrs:{' + (genProps(el.attrs)) + '},'; 565 | } 566 | // DOM props 567 | if(el.props){ 568 | data += 'domProps:{' + (genProps(el.props)) + '},'; 569 | } 570 | // 事件处理 571 | if(el.events){ 572 | data += (genHandlers(el.events, false, warn$3)) + ','; 573 | } 574 | if(el.nativeEvents){ 575 | data += (genHandlers(el.nativeEvents, true, warn$3)) + ','; 576 | } 577 | // slot 578 | if(el.slotTarget){ 579 | data += 'slot:' + (el.slotTarget) + ','; 580 | } 581 | // scoped slot 582 | if(el.scopedSlots){ 583 | data += (genScopedSlots(el.scopedSlots)) + ','; 584 | } 585 | // v-model组件 586 | if(el.model){ 587 | data += 'model:{value:' + (el.model.value) + ',callback:' + (el.model.callback) + ',expression:' + (el.model.expression) + '},'; 588 | } 589 | // inline-template 590 | if(el.inlineTemplate){ 591 | var inlineTemplate = genInlineTemplate(el); 592 | if(inlineTemplate){ 593 | data += inlineTemplate + ','; 594 | } 595 | } 596 | // 去掉最后的逗号闭合大括号 597 | data = data.replace(/,$/,'') + '}'; 598 | // v-bind 599 | if(el.wrapData){ 600 | data = el.wrapData(data); 601 | } 602 | return data; 603 | } 604 | ``` 605 | 606 | 607 | 608 | ### genDirectives 609 | 610 | ```javascript 611 | function genDirectives(el){ 612 | var dirs = el.directives; 613 | if(!dirs) { return; } 614 | var res = 'directives:['; 615 | var hasRuntime = false; 616 | var i, l, dir, needRuntime; 617 | for(var i = 0, l = dirs.length; i < l; i++){ 618 | dir = dirs[i]; 619 | needRuntime = true; 620 | var gen = platformDirectives$1[dir.name] || baseDirectives[dir.name]; 621 | if(gen){ 622 | needRuntime = !!gen(el, dir, warn$3); 623 | } 624 | if(needRuntime){ 625 | hasRuntime = true; 626 | res += '{name:"' + (dir.name) + '",rawName:"' + (dir.rawName) + '"' + (dir.value ? (',value:(' + (dir.value) + '),expression:' + (JSON.stringify(dir.value))) : '') + (dir.arg ? (',arg:"' + (dir.arg) + '"') : '') + (dir.modifiers ? (',modifiers:' + (JSON.stringify(dir.modifiers))) : '') + '},'; 627 | } 628 | } 629 | if(hasRuntime){ 630 | return res.slice(0, -1) + ']'; 631 | } 632 | } 633 | ``` 634 | 635 | 636 | 637 | ### genInlineTemplate 638 | 639 | ```javascript 640 | function genInlineTemplate(el){ 641 | var ast = el.children[0]; 642 | if('development' !== 'production' && (el.children.length > 1 || ast.type !== 1)){ 643 | warn$3('Inline-template components must have exactly one child element.'); 644 | } 645 | if(ast.type === 1){ 646 | var inlineRenderFns = generate(ast, currentOptions); 647 | return ('inlineTemplate:{render:function(){' + (inlineRenderFns.render) + '},staticRenderFns:[' + (inlineRenderFns.staticRenderFns.map(function(code){ reutrn ('function(){' + code +'}'); }).join(',')) + ']}'); 648 | } 649 | } 650 | ``` 651 | 652 | 653 | 654 | ### genScopedSlots 655 | 656 | ```javascript 657 | function genScopedSlots(slots){ 658 | return ('scopedSlots:_u([' + (Object.keys(slots).map(function(key) { return genScopedSlot(key, slots[key]); }).join(',')) + '])') 659 | } 660 | ``` 661 | 662 | 663 | 664 | ### genScopedSlot 665 | 666 | ```javascript 667 | function genScopedSlot(key, el){ 668 | return '[' + key + ',function(' + (String(el.attrsMap.scope)) + '){' + 669 | 'return ' + (el.tag === 'template' ? 670 | genChildren(el) || 'void 0') : 671 | genElement(el) + '}]'; 672 | } 673 | ``` 674 | 675 | 676 | 677 | ### genChildren 678 | 679 | ```javascript 680 | function genChildren(el, checkSkip){ 681 | var children = el.children; 682 | if(children.length){ 683 | var el$1 = children[0]; 684 | // 优化简单的v-for 685 | if(children.length === 1 && el$1.for && el$1.tag !== 'template' && el$1.tag !== 'slot'){ 686 | return genElement(el$1); 687 | } 688 | var normalizationType = checkSkip ? getNormalizationType(children) : 0; 689 | return ('[' + (children.map(genNode).join(',')) + ']' + (normalizationType ? (',' + normalizationType) : '')); 690 | } 691 | } 692 | ``` 693 | 694 | 695 | 696 | 697 | 698 | --- 699 | 700 | 701 | 702 | 703 | 704 | ## normaliaztion 705 | 706 | 707 | 708 | 709 | 710 | ### getNormalizationType 711 | 712 | ```javascript 713 | function getNormalizationType(children){ 714 | var res = 0; 715 | for(var i = 0; i < children.length; i++){ 716 | var el = children[i]; 717 | if(el.type !== 1){ 718 | continue; 719 | } 720 | if(needsNormalization(el) || (el.ifConditions && el.ifConditions.some(function(c) { return needsNormalization(c.block); }))){ 721 | res = 2; 722 | break; 723 | } 724 | if(maybeComponent(el) || (el.ifConditions && el.ifConditions.some(function(c) { return maybeComponent(c.block); }))){ 725 | res = 1; 726 | } 727 | } 728 | return res; 729 | } 730 | ``` 731 | 732 | 733 | 734 | ### needsNormalization 735 | 736 | ```javascript 737 | function needsNormalization(el){ 738 | return el.for !== undefined || el.tag === 'template' || el.tag === 'slot'; 739 | } 740 | ``` 741 | 742 | 743 | 744 | ### maybeComponent 745 | 746 | ```javascript 747 | function maybeComponent(el){ 748 | return !isPlatformReservedTag$1(el.tag); 749 | } 750 | ``` 751 | 752 | 753 | 754 | ### genNode 755 | 756 | ```javascript 757 | function genNode(node){ 758 | if(node.type === 1){ 759 | return genElement(node); 760 | } else{ 761 | return genText(node); 762 | } 763 | } 764 | ``` 765 | 766 | 767 | 768 | ### genText 769 | 770 | ```javascript 771 | function genText(text){ 772 | return ('_v(' + (text.type === 2 ? text.expression : transformSpecialNewlines(JSON.stringify(text.text))) + ')'); 773 | } 774 | ``` 775 | 776 | 777 | 778 | ### genSlot 779 | 780 | ```javascript 781 | function genSlot(el){ 782 | var slotName = el.slotName || '"default"'; 783 | var children = genChildren(el); 784 | var res = '_t(' + slotName + (children ? (',' + children) : ''); 785 | var attrs = el.attrs && ('{' + (el.attrs.map(function(a){ return ((camelize(a.name)) + ':' + (a.value)); }).join(',')) + '}'); 786 | var bind$$1 = el.attrsMap['v-bind']; 787 | if((attrs || bind$$1) && !children){ 788 | res += ',null'; 789 | } 790 | if(attrs){ 791 | res += ',' + attrs; 792 | } 793 | if(bind$$1){ 794 | res += (attrs ? '' : ',null') + ',' + bind$$1; 795 | } 796 | return res + ')'; 797 | } 798 | ``` 799 | 800 | 801 | 802 | ### genComponent 803 | 804 | ```javascript 805 | function genComponent(componentName, el){ 806 | var children = el.inlineTemplate ? null : genChilaren(el, true); 807 | return ('_c(' + componentName + ',' + (genData(el)) + (children ? (',' + children) : '') + ')'); 808 | } 809 | ``` 810 | 811 | 812 | 813 | ### genProps 814 | 815 | ```javascript 816 | function genProps(props){ 817 | var res = ''; 818 | for(var i = 0; i < props.length; i++){ 819 | var prop =props[i]; 820 | res += '"' + (prop.name) + '":' + (transformSpecialNewlines(prop.value)) + ','; 821 | } 822 | return res.slice(0, -1); 823 | } 824 | ``` 825 | 826 | 827 | 828 | ### transformSpecialNewLines 829 | 830 | ```javascript 831 | function transformSpecialNewlines(text){ 832 | return text.replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029'); 833 | } 834 | ``` 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | -------------------------------------------------------------------------------- /19-Omega.md: -------------------------------------------------------------------------------- 1 | # END 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ## utils 10 | 11 | 12 | 13 | 14 | 15 | ### prohibitedKeywordRE 16 | 17 | ```javascript 18 | // 禁用的关键词 不包含typeof等运算符 19 | var prohibitedKeywordRE = new RegExp('\\b' + ( 20 | 'do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' + 21 | 'super,throw,while,yield,delete,export,import,return,switch,default,' + 22 | 'extends,finally,continue,debugger,function,arguments' 23 | ).split(',').join('\\b|\\b') + '\\b'); 24 | ``` 25 | 26 | 27 | 28 | ### unaryOperatorsRE 29 | 30 | ```javascript 31 | var unaryOperatorsRE = new RexExp('\\b' + ( 32 | 'delete,typeof,void' 33 | ).split(',').join('\\s*\\([^\\)]*\\)|\\b') + '\\s*\\([^\\)]*\\'); 34 | ``` 35 | 36 | 37 | 38 | ### identRE 39 | 40 | ```javascript 41 | var identRE = /[A-Za-z_$][\w$]*/; 42 | ``` 43 | 44 | 45 | 46 | ### stripStringRE 47 | 48 | ```javascript 49 | var stripStringRE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*\$\{|\}(?:[^`\\]|\\.)*`|`(?:[^`\\]|\\.)*`/g; 50 | ``` 51 | 52 | 53 | 54 | 55 | 56 | --- 57 | 58 | 59 | 60 | 61 | 62 | ## check 63 | 64 | 65 | 66 | 67 | 68 | ### detectErrors 69 | 70 | ```javascript 71 | function detectErrors(ast){ 72 | var errors = []; 73 | if(ast){ 74 | checkNode(ast, errors); 75 | } 76 | return errors; 77 | } 78 | ``` 79 | 80 | 81 | 82 | ### checkNode 83 | 84 | ```javascript 85 | function checkNode(node, errors){ 86 | if(node.type === 1){ 87 | for(var name in node.attrsMap){ 88 | if(dirRE.test(name)){ 89 | var value = node.attrsMap[name]; 90 | if(value){ 91 | if(name === 'v-for'){ 92 | checkFor(node, ('v-for="' + value + '"'), errors); 93 | } else if(onRE.test(name)){ 94 | checkEvent(value, (name + '="' + value + '"'), errors); 95 | } else{ 96 | checkExpression(value, (name + '="' + value + '"'), errors); 97 | } 98 | } 99 | } 100 | } 101 | if(node.children){ 102 | for(var i = 0; i < node.children.length; i++){ 103 | checkNode(node.children[i], errors); 104 | } 105 | } 106 | } else if(node.type === 2){ 107 | checkExpression(node.expression, node.text, errors); 108 | } 109 | } 110 | ``` 111 | 112 | 113 | 114 | ### checkEvent 115 | 116 | ```javascript 117 | function checkEvent(exp, text, errors){ 118 | var stipped = exp.replace(stripStringRE, ''); 119 | var keywordMatch = stipped.match(unaryOperatorsRE); 120 | if(keywordMatch && stipped.charAt(keywordMatch.index - 1) !== '$'){ 121 | errors.push( 122 | 'avoid using Javascript unary operator as property name: ' + 123 | '"' + (keywordMatch[0]) + '" in expression ' + (text.trim()) 124 | ); 125 | } 126 | checkExpression(exp, text, errors); 127 | } 128 | ``` 129 | 130 | 131 | 132 | ### checkFor 133 | 134 | ```javascript 135 | function checkFor(node, text, errors){ 136 | checkExpression(node.for || '', text, errors); 137 | checkIdentifier(node.alias, 'v-for alias', text, errors); 138 | checkIdentifier(node.iterator1, 'v-for iterator', text, errors); 139 | checkIdentifier(node.iterator2, 'v-for iterator', text, errors); 140 | } 141 | ``` 142 | 143 | 144 | 145 | ### checkIdentifier 146 | 147 | ```javascript 148 | function checkIdentifier(ident, type, text, errors){ 149 | if(typeof ident === 'string' && !identRE.test(ident)){ 150 | errors.push(('invalid' + type + ' "' + ident + '" in expression: ' + (text.trim()))); 151 | } 152 | } 153 | ``` 154 | 155 | 156 | 157 | ### checkExpression 158 | 159 | ```javascript 160 | function checkExpression(exp, text, errors){ 161 | try{ 162 | new Function(('return ' + exp)); 163 | } catch(e){ 164 | var keywordMatch = exp.replace(stripStringRE, '').match(prohibitedKeywordRE); 165 | if(keywordMatch){ 166 | errors.push( 167 | 'avoid using Javascript keyword as property name: ' + 168 | '"' + (keywordMatch[0]) + '" in expression ' + (text.trim()) 169 | ); 170 | } else{ 171 | errors.push(('invalid expression: ' + (text.trim()))); 172 | } 173 | } 174 | } 175 | ``` 176 | 177 | 178 | 179 | 180 | 181 | --- 182 | 183 | 184 | 185 | 186 | 187 | ## compile 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | ### baseCompile 196 | 197 | ```javascript 198 | function baseCompile(template, options){ 199 | var ast = parse(template.trim(), options); 200 | optimize(ast, options); 201 | var code = generate(ast, options); 202 | return { 203 | ast: ast, 204 | render: code.render, 205 | staticRenderFns: code.staticRenderFns 206 | } 207 | } 208 | ``` 209 | 210 | 211 | 212 | ### makeFunction 213 | 214 | ```javascript 215 | function makeFunction(code, errors){ 216 | try{ 217 | return new Function(code); 218 | } catch(err){ 219 | errors.push({err: err, code: code }); 220 | return noop; 221 | } 222 | } 223 | ``` 224 | 225 | 226 | 227 | ### createCompiler 228 | 229 | ```javascript 230 | function createCompiler(baseOptions){ 231 | var functionCompileCache = Object.create(null); 232 | 233 | // compile 234 | function compile(template, options){ 235 | var finalOptions = Object.create(baseOptions); 236 | var errors = []; 237 | var tips = []; 238 | finalOptions.warn = function(msg, tip$$1){ 239 | (tip$$1 ? tips : errors).push(msg); 240 | }; 241 | 242 | if(options){ 243 | if(options.modules){ 244 | finalOptions.modules = (baseOptions.modules || []).concat(options.modules); 245 | } 246 | if(options.directives){ 247 | finalOptions.directives = extend( 248 | Object.create(baseOptions.directives), 249 | options.directives 250 | ); 251 | } 252 | 253 | for(var key in options){ 254 | if(key !== 'modules' && key !== 'directives'){ 255 | finalOptions[key] = options[key]; 256 | } 257 | } 258 | } 259 | var compiled = baseCompile(template, finalOptions); 260 | errors.push.apply(errors, detectErrors(compiled.ast)); 261 | compiled.errors = errors; 262 | compiled.tips = tips; 263 | return compiled; 264 | } 265 | 266 | // compileToFunctions 267 | function compileToFunctions(template, options, vm){ 268 | options = options || {}; 269 | 270 | try{ 271 | new Function('return 1'); 272 | } catch(e){ 273 | if(e.toString().match(/unsafe-eval|CSP/)){ 274 | warn( 275 | 'It seems you are using the standalone build of Vue.js in an ' + 276 | 'environment with Content Security Policy that prohibits unsafe-eval. ' + 277 | 'The template compiler cannot work in this environment . Consider ' + 278 | 'relaxing the policy to allow unsafe-eval or pre-compiling your ' + 279 | 'templates into render functions.' 280 | ); 281 | } 282 | } 283 | 284 | var key = options.delimiters ? String(options.delimiters) + template : template; 285 | if(functionCompileCache[key]){ 286 | return functionCompileCache[key]; 287 | } 288 | 289 | // compile 290 | var compiled = compile(template, options); 291 | 292 | // errors,tips信息打印 293 | if(compiled.errors && compiled.errors.length){ 294 | warn( 295 | 'Error compiling template:\n\n' + template + '\n\n' + 296 | compiled.errors.map(function(e){ return ('- ' + e); }).join('\n') + '\n', 297 | vm 298 | ); 299 | } 300 | if(compiled.tips && compiled.tips.length){ 301 | compiled.tips.forEach(function(msg){ return tip(msg, vm); }); 302 | } 303 | 304 | var res = {}; 305 | var fnGenErrors = []; 306 | res.render = makeFunction(compiled.render, fnGenErrors); 307 | var l = compiled.staticRenderFns.length; 308 | res.staticRenderFns = new Array(l); 309 | for(var i = 0; i < l; i++){ 310 | res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i], fnGenErrors); 311 | } 312 | 313 | // 编译器问题检测 314 | if((!compiled.errors || !compiled.errors.length) && fnGenErrors.length){ 315 | warn( 316 | 'Failed to generate render function:\n\n' + 317 | fnGenErrors.map(function(ref){ 318 | var err = ref.err; 319 | var code = ref.code; 320 | return ((err.toString()) + ' in\n\n' + code +'\n'); 321 | }).join('\n'), 322 | vm 323 | ); 324 | } 325 | return (functionCompileCache[key] = res); 326 | } 327 | return { 328 | compile: compile, 329 | compileToFunctions: compileToFunctions 330 | } 331 | } 332 | ``` 333 | 334 | 335 | 336 | ### transformNode 337 | 338 | ```javascript 339 | function transformNode(el, options){ 340 | var warn = options.warn || baseWarn; 341 | var staticClass = getAndRemoveAttr(el, 'class'); 342 | if('development' !== 'production' && staticClass){ 343 | var expression = parseText(staticClass, options.delimiters); 344 | if(expression){ 345 | warn( 346 | 'class="' + staticClass + '": ' + 347 | 'Interpolation inside attributes has been removed. ' + 348 | 'Use v-bind or the colon shorthand instead. For example, ' + 349 | 'instead of

, use
.' 350 | ); 351 | } 352 | } 353 | if(staticClass){ 354 | el.staticClass = JSON.stringify(staticClass); 355 | } 356 | var classBinding = getBindingAttr(el, 'class', false); 357 | if(classBinding){ 358 | el.classBinding = classBinding; 359 | } 360 | } 361 | ``` 362 | 363 | 364 | 365 | ### genData$1 366 | 367 | ```javascript 368 | function genData$1(el){ 369 | var data = ''; 370 | if(el.staticClass){ 371 | data += 'staticClass:' + (el.staticClass) + ','; 372 | } 373 | if(el.classBinding){ 374 | data += 'class:' + (el.classBinding) + ','; 375 | } 376 | return data; 377 | } 378 | ``` 379 | 380 | 381 | 382 | 383 | 384 | ### klass$1 385 | 386 | 387 | 388 | ```javascript 389 | var klass$1 = { 390 | staticKeys: ['staticClass'], 391 | transformNode: transformNode, 392 | genData: genData$1 393 | } 394 | ``` 395 | 396 | 397 | 398 | 399 | 400 | --- 401 | 402 | 403 | 404 | 405 | 406 | ### transformNode$1 407 | 408 | ```javascript 409 | function transformNode$1(el, options){ 410 | var warn = options.warn || baseWarn; 411 | var staticStyle = getAndRemoveAttr(el, 'style'); 412 | if(staticStyle){ 413 | var expression = parseText(staticStyle, options.delimiters); 414 | if(expression){ 415 | warn( 416 | 'style="' + staticStyle + '": ' + 417 | 'Interpolation inside attribute has been removed. ' + 418 | 'Use v-bind or the colon shorthand instead. For example, ' + 419 | 'instead of
, use
.' 420 | ); 421 | } 422 | el.staticStyle = JSON.stringify(parseStyleText(staticStyle)); 423 | } 424 | var styleBinding = getBindingAttr(el, 'style', false); 425 | if(styleBinding){ 426 | el.styleBinding = styleBinding; 427 | } 428 | } 429 | ``` 430 | 431 | 432 | 433 | ### genData$2 434 | 435 | ```javascript 436 | function genData$2(el){ 437 | var data = ''; 438 | if(el.staticStyle){ 439 | data += 'staticStyle:' + (el.staticStyle) + ','; 440 | } 441 | if(el.styleBinding){ 442 | data += 'style:(' + (el.styleBinding) + '),'; 443 | } 444 | return data; 445 | } 446 | ``` 447 | 448 | 449 | 450 | ### style$1 451 | 452 | 453 | 454 | ```javascript 455 | var style$1 = { 456 | staticKeys: ['staticStyle'], 457 | transformNode: transformNode$1, 458 | genData: genData$2 459 | }; 460 | ``` 461 | 462 | 463 | 464 | ### module$1 465 | 466 | ```javascript 467 | var modules$1 = [ 468 | klass$1, 469 | style$1 470 | ]; 471 | ``` 472 | 473 | 474 | 475 | 476 | 477 | ## /* */ 478 | 479 | 480 | 481 | ### text 482 | 483 | ```javascript 484 | function text(el, dir){ 485 | if(dir.value){ 486 | addProp(el, 'textContent', ('_s(' + (dir.value) + ')')); 487 | } 488 | } 489 | ``` 490 | 491 | 492 | 493 | ### html 494 | 495 | ```javascript 496 | function html(el, dir){ 497 | if(dir.value){ 498 | addProp(el, 'innerHTML', ('_s(' + (dir.value) + ')')); 499 | } 500 | } 501 | ``` 502 | 503 | 504 | 505 | ### directives$1 506 | 507 | ```javascript 508 | var directives$1 = { 509 | // 第5900行的函数 510 | model: model, 511 | text: text, 512 | html: html 513 | }; 514 | ``` 515 | 516 | 517 | 518 | 519 | 520 | --- 521 | 522 | 523 | 524 | 525 | 526 | ## baseOptions 527 | 528 | 529 | 530 | 531 | 532 | ```javascript 533 | function baseOptions = { 534 | expectHTML: true, 535 | modules: modules$1, 536 | directives: directives$1, 537 | isPreTag: isPreTag, 538 | isUnaryTag: isUnaryTag, 539 | mustUseProp: mustUseProp, 540 | canBeLeftOpenTag: canBeLeftOpenTag, 541 | isReservedTag: isReservedTag, 542 | getTagNamespace: getTagNamespace, 543 | staticKeys: genStaticKeys(modules$1) 544 | } 545 | ``` 546 | 547 | 548 | 549 | ### ref$1 550 | 551 | ```javascript 552 | var ref$1 = createCompiler(baseOptions); 553 | ``` 554 | 555 | 556 | 557 | ### compileToFunctions 558 | 559 | ```javascript 560 | var compileToFunctions = ref$1.compileToFunctions; 561 | ``` 562 | 563 | 564 | 565 | ### idToTemplate 566 | 567 | ```javascript 568 | var idToTemplate = cached(function(id){ 569 | var el = query(id); 570 | return el && el.innerHTML; 571 | }); 572 | ``` 573 | 574 | 575 | 576 | --- 577 | 578 | 579 | 580 | 581 | 582 | ## mount 583 | 584 | 585 | 586 | 587 | 588 | ```javascript 589 | // 手动挂载mount 590 | var mount = Vue$3.prototype.$mount; 591 | Vue$3.prototype.$mount = function(el, hydrating){ 592 | el = el && query(el); 593 | 594 | if(el === document.body || el === document.documentElement){ 595 | 'development' !== 'production' && warn( 596 | 'Do not mount Vue to or - mount to normal elements instead.' 597 | ); 598 | return this; 599 | } 600 | 601 | var options = this.$options; 602 | 603 | if(!options.render){ 604 | var template = options.template; 605 | if(template){ 606 | if(typeof template === 'string'){ 607 | if(template.charAt(0) === '#'){ 608 | template = idToTemplate(template); 609 | if('development' !== 'production' && !template){ 610 | warn( 611 | ('Template element not found or is empty: ' + (options.template)), 612 | this 613 | ); 614 | } 615 | } 616 | } else if(template.nodeType){ 617 | template = template.innerHTML; 618 | } else{ 619 | warn('invalid template option:' + template, this); 620 | return this; 621 | } 622 | } else if(el){ 623 | template = getOuterHTML(el); 624 | } 625 | if(template){ 626 | if('development' !== 'production' && config.performance && mark){ 627 | mark('compile'); 628 | } 629 | 630 | var ref = compileToFunctions(template, { 631 | shouldDecodeNewlines: shouldDecodeNewlines, 632 | delimiters: options.delimiters 633 | }, this); 634 | var render = ref.render; 635 | var staticRenderFns = ref.staticRenderFns; 636 | options.render = render; 637 | options.staticRenderFns = staticRenderFns; 638 | 639 | if('development' !== 'production' && config.performance && mark){ 640 | mark('compile end'); 641 | measure(((this._name) + ' compile'), 'compile', 'compile end'); 642 | } 643 | } 644 | } 645 | return mount.call(this, el, hydrating); 646 | }; 647 | ``` 648 | 649 | 650 | 651 | ### getOuterHTML 652 | 653 | ```javascript 654 | function getOuterHTML(el){ 655 | if(el.outerHTML){ 656 | return el.outerHTML; 657 | } else{ 658 | var container = document.createElement('div'); 659 | container.appendChild(el.cloneNode(true)); 660 | return container.innerHTML; 661 | } 662 | } 663 | ``` 664 | 665 | 666 | 667 | 668 | 669 | ## /* */ 670 | 671 | 672 | 673 | 674 | 675 | ```javascript 676 | Vue$3.compile = compileToFunctions; 677 | reutrn Vue$3; 678 | ``` 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue 2 | 3 | 4 | -   此repo仅为保存当初的抄录痕迹 5 | -   源码详细分析见我的blog:http://www.cnblogs.com/QH-Jimmy/ 6 | --------------------------------------------------------------------------------