├── src ├── index.js ├── my-mvvm │ ├── test.gif │ ├── test.html │ ├── observer │ │ ├── dep.js │ │ └── index.js │ ├── index.html │ ├── example.html │ ├── utils │ │ ├── index.js │ │ └── dom.js │ ├── watcher │ │ └── index.js │ ├── xue.js │ ├── compiler │ │ └── index.js │ ├── test.js │ ├── observeArray.md │ ├── mvvm.js │ └── README.md ├── my-promise │ ├── EventLoop.jpg │ ├── example.html │ ├── README.md │ └── promise.js ├── my-copy │ ├── README.md │ └── copy.html ├── my-virtual-dom │ ├── virtual-dom.html │ └── README.md ├── my-setTimeout │ ├── setTimeout.html │ └── README.md ├── test │ └── test.html ├── index.html ├── my-assign │ ├── assign.html │ └── README.md ├── my-unique │ ├── unique.js │ ├── example.html │ └── README.md ├── my-clone │ ├── example.html │ ├── README.md │ └── clone.js ├── my-ajax │ ├── ajax.html │ └── README.md ├── my-debounceThrottle │ ├── debounceThrottle.html │ └── README.md ├── my-drag │ ├── drag.html │ └── README.md ├── my-observer │ ├── example.html │ ├── README.md │ └── observer.js ├── my-bind │ ├── bind.html │ └── README.md └── Vue移动端开发的那些坑.md ├── .eslintignore ├── .gitignore ├── .DS_Store ├── assets └── qrcode.jpg ├── .babelrc ├── .editorconfig ├── modules ├── my-copy │ └── copy.js ├── my-assign │ └── assign.js ├── my-virtual-dom │ ├── virtual-dom.js │ ├── utils.js │ ├── element.js │ ├── diff.js │ ├── patch.js │ └── list-diff.js ├── my-ajax │ ├── xhr.js │ └── ajax.js ├── my-setTimeout │ └── setTimeout.js ├── my-drag │ └── drag.js ├── my-bind │ └── bind.js └── my-debounceThrottle │ └── debounceThrottle.js ├── .jshintrc ├── LICENSE ├── Makefile ├── webpack.config.js ├── package.json ├── README.md └── .eslintrc /src/index.js: -------------------------------------------------------------------------------- 1 | document.write('

Hello Overwrite

') 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | build2 3 | config 4 | *.js 5 | node_modules 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | npm-debug.log 4 | dist/ 5 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuqiang521/overwrite/HEAD/.DS_Store -------------------------------------------------------------------------------- /assets/qrcode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuqiang521/overwrite/HEAD/assets/qrcode.jpg -------------------------------------------------------------------------------- /src/my-mvvm/test.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuqiang521/overwrite/HEAD/src/my-mvvm/test.gif -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2"], 3 | "plugins": [], 4 | "comments": false 5 | } 6 | -------------------------------------------------------------------------------- /src/my-promise/EventLoop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuqiang521/overwrite/HEAD/src/my-promise/EventLoop.jpg -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /src/my-copy/README.md: -------------------------------------------------------------------------------- 1 | # [copy 复制粘贴](https://github.com/xuqiang521/overwrite/blob/master/modules/my-copy/copy.js) 2 | 3 | > overwrite copy() copy 4 | 5 | ## 相关 API 6 | 7 | [Range](https://developer.mozilla.org/zh-CN/docs/Web/API/Range) 8 | 9 | [Selection](https://developer.mozilla.org/zh-CN/docs/Web/API/Selection) 10 | -------------------------------------------------------------------------------- /modules/my-copy/copy.js: -------------------------------------------------------------------------------- 1 | function copy (el) { 2 | const range = document.createRange() 3 | const end = el.childNodes.length 4 | range.setStart(el, 0) 5 | range.setEnd(el, end) 6 | 7 | const selection = window.getSelection() 8 | selection.removeAllRanges() 9 | selection.addRange(range) 10 | document.execCommand("copy", false, null) 11 | selection.removeRange(range) 12 | } 13 | -------------------------------------------------------------------------------- /src/my-virtual-dom/virtual-dom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | simple virtual dom 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/my-setTimeout/setTimeout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | polyfill 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "quotmark": "single", 14 | "regexp": true, 15 | "undef": true, 16 | "unused": true, 17 | "strict": true, 18 | "trailing": true, 19 | "smarttabs": true, 20 | "white": true 21 | } 22 | -------------------------------------------------------------------------------- /src/test/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Hot Reload Test 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | OVERWRITE 8 | 9 | 10 |

Overwrite Some Methods Of JavaScript

11 | 12 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /src/my-mvvm/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 |

13 |

{{ a }}

14 |
15 | 16 | 23 | 24 | -------------------------------------------------------------------------------- /src/my-mvvm/observer/dep.js: -------------------------------------------------------------------------------- 1 | let uid = 0; 2 | class Dep { 3 | constructor() { 4 | this.id = uid++; 5 | this.subs = [] 6 | } 7 | 8 | addSub (sub) { 9 | this.subs.push(sub) 10 | } 11 | 12 | removeSub (sub) { 13 | let index = this.subs.indexOf(sub); 14 | if (index > -1) { 15 | this.subs.splice(index, 1) 16 | } 17 | } 18 | 19 | notify () { 20 | this.subs.forEach(sub => { 21 | sub.update() 22 | }) 23 | } 24 | 25 | depend () { 26 | Dep.target.addDep(this); 27 | } 28 | } 29 | Dep.target = null; 30 | 31 | export default Dep; 32 | -------------------------------------------------------------------------------- /src/my-assign/assign.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | assign 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 | 23 | -------------------------------------------------------------------------------- /src/my-unique/unique.js: -------------------------------------------------------------------------------- 1 | /** 2 | * [indexOf 检测数组元素位置] 3 | * @param {[type]} e [元素] 4 | */ 5 | Array.prototype.indexOf= function(e) { 6 | for (var i = 0, l = this.length; i < l; i++) { 7 | if (this[i] === e) { 8 | return i; 9 | } 10 | } 11 | return -1; 12 | } 13 | /** 14 | * [unique 数组唯一] 15 | */ 16 | Array.prototype.unique = function () { 17 | var self = this; 18 | // 声明缓存空数组 19 | var tmp = new Array(); 20 | for (var i = 0, l = self.length; i < l; i++) { 21 | if (tmp.indexOf(self[i]) === -1) { 22 | tmp.push(self[i]); 23 | } 24 | } 25 | return tmp; 26 | } 27 | -------------------------------------------------------------------------------- /src/my-unique/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | example 8 | 9 | 10 | 11 | 12 | 13 | 22 | 23 | -------------------------------------------------------------------------------- /src/my-mvvm/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 | 9 |
10 | 11 |

{{ b }}

12 |

13 |
14 | 15 | 30 | -------------------------------------------------------------------------------- /modules/my-assign/assign.js: -------------------------------------------------------------------------------- 1 | Object.assign = function (target) { 2 | if (target === null || target === undefined) { 3 | throw new TypeError('Cannot convert null or undefined to object') 4 | } 5 | target = Object(target) 6 | 7 | var index = 1 8 | , args = arguments 9 | , len = args.length 10 | for (; index < len; index++) { 11 | var source = args[index] 12 | 13 | if (source !== null) { 14 | for (var key in source) { 15 | if (Object.prototype.hasOwnProperty.call(source, key)) { 16 | target[key] = source[key] 17 | } 18 | } 19 | } 20 | } 21 | 22 | return target 23 | } 24 | -------------------------------------------------------------------------------- /modules/my-virtual-dom/virtual-dom.js: -------------------------------------------------------------------------------- 1 | import el from './element' 2 | import diff from './diff' 3 | import patch from './patch' 4 | 5 | 6 | let ul = el('ul', { id: 'list' }, [ 7 | el('li', { class: 'item' }, ['Item 1']), 8 | el('li', { class: 'item', key: 1 }, ['Item 2']), 9 | el('li', { class: 'item' }, ['Item 3']) 10 | ]) 11 | let ul1 = el('ul', { id: 'list' }, [ 12 | el('li', { class: 'item' }, ['Item 3']), 13 | el('li', { class: 'item' }, ['Item 1']), 14 | el('li', { class: 'item', key: 1 }, ['Item 2']) 15 | ]) 16 | let ulRoot = ul.render() 17 | let patches = diff(ul, ul1); 18 | console.log(patches) 19 | document.body.appendChild(ulRoot); 20 | 21 | patch(ulRoot, patches) 22 | -------------------------------------------------------------------------------- /src/my-clone/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | example 8 | 9 | 10 | 11 | 12 | 13 | 27 | 28 | -------------------------------------------------------------------------------- /src/my-clone/README.md: -------------------------------------------------------------------------------- 1 | # clone 2 | 3 | >overwrite `clone()` 4 | 5 | ## 对象深度克隆实现思路 6 | 7 | + 若参数是数组`Array` 8 | + 若参数是对象`Object` 9 | + 若参数非数组非对象,正常赋值 10 | 11 | ## Demo 12 | ```javascript 13 | // if Array 14 | if (Obj instanceof Array) { 15 | // dosomething for Array Obj 16 | } 17 | // if Object and Obj is not a function 18 | if (Obj instanceof Object && typeof Obj !== 'function') { 19 | // dosomething for Object Obj 20 | } 21 | // else 22 | else { 23 | // dosomething for other 24 | } 25 | ``` 26 | 27 | ## 注意事项 28 | 29 | 这里需要注意的事项还是基于传递过来的参数判定,我们都知道`function` 也是 `Object`的一类,所以此处一定需要进行一次`typeof`。大家可以先试一下去掉typeof这一层判定,那么当参数Obj为`function`的时候,他会直接进入`Object`的逻辑判定里面,这样便不能实现一个`deep clone`了! 30 | -------------------------------------------------------------------------------- /src/my-ajax/ajax.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Ajax 9 | 10 | 11 | 12 | 13 | 14 | 29 | 30 | -------------------------------------------------------------------------------- /src/my-debounceThrottle/debounceThrottle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 函数防抖,函数节流 9 | 10 | 11 | 12 | 13 | 14 | 15 | 27 | 28 | -------------------------------------------------------------------------------- /src/my-assign/README.md: -------------------------------------------------------------------------------- 1 | # [assign](https://github.com/xuqiang521/overwrite/tree/master/modules/my-assign) 2 | 3 | >overwrite assign() assign 4 | 5 | ## Object.assign(target, ...sources) 6 | 7 | - `target` 目标对象 8 | - `sources` 源对象(可多个) 9 | - `返回值` 目标对象 10 | 11 | ## 描述 12 | 13 | 如果目标对象中的属性具有相同的键,则属性将被源中的属性覆盖。后来的源的属性将类似地覆盖早先的属性。 14 | 15 | ## Demo 16 | 17 | ```javascript 18 | // 复制一个object 19 | var obj = { a: 1 }; 20 | var copy = Object.assign({}, obj); 21 | console.log(copy); // { a: 1 } 22 | ``` 23 | 24 | ## 深度copy?并不能实现 25 | 26 | 针对深度拷贝,需要使用其他方法,因为 Object.assign() 拷贝的是属性值。假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。 27 | 28 | ## More 29 | 30 | [`Object.assign`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) 31 | -------------------------------------------------------------------------------- /src/my-clone/clone.js: -------------------------------------------------------------------------------- 1 | /** 2 | * [clone 深度对象克隆] 3 | * @param {[type]} Obj [参数对象] 4 | */ 5 | function clone(Obj) { 6 | // 定义buf进行缓存 7 | var buf; // 创建一个空数组 8 | // if Array 9 | if (Obj instanceof Array) { 10 | // dosomething for Array Obj 11 | buf = []; 12 | var i = Obj.length 13 | while (i--) { 14 | buf[i] = clone(Obj[i]); 15 | } 16 | return buf; 17 | } 18 | // if Object and Obj is not a function 19 | if (Obj instanceof Object && typeof Obj !== 'function') { 20 | // dosomething for Object Obj 21 | buf = {}; // 创建一个空对象 22 | for (var key in Obj) { 23 | buf[key] = clone(Obj[key]); 24 | } 25 | return buf 26 | } 27 | // else 28 | else { 29 | // dosomething for other 30 | return Obj; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/my-copy/copy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | copy 9 | 10 | 15 | 16 | 17 | 18 |
复制到剪贴板的内容
19 |
复制
20 | 21 | 28 | 29 | -------------------------------------------------------------------------------- /src/my-drag/drag.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | simple 9 | 10 | 21 | 22 | 23 | 24 |
25 | 26 |
27 | 28 | 36 | 37 | -------------------------------------------------------------------------------- /modules/my-ajax/xhr.js: -------------------------------------------------------------------------------- 1 | function createXHR() { 2 | if (typeof XMLHttpRequest !== 'undefined') { 3 | return new XMLHttpRequest(); 4 | } else if (typeof ActiveXObject !== 'undefined') { 5 | if (typeof arguments.callee.activeXString !== 'undefined') { 6 | var versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp']; 7 | var i, len; 8 | for (i = 0, len = versions.length; i < len; i++) { 9 | try { 10 | new ActiveXObject(versions[i]); 11 | arguments.callee.activeXString = versions[i]; 12 | break; 13 | } catch (error) { 14 | 15 | } 16 | } 17 | } 18 | return new ActiveXObject(arguments.callee.activeXString); 19 | } else { 20 | throw new Error('No XHR Object available.') 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/my-setTimeout/README.md: -------------------------------------------------------------------------------- 1 | # [setTimeout](https://github.com/xuqiang521/overwrite/tree/master/modules/my-setTimeout) 2 | 3 | >overwrite setTimeout() polyfill 4 | 5 | ## `WindowOrWorkerGlobalScope.setTimeout()` 6 | 7 | ```javascript 8 | // 语法1 9 | var timeoutId = scope.setTimeout(function[, delay, param1, param2, ...]); 10 | // 语法2 11 | var timeoutId = scope.setTimeout(code[, delay]); 12 | ``` 13 | ## 参数及返回值 14 | 15 | - `function` : 想要在delay毫秒之后执行的函数。 16 | - `delay`(可选): 延迟毫秒数,即函数调用会在改延迟后发生,省略该参数,则delay为0,但此时实际延时并不是0,而是4ms左右的延迟 17 | - `param1, ..., paramN`(可选) 18 | - `code`:delay毫秒之后需要执行的代码字符串 19 | - 返回值:返回值timeoutID是一个正整数,表示定时器的编号。这个值可以传递给clearTimeout()来取消该定时 20 | 21 | ## polifill 22 | 23 | 具体看[setTimeout.js](https://github.com/xuqiang521/overwrite/blob/master/modules/my-setTimeout/setTimeout.js) 24 | 25 | -------------------------------------------------------------------------------- /src/my-observer/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | example 8 | 9 | 10 | 11 |

请打开控制面板进行结果查询

12 | 13 | 31 | 32 | -------------------------------------------------------------------------------- /src/my-virtual-dom/README.md: -------------------------------------------------------------------------------- 1 | # [virtual-dom](https://github.com/xuqiang521/overwrite/tree/master/modules/my-virtual-dom) 2 | 3 | >overwrite virtualdom() simple virtual dom 4 | 5 | ## 进展 6 | 7 | - [x] Element (VDOM) 8 | - [x] utils 方法集合 9 | - [x] O(n^3) => O(n) diff算法 10 | - [x] O(m*n) => O(max(m,n)) list diff 算法 11 | - [x] patch方法,将不同的virtual dom比较并转为真实节点 12 | 13 | ## 图解 14 | 15 | `Element` 16 | 17 | ![](https://static.oschina.net/uploads/space/2017/0615/172520_IstJ_2912341.png) 18 | 19 | `diff` 20 | 21 | ![](https://static.oschina.net/uploads/space/2017/0615/195650_NBDZ_2912341.png) 22 | 23 | `list-diff` 24 | 25 | ![](https://static.oschina.net/uploads/space/2017/0616/195723_XlhA_2912341.png) 26 | 27 | ## 详细解析 28 | 29 | [合格前端系列第五弹-Virtual Dom && Diff](https://zhuanlan.zhihu.com/p/27437595) 30 | 31 | [虚拟 DOM 内部是如何工作的](http://www.zcfy.cc/article/the-inner-workings-of-virtual-dom-rajaraodv-medium-3248.html) 32 | -------------------------------------------------------------------------------- /src/my-drag/README.md: -------------------------------------------------------------------------------- 1 | # [drag](https://github.com/xuqiang521/overwrite/tree/master/modules/my-drag) 2 | 3 | >overwrite drag() simple drag library 4 | 5 | 6 | 7 | ## 设计思路 8 | 9 | ### getCss(el, styleName) 10 | 11 | - `el` : DOM对象 12 | - `styleName` : styleName,如`'left'`或者`'top'` 13 | 14 | ### drag(el) 15 | 16 | - `el` :DOM对象,该对象需要进行position绝对定位(static除外) 17 | - 用法如下 18 | ```javascript 19 | var el = document.getElementById('test'); 20 | 21 | drag(el); 22 | ``` 23 | 24 | ### 基本实现 25 | 26 | ```javascript 27 | # getCss() 28 | var getCss = function(b, a) { 29 | return b.currentStyle ? b.currentStyle[a] : document.defaultView.getComputedStyle(b, false)[a] 30 | }; 31 | 32 | # drag() 33 | var drag = function(a) { 34 | a.onmousedown = function(c) { 35 | // todo something 36 | }; 37 | document.onmouseup = function() { 38 | // todo something 39 | }; 40 | document.onmousemove = function(h) { 41 | // todo something 42 | } 43 | }; 44 | ``` 45 | 46 | -------------------------------------------------------------------------------- /src/my-promise/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | example 8 | 9 | 10 | 11 |

请打开控制面板进行结果查询

12 | 13 | 36 | 37 | -------------------------------------------------------------------------------- /src/my-observer/README.md: -------------------------------------------------------------------------------- 1 | # Observer 2 | 3 | >overwrite `Observer()` 4 | 5 | 6 | ## 思路 7 | 8 | 观察者模式:又被称作发布-订阅模式或者消息机制,定义了一种依赖关系,解决了主体对象与观察者之间功能的耦合。
9 | 这里我将写三个方法,分别做`消息发布`,`消息订阅`以及`消息的取消发布`。 10 | 11 | ## 实现 12 | ```javascript 13 | // 观察者 14 | var Observer = function () {}; 15 | // 发布消息 16 | Observer.prototype.$on = function () {}; 17 | // 取消发布 18 | Observer.prototype.$off = function () {}; 19 | // 消息监听触发 20 | Observer.prototype.$emit = function () {}; 21 | // 仅发布一次消息,消息触发后就取消该消息 22 | Observer.prototype.$once = function () {}; 23 | ``` 24 | 25 | ## 效果 26 | ```javascript 27 | var test = new Observer(); 28 | test.$on('test', function (data) { 29 | console.log(data); 30 | }); 31 | test.$emit('test', 'i am an example'); 32 | // i am an example 33 | test.$off(); 34 | // test._events = {} // No Properties 35 | test.$once('test1', function(){ 36 | console.log(123) 37 | }) 38 | // test._events => Object {test1:Array(1)} 39 | test.$emit('test1'); 40 | // 123 41 | // test._events =>Object {test1: Array(0)} 42 | ``` 43 | -------------------------------------------------------------------------------- /src/my-mvvm/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | example 8 | 9 | 10 | 11 |
12 |

{{b}}

13 | 14 | 15 |

{{ a }}

16 | 17 |
18 | 19 | 39 | 40 | -------------------------------------------------------------------------------- /src/my-bind/bind.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | bind 9 | 10 | 11 | 12 | 13 | 14 | 15 | 45 | 46 | -------------------------------------------------------------------------------- /src/my-unique/README.md: -------------------------------------------------------------------------------- 1 | # unique 数组去重 2 | 3 | >overwrite `unique()` 4 | 5 | ## 关于数组去重 6 | 7 | 其实这个话题还是非常常见的,这里我列举几个数组去重的例子。 8 | + `ES6去重方法` 9 | 10 | ```javascript 11 | let arr = [1, 1, 2, 3, '5', 3]; 12 | [...new Set(arr)]; 13 | // [1, 2, 3, "5"] 14 | ``` 15 | 16 | + `对面字面量` 17 | ```javascript 18 | function keyValueForArray(arr) { 19 | var obj = Object.create(null), 20 | i = 0, 21 | len = 0; 22 | if (Array.isArray(arr) && arr.length > 0) { 23 | len = arr.length; 24 | for (i = 0; i < len; i += 1) { 25 | obj[arr[i]] = arr[i]; 26 | } 27 | return Object.values(obj); 28 | } 29 | return []; 30 | } 31 | let arr = [1, 1, 2, 3, '5', 3]; 32 | keyValueForArray(arr); 33 | // [1, 2, 3, "5"] 34 | ``` 35 | 36 | + `Array.from 搭配 Set` : Array.from方法可以将 Set 结构转为数组。 37 | 38 | ```javascript 39 | let arr = [1, 1, 2, 3, '5', 3]; 40 | arr = Array.from(new Set(arr)) 41 | // [1, 2, 3, "5"] 42 | ``` 43 | 44 | 其实还有很多很多方法,除了这里提到的几种,我还会重写一个去重方法到`Array.prototype`上去。 45 | 46 | ## 思路 47 | 48 | - 重写遍历方法indexOf 49 | - 数组元素判定去重 50 | 51 | ## 总结 52 | 53 | 多看看一些常用方法,自己多写点,总归是有好处的 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 qiangdada 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | .PHONY: test build 3 | 4 | default: help 5 | 6 | install: 7 | npm install 8 | 9 | install-cn: 10 | npm install --registry=http://registry.npm.taobao.org 11 | 12 | build: 13 | npm run build 14 | 15 | http: 16 | npm run http 17 | 18 | new: 19 | node build/bin/new.js $(filter-out $@,$(MAKECMDGOALS)) 20 | 21 | del: 22 | node build/bin/delete.js $(filter-out $@,$(MAKECMDGOALS)) 23 | 24 | dev: 25 | npm run dev 26 | 27 | # clean: 28 | # rm ./dist/*.js 29 | 30 | help: 31 | @echo " \033[35mmake\033[0m \033[1m命令使用说明\033[0m" 32 | @echo " \033[35mmake install\033[0m\t\033[0m\t\033[0m\t\033[0m\t--- 安装依赖" 33 | @echo " \033[35mmake install-cn\033[0m\t\033[0m\t\033[0m\t--- 安装淘宝镜像" 34 | @echo " \033[35mmake build\033[0m\t\033[0m\t\033[0m\t\033[0m\t--- 脚本打包" 35 | @echo " \033[35mmake new [中文名]\033[0m\t--- 创建新重写方法 生成对应文件 例如 'make new test 重写test()'" 36 | @echo " \033[35mmake del \033[0m\t\033[0m\t--- 删除重写方法 删除对应文件 例如 'make del test'" 37 | @echo " \033[35mmake dev\033[0m\t\033[0m\t\033[0m\t\033[0m\t--- 开发模式" 38 | @echo " \033[35mmake http\033[0m\t\033[0m\t\033[0m\t\033[0m\t--- 静态服务模式" 39 | -------------------------------------------------------------------------------- /modules/my-setTimeout/setTimeout.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | // setTimeout 3 | setTimeout(function (arg1) { 4 | if (arg1 === 'test') return 5 | var __native_setTimeout__ = window.setTimeout 6 | window.setTimeout = function ( 7 | callback, 8 | delay, 9 | /*, argumentToPass1, argumentToPass2, etc.*/ 10 | ) { 11 | // 获取func,delay以后的参数 12 | var args = Array.prototype.slice.call(arguments, 2) 13 | return __native_setTimeout__(callback instanceof Function ? function () { 14 | callback.apply(null, args) 15 | } : callback, delay) 16 | } 17 | }, 0 , 'test') 18 | 19 | // setInterval 20 | var interval = setInterval(function (arg1) { 21 | clearInterval(interval) 22 | if (arg1 === 'test') return 23 | var __native_setInterval__ = window.setInterval 24 | window.setInterval = function ( 25 | callback, 26 | delay, 27 | /*, argumentToPass1, argumentToPass2, etc.*/ 28 | ) { 29 | var args = Array.prototype.slice.call(arguments, 2) 30 | return __native_setInterval__(callback instanceof Function ? function () { 31 | callback.apply(null, args) 32 | } : callback, delay) 33 | } 34 | }, 0, 'test') 35 | }()) 36 | -------------------------------------------------------------------------------- /src/my-bind/README.md: -------------------------------------------------------------------------------- 1 | # [myBind and softBind](https://github.com/xuqiang521/overwrite/tree/master/modules/my-bind) 2 | 3 | >overwrite bind() and softBind() 4 | 5 | ## Function.prototype.myBind(thisArg[, arg1[, arg2[, ...]]]) 6 | 7 | - `thisArg` 当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用new 操作符调用绑定函数时,该参数无效 8 | - `arg1, arg2, ...` 当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。 9 | 10 | ```javascript 11 | function foo1 (something) { 12 | this.a = something 13 | } 14 | 15 | var obj_1 = {} 16 | var bar = foo1.myBind(obj_1) 17 | bar(2) 18 | console.log(obj_1.a) // 2 19 | var baz = new bar(3) 20 | console.log(baz.a) // 3 21 | console.log(obj_1.a) // 2 22 | ``` 23 | 24 | ## Function.prototype.softBind(thisArg[, arg1[, arg2[, ...]]]) 25 | 26 | - `thisArg` 当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。如果this绑定到全局对象或者undefined上,那就把指定的默认对象绑定到this,否则不修改this 27 | - `arg1, arg2, ...` 当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。 28 | 29 | ```javascript 30 | function foo2 () { 31 | console.log('name: ' + this.name) 32 | } 33 | var obj1 = { name: 'obj1' } 34 | var obj2 = { name: 'obj2' } 35 | var obj3 = { name: 'obj3' } 36 | 37 | var foo2Obj = foo2.softBind(obj1) 38 | foo2Obj() // name: obj1 39 | 40 | obj2.foo2 = foo2.softBind(obj1) 41 | obj2.foo2() // name: obj2 42 | foo2Obj.call(obj3) // name: obj3 43 | 44 | setTimeout(obj2.foo2, 10) // name: obj1 45 | // => 应用了软绑定(如果this绑定到全局对象或者undefined上,那就把指定的默认对象绑定到this,否则不修改this) 46 | ``` -------------------------------------------------------------------------------- /src/my-mvvm/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author qiangdada 2017.06.07 3 | */ 4 | 5 | /** 6 | * [def 定义对象属性] 7 | * @param {Object} obj 对象 8 | * @param {String} key 键值 9 | * @param {*} val 属性值 10 | * @param {Boolean} enumerable 是否可被枚举 11 | */ 12 | function def (obj, key, val, enumerable) { 13 | Object.defineProperty(obj, key, { 14 | value: val, 15 | enumerable: !!enumerable, 16 | configurable: true, 17 | writable: true 18 | }) 19 | } 20 | 21 | /** 22 | * [protoAugment 支持 __proto__ , 直接将对象的__proto__指向 src 这一组方法] 23 | * @param {Object} target [目标对象] 24 | * @param {Object} src [Array方法] 25 | */ 26 | function protoAugment(target, src) { 27 | target.__proto__ = src; 28 | } 29 | 30 | /** 31 | * [copyAugment 不支持 __proto__ , 遍历这一组方法,依次添加到对象中,作为隐藏属性(即 enumerable: false,不能被枚举)] 32 | * @param {Object} target [目标对象] 33 | * @param {Object} src [Array方法] 34 | * @param {Array} keys [Array方法键值集合] 35 | */ 36 | function copyAugment(target, src, keys) { 37 | for (let i = 0, l = keys.length; i < l; i++) { 38 | let key = keys[i]; 39 | def(target, key, src[key]); 40 | } 41 | } 42 | 43 | // 返回一个布尔值,指示对象是否具有指定的属性作为自身(不继承)属性 44 | function hasOwn(obj, key) { 45 | return hasOwnProperty.call(obj, key); 46 | } 47 | 48 | 49 | module.exports = { 50 | def: def, 51 | protoAugment: protoAugment, 52 | copyAugment: copyAugment, 53 | hasOwn: hasOwn 54 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | var OpenBrowserWebpackPlugin = require('open-browser-webpack-plugin') 4 | 5 | var port = process.env.NODE_ENV || 9000; 6 | 7 | var uri = "http://localhost:" + port; 8 | 9 | module.exports = { 10 | entry: { 11 | entry: './modules/my-virtual-dom/virtual-dom.js', 12 | entry1: './src/index.js', 13 | entry2: './src/my-mvvm/xue.js' 14 | }, 15 | output: { 16 | path: path.resolve(__dirname, 'dist'), 17 | filename: '[name].js' 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.js$/, 23 | exclude: /node_modules/, 24 | loader: 'babel-loader', 25 | options: { 26 | presets: ['es2015'] 27 | } 28 | } 29 | ] 30 | }, 31 | plugins: [ 32 | new OpenBrowserWebpackPlugin({ 33 | url: uri 34 | }) 35 | // new webpack.HotModuleReplacementPlugin() 36 | ], 37 | resolve: { 38 | extensions: ['.js', '.json', '.css'], 39 | alias: { 40 | 'utils': path.resolve(__dirname, 'src/my-mvvm/utils'), 41 | 'compiler': path.resolve(__dirname, 'src/my-mvvm/compiler'), 42 | 'watcher': path.resolve(__dirname, 'src/my-mvvm/watcher'), 43 | 'observer': path.resolve(__dirname, 'src/my-mvvm/observer') 44 | } 45 | }, 46 | 47 | devServer: { 48 | contentBase: path.resolve(__dirname, 'src'), 49 | // hot: true, 50 | port: port 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /modules/my-drag/drag.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var params = { 4 | left: 0, 5 | top: 0, 6 | currentX: 0, 7 | currentY: 0, 8 | flag: false 9 | }; 10 | var getCss = function(b, a) { 11 | return b.currentStyle ? b.currentStyle[a] : document.defaultView.getComputedStyle(b, false)[a] 12 | }; 13 | var drag = function(a) { 14 | if (getCss(a, "left") !== "auto") { 15 | params.left = getCss(a, "left") 16 | } 17 | if (getCss(a, "top") !== "auto") { 18 | params.top = getCss(a, "top") 19 | } 20 | a.onmousedown = function(c) { 21 | params.flag = true; 22 | if (!c) { 23 | c = window.event; 24 | a.onselectstart = function() { 25 | return false 26 | } 27 | } 28 | var d = c; 29 | params.currentX = d.clientX; 30 | params.currentY = d.clientY 31 | }; 32 | document.onmouseup = function() { 33 | params.flag = false; 34 | if (getCss(a, "left") !== "auto") { 35 | params.left = getCss(a, "left") 36 | } 37 | if (getCss(a, "top") !== "auto") { 38 | params.top = getCss(a, "top") 39 | } 40 | }; 41 | document.onmousemove = function(h) { 42 | var i = h ? h : window.event; 43 | if (params.flag) { 44 | var d = i.clientX, 45 | c = i.clientY; 46 | var g = d - params.currentX, 47 | f = c - params.currentY; 48 | a.style.left = parseInt(params.left) + g + "px"; 49 | a.style.top = parseInt(params.top) + f + "px" 50 | } 51 | } 52 | }; 53 | 54 | -------------------------------------------------------------------------------- /src/my-debounceThrottle/README.md: -------------------------------------------------------------------------------- 1 | # [debounceThrottle](https://github.com/xuqiang521/overwrite/tree/master/modules/my-debounceThrottle) 2 | 3 | >overwrite debounce() and throttle() 函数防抖,函数节流 4 | 5 | ## 函数防抖(debounce) 6 | 7 | `使用场景`:现在我们需要做一个搜索框,当用户输入文字,执行keyup事件的时候,需要发出异步请求去进行结果查询。但如果用户快速输入了一连串字符,例如是5个字符,那么此时会瞬间触发5次请求,这肯定不是我们希望的结果。我们想要的是用户停止输入的时候才去触发查询的请求,这个时候函数防抖可以帮到我们 8 | 9 | `原理`:让函数在上次执行后,满足等待某个时间内不再触发次函数后再执行,如果触发则等待时间重新计算 10 | 11 | `用法参数设计`: 12 | 13 | ```javascript 14 | /** 15 | * [debounce 函数防抖] 16 | * @param {Function} fn [需要进行函数防抖的函数] 17 | * @param {Number} wait [需要等待的时间] 18 | * @param {Boolean} immediate [调用时是否立即执行一次] 19 | */ 20 | function debounce (fn, wait, immediate) { 21 | // do something 22 | } 23 | ``` 24 | 25 | 26 | ## 函数节流(throttle) 27 | 28 | `使用场景`:window.onscroll,以及window.onresize等,每间隔某个时间去执行某函数,避免函数的过多执行 29 | 30 | `原理`:与函数防抖不同,它不是要在每完成某个等待时间后去执行某个函数,而是要每间隔某个时间去执行某个函数 31 | 32 | `用法参数设计`: 33 | 34 | ```javascript 35 | /** 36 | * [throttle 函数节流] 37 | * @param {Function} fn [需要进行函数节流的函数] 38 | * @param {Number} wait [函数执行的时间间隔] 39 | * @param {Object} options [执行参数] 40 | */ 41 | // options = { 42 | // leading: true // 第一次调用事件是否立即执行 43 | // trailing: true // 最后一次延迟调用是否执行 44 | // } 45 | function throttle (fn, wait, options) { 46 | // do something 47 | } 48 | ``` 49 | 50 | 51 | ## 参考 52 | 53 | [函数防抖(debounce)](http://underscorejs.org/#debounce) 54 | 55 | [函数节流(throttle)](http://underscorejs.org/#throttle) 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /modules/my-virtual-dom/utils.js: -------------------------------------------------------------------------------- 1 | const _ = exports 2 | 3 | _.setAttr = function setAttr (node, key, value) { 4 | switch (key) { 5 | case 'style': 6 | node.style.cssText = value 7 | break; 8 | case 'value': 9 | let tagName = node.tagName || '' 10 | tagName = tagName.toLowerCase() 11 | if ( 12 | tagName === 'input' || tagName === 'textarea' 13 | ) { 14 | node.value = value 15 | } else { 16 | // if is not a input or textarea, use `setAttribute` to set 17 | node.setAttribute(key, value) 18 | } 19 | break; 20 | default: 21 | node.setAttribute(key, value) 22 | break; 23 | } 24 | } 25 | 26 | _.slice = function slice (arrayLike, index) { 27 | return Array.prototype.slice.call(arrayLike, index) 28 | } 29 | 30 | _.truthy = function truthy (val) { 31 | return !!val 32 | } 33 | 34 | _.type = function type (obj) { 35 | return Object.prototype.toString.call(obj).replace(/\[object\s|\]/g, '') 36 | } 37 | 38 | _.isArray = function isArray (list) { 39 | return _.type(list) === 'Array' 40 | } 41 | 42 | _.toArray = function toArray (listLike) { 43 | if (!listLike) return [] 44 | 45 | let list = [] 46 | for (let i = 0, l = listLike.length; i < l; i++) { 47 | list.push(listLike[i]) 48 | } 49 | return list 50 | } 51 | 52 | _.isString = function isString (list) { 53 | return _.type(list) === 'String' 54 | } 55 | 56 | _.isElementNode = function isElementNode (node) { 57 | return node.nodeType === 1 58 | } 59 | -------------------------------------------------------------------------------- /modules/my-bind/bind.js: -------------------------------------------------------------------------------- 1 | // 硬绑定 2 | Function.prototype.myBind = function (oThis) { 3 | if (typeof this !== 'function') { 4 | // 与 ECMAScript 5 最接近的 5 | // 内部 IsCallable 函数 6 | throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); 7 | } 8 | 9 | var aArgs = Array.prototype.slice.call(arguments, 1) 10 | , fToBind = this 11 | , fNOOP = function () {} 12 | , fBound = function () { 13 | // 硬绑定函数是否被new 调用,如果有则使用新创建的this替换硬绑定的this 14 | return fToBind.apply( 15 | this instanceof fNOOP ? 16 | this : oThis, 17 | // 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的 18 | aArgs.concat(Array.prototype.slice.call(arguments))); 19 | } 20 | 21 | // 维护原型关系 22 | if (this.prototype) { 23 | // Function.prototype doesn't have a prototype property 24 | fNOOP.prototype = this.prototype 25 | } 26 | fBound.prototype = new fNOOP() 27 | 28 | return fBound; 29 | }; 30 | 31 | // 软绑定 32 | Function.prototype.softBind = function (oThis) { 33 | // 捕获所有的arguments参数 34 | var aArgs = Array.prototype.slice.call(arguments, 1) 35 | , fSoftBind = this 36 | , fBound = function () { 37 | // 如果this绑定到全局对象或者undefined上,那就把指定的默认对象绑定到this,否则不修改this 38 | return fSoftBind.apply( 39 | (!this || this === (window || global)) ? 40 | oThis : this, 41 | aArgs.concat.apply(aArgs, Array.prototype.slice.call(arguments)) 42 | ) 43 | } 44 | fBound.prototype = Object.create(fSoftBind.prototype) 45 | return fBound 46 | } -------------------------------------------------------------------------------- /src/my-mvvm/watcher/index.js: -------------------------------------------------------------------------------- 1 | import Dep from 'observer/dep'; 2 | 3 | class Watcher { 4 | constructor(vm, expOrFn, cb) { 5 | expOrFn = expOrFn.trim(); 6 | 7 | this.vm = vm; 8 | this.expOrFn = expOrFn; 9 | this.cb = cb; 10 | this.depIds = {}; 11 | 12 | if (typeof expOrFn === 'function') { 13 | this.getter = expOrFn 14 | } 15 | else { 16 | this.getter = this.parseGetter(expOrFn); 17 | } 18 | this.value = this.get(); 19 | } 20 | 21 | update () { 22 | this.run() 23 | } 24 | 25 | run () { 26 | let newVal = this.get(); 27 | let oldVal = this.value; 28 | if (newVal === oldVal) { 29 | return; 30 | } 31 | this.value = newVal; 32 | // 将newVal, oldVal挂载到MVVM实例上 33 | this.cb.call(this.vm, newVal, oldVal); 34 | } 35 | 36 | addDep (dep) { 37 | if (!this.depIds.hasOwnProperty(dep.id)) { 38 | dep.addSub(this); 39 | this.depIds[dep.id] = dep; 40 | } 41 | } 42 | 43 | get () { 44 | Dep.target = this; // 将当前订阅者指向自己 45 | let value = this.getter.call(this.vm, this.vm); // 触发getter,将自身添加到dep中 46 | Dep.target = null; // 添加完成 重置 47 | return value; 48 | } 49 | 50 | parseGetter (exp) { 51 | if (/[^\w.$]/.test(exp)) return; 52 | 53 | let exps = exp.split('.'); 54 | 55 | // 简易的循环依赖处理 56 | return function(obj) { 57 | for (let i = 0, len = exps.length; i < len; i++) { 58 | if (!obj) return; 59 | obj = obj[exps[i]]; 60 | } 61 | return obj; 62 | } 63 | } 64 | } 65 | 66 | export default Watcher; 67 | -------------------------------------------------------------------------------- /modules/my-virtual-dom/element.js: -------------------------------------------------------------------------------- 1 | import _ from './utils' 2 | 3 | /** 4 | * @class Element Virtrual Dom 5 | * @param { String } tagName 6 | * @param { Object } attrs Element's attrs, eg: { id: 'list' } 7 | * @param { Array } - This element's children elements. 8 | * - Can be Element instance or just a piece plain text. 9 | */ 10 | class Element { 11 | constructor(tagName, attrs, children) { 12 | if (!(this instanceof Element)) { 13 | if (!_.isArray(children) && children !== null) { 14 | children = _.slice(arguments, 2).filter(_.truthy) 15 | } 16 | } 17 | 18 | if (_.isArray(attrs)) { 19 | children = attrs 20 | attrs = {} 21 | } 22 | 23 | this.tagName = tagName 24 | this.attrs = attrs || {} 25 | this.children = children 26 | this.key = attrs 27 | ? attrs.key 28 | : void 0 29 | 30 | // let count = 0 31 | // this.children.forEach((child, i) => { 32 | // if (child instanceof Element) { 33 | // count += child.count 34 | // } else { 35 | // children[i] = '' + child 36 | // } 37 | // count++ 38 | // }) 39 | // this.count = count 40 | } 41 | 42 | render () { 43 | let el = document.createElement(this.tagName); 44 | let attrs = this.attrs; 45 | 46 | for (let attrName in attrs) { 47 | let attrValue = attrs[attrName]; 48 | _.setAttr(el, attrName, attrValue); 49 | } 50 | 51 | let children = this.children || [] 52 | 53 | children.forEach(child => { 54 | let childEl = child instanceof Element 55 | ? child.render() 56 | : document.createTextNode(child); 57 | el.appendChild(childEl); 58 | }) 59 | 60 | return el; 61 | } 62 | } 63 | 64 | module.exports = function (tagName, attrs, children) { 65 | return new Element(tagName, attrs, children) 66 | } 67 | -------------------------------------------------------------------------------- /src/my-mvvm/xue.js: -------------------------------------------------------------------------------- 1 | import _ from 'utils' 2 | import {Observer, observe, defineReactive$$1} from 'observer' 3 | import Compiler from 'compiler' 4 | 5 | class Xue { 6 | constructor (options) { 7 | this.$options = options || {}; 8 | let data = this._data = this.$options.data; 9 | let self = this; 10 | 11 | Object.keys(data).forEach(key => { 12 | self.proxy(key); 13 | }); 14 | observe(data, this); 15 | new Compiler(options.el || document.body, this); 16 | } 17 | 18 | proxy (key, setter, getter) { 19 | let self = this; 20 | setter = setter || 21 | Object.defineProperty(self, key, { 22 | configurable: false, 23 | enumerable: true, 24 | get: function proxyGetter() { 25 | return self._data[key]; 26 | }, 27 | set: function proxySetter(newVal) { 28 | self._data[key] = newVal; 29 | } 30 | }) 31 | } 32 | 33 | $set (target, key, val) { 34 | set (target, key, val) 35 | } 36 | 37 | $delete (target, key) { 38 | del (target, key) 39 | } 40 | } 41 | 42 | function set (target, key, val) { 43 | if (Array.isArray(target) && typeof key === 'number') { 44 | target.length = Math.max(target.length, key); 45 | target.splice(key, 1, val); 46 | return val; 47 | } 48 | if (_.hasOwn(target, key)) { 49 | target[key] = val; 50 | return val; 51 | } 52 | let ob = (target).__ob__; 53 | if (!ob) { 54 | target[key] = val; 55 | return val; 56 | } 57 | defineReactive$$1(ob.value, key, val); 58 | ob.dep.notify(); 59 | return val; 60 | } 61 | function del (target, key) { 62 | if (Array.isArray(target) && typeof key === 'number') { 63 | target.splice(key, 1); 64 | return; 65 | } 66 | let ob = (target).__ob__; 67 | if (!_.hasOwn(target, key)) { 68 | return; 69 | } 70 | delete target[key]; 71 | if (!ob) { 72 | return; 73 | } 74 | ob.dep.notify(); 75 | } 76 | 77 | module.exports = window.Xue = Xue; 78 | -------------------------------------------------------------------------------- /modules/my-debounceThrottle/debounceThrottle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * [debounce 函数防抖] 3 | * @param {Function} fn [需要进行函数防抖的函数] 4 | * @param {Number} wait [需要等待的时间] 5 | * @param {Boolean} immediate [调用时是否立即执行一次] 6 | */ 7 | function debounce (fn, wait, immediate) { 8 | let timeout = null 9 | 10 | return function () { 11 | if (immediate) { 12 | fn.apply(this, arguments) 13 | immediate = false 14 | } else { 15 | clearTimeout(timeout) 16 | timeout = setTimeout(() => { 17 | fn.apply(this, arguments) 18 | }, wait) 19 | } 20 | } 21 | 22 | } 23 | 24 | /** 25 | * [throttle 函数节流] 26 | * @param {Function} fn [需要进行函数节流的函数] 27 | * @param {Number} wait [函数执行的时间间隔] 28 | * @param {Object} options [执行参数] 29 | */ 30 | // options = { 31 | // leading: true // 第一次调用事件是否立即执行 32 | // trailing: true // 最后一次延迟调用是否执行 33 | // } 34 | function throttle (fn, wait, options) { 35 | let timeout, context, args, result 36 | let previous = 0 37 | if (!options) options = {} 38 | 39 | let later = function () { 40 | previous = options.leading === false ? 0 : +new Date() 41 | timeout = null 42 | result = fn.apply(context, args) 43 | if (!timeout) context = args = null 44 | } 45 | 46 | let throttled = function () { 47 | let now = +new Date() 48 | if (!previous && options.leading === false) previous = now 49 | let remaining = wait - (now - previous) 50 | context = this 51 | args = arguments 52 | if (remaining <= 0 || remaining > wait) { 53 | if (timeout) { 54 | clearTimeout(timeout) 55 | timeout = null 56 | } 57 | previous = now 58 | result = fn.apply(context, args) 59 | if (!timeout) context = args = null 60 | } else if (!timeout && options.trailing !== false) { 61 | timeout = setTimeout(later, remaining) 62 | } 63 | return result 64 | } 65 | 66 | return throttled 67 | } 68 | 69 | // 简易版本(两个参数) 70 | function _throttle (fn, wait) { 71 | let canRun = true 72 | return function () { 73 | if (!canRun) return 74 | canRun = false 75 | setTimeout(() => { 76 | fn.apply(this, arguments) 77 | canRun = true 78 | }, wait) 79 | } 80 | } 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "overwrite", 3 | "version": "1.0.0", 4 | "description": "overwrite some methods of javascript", 5 | "main": "overwrite.js", 6 | "scripts": { 7 | "test": "test", 8 | "dev": "webpack-dev-server --hot", 9 | "http": "cd src && http-server -p 9000", 10 | "build": "webpack" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/xuqiang521/overwrite.git" 15 | }, 16 | "keywords": [ 17 | "overwrite" 18 | ], 19 | "author": "qiangdada", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/xuqiang521/overwrite/issues" 23 | }, 24 | "homepage": "https://github.com/xuqiang521/overwrite#readme", 25 | "devDependencies": { 26 | "autoprefixer": "^6.7.2", 27 | "babel-cli": "^6.18.0", 28 | "babel-core": "^6.22.1", 29 | "babel-eslint": "^7.1.1", 30 | "babel-loader": "^6.2.8", 31 | "babel-plugin-transform-runtime": "^6.22.0", 32 | "babel-preset-es2015": "^6.18.0", 33 | "babel-preset-stage-2": "^6.22.0", 34 | "babel-register": "^6.22.0", 35 | "chalk": "^1.1.3", 36 | "child_process": "^1.0.2", 37 | "connect-history-api-fallback": "^1.3.0", 38 | "css-loader": "^0.26.1", 39 | "eslint": "^3.11.0", 40 | "eslint-friendly-formatter": "^2.0.7", 41 | "eslint-loader": "^1.6.1", 42 | "eslint-plugin-html": "^2.0.0", 43 | "eventsource-polyfill": "^0.9.6", 44 | "express": "^4.14.1", 45 | "extract-text-webpack-plugin": "^2.0.0", 46 | "file-loader": "^0.9.0", 47 | "file-save": "^0.2.0", 48 | "function-bind": "^1.1.0", 49 | "html-webpack-plugin": "^2.28.0", 50 | "http-proxy-middleware": "^0.17.3", 51 | "inquirer": "^3.1.0", 52 | "jshint": "^2.9.4", 53 | "json-loader": "^0.5.4", 54 | "less-loader": "^2.2.3", 55 | "open-browser-webpack-plugin": "0.0.5", 56 | "opn": "^4.0.2", 57 | "ora": "^0.3.0", 58 | "semver": "^5.3.0", 59 | "shelljs": "^0.7.4", 60 | "url-loader": "^0.5.7", 61 | "webpack": "^2.2.1", 62 | "webpack-dev-middleware": "^1.10.0", 63 | "webpack-dev-server": "^2.4.2", 64 | "webpack-hot-middleware": "^2.16.1", 65 | "webpack-merge": "^2.6.1" 66 | }, 67 | "engines": { 68 | "node": ">= 4.0.0", 69 | "npm": ">= 3.0.0" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/my-ajax/README.md: -------------------------------------------------------------------------------- 1 | # [ajax](https://github.com/xuqiang521/overwrite/tree/master/modules/my-ajax) 2 | 3 | >overwrite ajax() Ajax 4 | 5 | ## XHR用法 6 | 7 | 1. 使用XHR对象时,调用的第一个方法是open(),它接收三个参数 8 | - 要发送的请求的类型(get,post等) 9 | - 请求的url 10 | - 是否异步发送请求(Boolean类型) 11 | 12 | 2. send(),接收一个参数,即要作为请求主体发送的数据。如果不需要通过请求主体发送数据,则必须传入null 13 | 14 | 3. 收到响应,响应的数据自动填充XHR对象的属性,相关属性如下 15 | - responseText:作为响应主体被返回的文本 16 | - responseXML:如果响应内容类型是"text/xml"或"application/xml",这个属性中将保存包含着响应数据的XML DOM文档 17 | - status:响应的HTTP状态 18 | - statusText:HTPP状态的说明 19 | 20 | ## 检查状态 21 | 22 | ```javascript 23 | if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { 24 | alert(xhr.responseText); 25 | } 26 | else { 27 | alert('Request was unsuccessful: ' + xhr.status); 28 | } 29 | ``` 30 | 31 | ## XHR对象readyState属性 32 | 33 | - 0:未初始化。尚未调用open()方法。 34 | - 1:启动。已经调用open()方法,但尚未调用send()方法。 35 | - 2:发送。已经调用send()方法,但尚未接收到响应 36 | - 3:接收。已经接收到部分响应数据 37 | - 4:完成。已经接收到全部响应数据,而且已经在客户端使用 38 | 39 | ```javascript 40 | var xhr = createXHR(); 41 | xhr.onreadystatechange = function () { 42 | if (xhr.readyState === 4) { 43 | if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { 44 | alert(xhr.responseText); 45 | } 46 | else { 47 | alert('Request was unsuccessful: ' + xhr.status); 48 | } 49 | } 50 | } 51 | xhr.open('get', 'example.txt', true); 52 | xhr.send(null); 53 | ``` 54 | xhr.abort() => 取消异步请求,调用后,XHR对象会停止触发事件 55 | 56 | ## HTTP头部信息 57 | 58 | 每个HTTP请求和响应都会带有相应的头部信息,XHR对象也提供了操作这两种头部(即请求头部和响应头部)信息的方法 59 | 60 | 1. Accept:浏览器能够处理的类型。 61 | 2. Accept-Charset:浏览器能够显示的字符集。 62 | 3. Accept-Encoding:浏览器能够处理的压缩编码。 63 | 4. Accept-Language:浏览器当前设置的语言。 64 | 5. Connection:浏览器与服务器之间连接的类型。 65 | 6. Cookie:当前页面设置的任何Cookie。 66 | 7. Host:发出请求的页面所在的域。 67 | 8. Referer:发出请求的页面URI。 68 | 9. User-Agent:浏览器的用户代理字符串 69 | 70 | 使用setRequestHeader()方法可以设置自定义的请求头部消息。它接收两参数:头部字段的名称和头部字段的值。要成功发送请求头部信息,必须在open()方法之后send()方法之前 71 | 72 | ```javascript 73 | var xhr = createXHR(); 74 | xhr.onreadystatechange = function () { 75 | if (xhr.readyState === 4) { 76 | if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { 77 | alert(xhr.responseText); 78 | } 79 | else { 80 | alert('Request was unsuccessful: ' + xhr.status); 81 | } 82 | } 83 | } 84 | xhr.open('get', 'example.txt', true); 85 | 86 | xhr.setRequestHeader('MyHeader', 'MyValue') 87 | 88 | xhr.send(null); 89 | ``` -------------------------------------------------------------------------------- /src/my-mvvm/compiler/index.js: -------------------------------------------------------------------------------- 1 | import Watcher from 'watcher' 2 | import { CompilerUtils } from 'utils/dom' 3 | 4 | class Compiler { 5 | constructor(el, vm) { 6 | this.$vm = vm; 7 | this.$el = this.isElementNode(el) ? el : document.querySelector(el); 8 | 9 | if (this.$el) { 10 | this.$fragment = this.nodeFragment(this.$el); 11 | this.compileElement(this.$fragment); 12 | this.$el.appendChild(this.$fragment); 13 | } 14 | } 15 | 16 | nodeFragment (el) { 17 | let fragment = document.createDocumentFragment(); 18 | let child; 19 | 20 | while (child = el.firstChild) { 21 | fragment.appendChild(child); 22 | } 23 | return fragment; 24 | } 25 | 26 | compileElement (el) { 27 | let self = this; 28 | let childNodes = el.childNodes; 29 | 30 | [].slice.call(childNodes).forEach(node => { 31 | let text = node.textContent; 32 | let reg = /\{\{((?:.|\n)+?)\}\}/; 33 | 34 | // 如果是element节点 35 | if (self.isElementNode(node)) { 36 | self.compile(node); 37 | } 38 | // 如果是text节点 39 | else if (self.isTextNode(node) && reg.test(text)) { 40 | self.compileText(node, RegExp.$1) 41 | } 42 | // 解析子节点包含的指令 43 | if (node.childNodes && node.childNodes.length) { 44 | self.compileElement(node); 45 | } 46 | }); 47 | } 48 | 49 | compile (node) { 50 | let self = this; 51 | let nodeAttrs = node.attributes; 52 | 53 | [].slice.call(nodeAttrs).forEach(attr => { 54 | let attrName = attr.name; 55 | let dir = attrName.substring(2); 56 | 57 | if (self.isDirective(attrName)) { 58 | let exp = attr.value; 59 | 60 | if (self.isEventDirective(dir)) { 61 | CompilerUtils.eventHandler(node, self.$vm, exp, dir); 62 | } 63 | else { 64 | CompilerUtils[dir] && CompilerUtils[dir](node, self.$vm, exp); 65 | } 66 | 67 | node.removeAttribute(attrName); 68 | } 69 | }) 70 | } 71 | 72 | compileText (node, exp) { 73 | CompilerUtils.text(node, this.$vm, exp); 74 | } 75 | 76 | isElementNode (node) { 77 | return node.nodeType === 1 78 | } 79 | 80 | isTextNode (node) { 81 | return node.nodeType === 3 82 | } 83 | 84 | isDirective (attr) { 85 | return attr.indexOf('x-') === 0 86 | } 87 | 88 | isEventDirective (dir) { 89 | return dir.indexOf('on') === 0 90 | } 91 | } 92 | 93 | export default Compiler; 94 | -------------------------------------------------------------------------------- /src/my-observer/observer.js: -------------------------------------------------------------------------------- 1 | // write toArray() 2 | function toArray (list, start) { 3 | start = start || 0; 4 | var i = list.length - start; 5 | var ret = new Array(i); 6 | while (i--) { 7 | ret[i] = list[i + start]; 8 | } 9 | return ret; 10 | } 11 | 12 | // 定义观察者 13 | var Observer = function () { 14 | this._events = Object.create(null); 15 | } 16 | /** 17 | * [$on 发布消息] 18 | * @param {[type]} event [事件别名] 19 | * @param {Function} fn [事件回调] 20 | */ 21 | Observer.prototype.$on = function (event, fn) { 22 | var this$1 = this; 23 | 24 | var self = this; 25 | if (Array.isArray(event)) { 26 | for (var i = 0, l = event.length; i < l; i++) { 27 | this$1.$on(event[i], fn); 28 | } 29 | } 30 | else { 31 | (self._events[event] || (self._events[event] = [])).push(fn); 32 | } 33 | return self; 34 | } 35 | /** 36 | * [$once 仅发布一次消息] 37 | * @param {[type]} event [事件别名] 38 | * @param {Function} fn [事件回调] 39 | */ 40 | Observer.prototype.$once = function (event, fn) { 41 | var self = this; 42 | function on() { 43 | self.$off(event, on); 44 | fn.apply(self, arguments); 45 | } 46 | // on.fn = fn; 47 | self.$on(event, on); 48 | return self; 49 | } 50 | /** 51 | * [$off 取消发布] 52 | * @param {[type]} event [事件别名] 53 | * @param {Function} fn [事件回调] 54 | */ 55 | Observer.prototype.$off = function (event, fn) { 56 | var this$1 = this; 57 | 58 | var self = this; 59 | // clear all 60 | if (!arguments.length) { 61 | this._events = Object.create(null); 62 | return self; 63 | } 64 | // clear array of events 65 | if (Array.isArray(event)) { 66 | for (var i$1 = 0, l = event.length; i$1 < l; i$1++) { 67 | this$1.$off(event[i$1], fn); 68 | } 69 | return self; 70 | } 71 | // special event 72 | var cbs = self._events[event]; 73 | if (!cbs) { 74 | return self; 75 | } 76 | if (arguments.length === 1) { 77 | this._events[event] = null; 78 | return self; 79 | } 80 | // special handler 81 | var cb; 82 | var i = cbs.length; 83 | while (i--) { 84 | cb = cbs[i]; 85 | if (cb === fn || cb.fn === fn) { 86 | cbs.splice(i, 1); 87 | break; 88 | } 89 | } 90 | return self; 91 | } 92 | 93 | /** 94 | * [$emit 订阅触发] 95 | * @param {[type]} event [事件别名] 96 | */ 97 | Observer.prototype.$emit = function (event) { 98 | var self = this; 99 | var cbs = this._events[event]; 100 | 101 | if (cbs) { 102 | cbs = cbs.length > 1 ? toArray(cbs) : cbs; 103 | var args = toArray(arguments, 1); 104 | for (var i = 0, l = cbs.length; i < l; i++) { 105 | cbs[i].apply(self, args); 106 | } 107 | } 108 | return self; 109 | }; 110 | -------------------------------------------------------------------------------- /src/my-mvvm/utils/dom.js: -------------------------------------------------------------------------------- 1 | import Watcher from 'watcher' 2 | 3 | // 缓存当前执行input事件的input dom对象 4 | let $elm; 5 | let timer = null; 6 | // 指令处理集合 7 | const CompilerUtils = { 8 | html: function (node, vm ,exp) { 9 | this.bind(node, vm, exp, 'html') 10 | }, 11 | 12 | text: function (node, vm, exp) { 13 | this.bind(node, vm, exp, 'text') 14 | }, 15 | 16 | model: function (node, vm, exp) { 17 | this.bind(node, vm, exp, 'model'); 18 | // model 操作 19 | let self = this; 20 | let val = this._getVmVal(vm, exp); 21 | // 监听input事件 22 | node.addEventListener('input', function (e) { 23 | let newVal = e.target.value; 24 | $elm = e.target 25 | 26 | if (val === newVal) return; 27 | 28 | clearTimeout(timer); 29 | timer = setTimeout(function () { 30 | self._setVmVal(vm, exp, newVal); 31 | val = newVal; 32 | }) 33 | }) 34 | }, 35 | 36 | bind: function (node, vm, exp, dir) { 37 | let updaterFn = updater[dir + 'Updater']; 38 | let val = this._getVmVal(vm, exp); 39 | 40 | updaterFn && updaterFn(node, val); 41 | 42 | new Watcher(vm, exp, function(value, oldValue) { 43 | updaterFn && updaterFn(node, value, oldValue); 44 | }); 45 | }, 46 | 47 | eventHandler: function (node, vm, exp, dir) { 48 | let eventType = dir.split(':')[1]; 49 | let fn = vm.$options.methods && vm.$options.methods[exp]; 50 | 51 | if (eventType && fn) { 52 | // fn.bind(vm) 将作用域指向vm 53 | node.addEventListener(eventType, fn.bind(vm), false); 54 | } 55 | }, 56 | 57 | _getVmVal: function (vm, exp) { 58 | let val = vm; 59 | let exps = exp.split('.'); 60 | exps.forEach(key => { 61 | key = key.trim(); 62 | val = val[key] 63 | }) 64 | 65 | return val; 66 | }, 67 | 68 | _setVmVal: function (vm, exp, newVal) { 69 | let val = vm; 70 | let exps = exp.split('.'); 71 | 72 | exps.forEach((key, index) => { 73 | key = key.trim(); 74 | if (index < exps.length - 1) { 75 | val = val[key]; 76 | } 77 | else { 78 | val[key] = newVal; 79 | } 80 | }) 81 | } 82 | } 83 | 84 | // 指令渲染集合 85 | const updater = { 86 | htmlUpdater: function (node, value) { 87 | node.innerHTML = typeof value === 'undefined' ? '' : value; 88 | }, 89 | textUpdater: function (node, value) { 90 | node.textContent = typeof value === 'undefined' ? '' : value; 91 | }, 92 | modelUpdater: function (node, value, oldValue) { 93 | if ($elm === node) { 94 | return false; 95 | } 96 | $elm = undefined; 97 | node.value = typeof value === 'undefined' ? '' : value; 98 | } 99 | } 100 | 101 | export { CompilerUtils, updater }; 102 | -------------------------------------------------------------------------------- /modules/my-virtual-dom/diff.js: -------------------------------------------------------------------------------- 1 | import _ from './utils' 2 | import patch from './patch.js' 3 | import listDiff from './list-diff'; 4 | 5 | function diff (oldTree, newTree) { 6 | let index = 0; 7 | let patches = {} // 用来记录每个节点差异的对象 8 | walk(oldTree, newTree, index, patches) 9 | return patches; 10 | } 11 | 12 | function walk (oldNode, newNode, index, patches) { 13 | let currentPatch = [] 14 | 15 | if(newNode === null) { 16 | 17 | } 18 | else if (_.isString(oldNode) && _.isString(newNode)) { 19 | if (newNode !== oldNode) { 20 | currentPatch.push({ type: patch.TEXT, content: newNode }) 21 | } 22 | } 23 | else if ( 24 | oldNode.tagName === newNode.tagName && 25 | oldNode.key === newNode.key 26 | ) { 27 | // diff attrs 28 | let attrsPatches = diffAttrs(oldNode, newNode) 29 | if (attrsPatches) { 30 | currentPatch.push({ type: patch.ATTRS, attrs: attrsPatches }) 31 | } 32 | diffChildren(oldNode.children, newNode.children, index, patches, currentPatch); 33 | } 34 | else { 35 | currentPatch.push({ type: patch.REPLACE, node: newNode }) 36 | } 37 | 38 | if (currentPatch.length) { 39 | patches[index] = currentPatch 40 | } 41 | } 42 | let key_id = 0 43 | function diffChildren (oldChildren, newChildren, index, patches, currentPatch) { 44 | let diffs = listDiff(oldChildren, newChildren, 'key') 45 | newChildren = diffs.children 46 | 47 | if (diffs.moves.length) { 48 | var reorderPatch = { type: patch.REORDER, moves: diffs.moves } 49 | currentPatch.push(reorderPatch) 50 | } 51 | 52 | let leftNode = null; 53 | let currentNodeIndex = index; 54 | oldChildren.forEach( (child, i) => { 55 | key_id++ 56 | currentNodeIndex = key_id 57 | let newChild = newChildren[i]; 58 | // currentNodeIndex = (leftNode && leftNode.count) 59 | // ? currentNodeIndex + leftNode.count + 1 60 | // : currentNodeIndex + 1 61 | 62 | walk(child, newChild, currentNodeIndex, patches) 63 | // leftNode = child 64 | 65 | }) 66 | } 67 | 68 | function diffAttrs (oldNode, newNode) { 69 | let count = 0 70 | let oldAttrs = oldNode.attrs 71 | let newAttrs = newNode.attrs 72 | 73 | let key, value 74 | let attrsPatches = {} 75 | 76 | // find out different attrs 77 | for (key in oldAttrs) { 78 | value = oldAttrs[key] 79 | if (newAttrs[key] !== value) { 80 | count++ 81 | attrsPatches[key] = newAttrs[key] 82 | } 83 | } 84 | // find out new attr 85 | for (key in newAttrs) { 86 | value = newAttrs[key] 87 | if (!oldAttrs.hasOwnProperty(key)) { 88 | count++ 89 | attrsPatches[key] = newAttrs[key] 90 | } 91 | } 92 | 93 | if (count === 0) { 94 | return null 95 | } 96 | return attrsPatches 97 | } 98 | 99 | module.exports = diff 100 | -------------------------------------------------------------------------------- /modules/my-virtual-dom/patch.js: -------------------------------------------------------------------------------- 1 | import _ from './utils' 2 | 3 | const REPLACE = 0 4 | const ATTRS = 1 5 | const TEXT = 2 6 | const REORDER = 3 7 | 8 | function patch (node, patches) { 9 | let walker = {index: 0} 10 | walk(node, walker, patches) 11 | } 12 | 13 | function walk (node, walker, patches) { 14 | let currentPatches = patches[walker.index] 15 | 16 | let len = node.childNodes 17 | ? node.childNodes.length 18 | : 0 19 | for (let i = 0; i < len; i++) { 20 | let child = node.childNodes[i] 21 | walker.index++ 22 | walk(child, walker, patches) 23 | } 24 | 25 | if (currentPatches) { 26 | applyPatches(node, currentPatches) 27 | } 28 | } 29 | 30 | function applyPatches (node, currentPatches) { 31 | currentPatches.forEach(currentPatch => { 32 | switch (currentPatch.type) { 33 | case REPLACE: 34 | let newNode = (typeof currentPatch.node === 'string') 35 | ? document.createTextNode(currentPatch.node) 36 | : currentPatch.node.render() 37 | node.parentNode.replaceChild(newNode, node) 38 | break 39 | case REORDER: 40 | reorderChildren(node, currentPatch.moves) 41 | break 42 | case ATTRS: 43 | setAttrs(node, currentPatch.attrs) 44 | break 45 | case TEXT: 46 | if (node.textContent) { 47 | node.textContent = currentPatch.content 48 | } else { 49 | // for ie 50 | node.nodeValue = currentPatch.content 51 | } 52 | break 53 | default: 54 | throw new Error('Unknown patch type ' + currentPatch.type) 55 | } 56 | }) 57 | } 58 | 59 | function setAttrs (node, attrs) { 60 | for (let key in attrs) { 61 | if (attrs[key] === void 666) { 62 | node.removeAttribute(key) 63 | } else { 64 | let value = attrs[key] 65 | _.setAttr(node, key, value) 66 | } 67 | } 68 | } 69 | 70 | function reorderChildren (node, moves) { 71 | let staticNodeList = _.toArray(node.childNodes) 72 | let maps = {} 73 | 74 | staticNodeList.forEach(node => { 75 | if (_.isElementNode(node)) { 76 | let key = node.getAttribute('key') 77 | if (key) { 78 | maps[key] = node 79 | } 80 | } 81 | }) 82 | 83 | moves.forEach(move => { 84 | let index = move.index 85 | if (move.type === 0) { // remove item 86 | if (staticNodeList[index] === node.childNodes[index]) { // maybe have been removed for inserting 87 | node.removeChild(node.childNodes[index]) 88 | } 89 | staticNodeList.splice(index, 1) 90 | } else if (move.type === 1) { // insert item 91 | let insertNode = maps[move.item.key] 92 | ? maps[move.item.key] // reuse old item 93 | : (typeof move.item === 'object') 94 | ? move.item.render() 95 | : document.createTextNode(move.item) 96 | staticNodeList.splice(index, 0, insertNode) 97 | node.insertBefore(insertNode, node.childNodes[index] || null) 98 | } 99 | }) 100 | } 101 | 102 | patch.REPLACE = REPLACE 103 | patch.ATTRS = ATTRS 104 | patch.TEXT = TEXT 105 | patch.REORDER = REORDER 106 | 107 | module.exports = patch 108 | -------------------------------------------------------------------------------- /src/my-mvvm/observer/index.js: -------------------------------------------------------------------------------- 1 | import _ from 'utils' 2 | import Dep from './dep' 3 | import Compiler from 'compiler' 4 | 5 | 6 | // observe array 7 | let arrayProto = Array.prototype; 8 | let arrayMethods = Object.create(arrayProto); 9 | [ 10 | 'push', 11 | 'pop', 12 | 'shift', 13 | 'unshift', 14 | 'splice', 15 | 'sort', 16 | 'reverse' 17 | ].forEach(method => { 18 | // 原始数组操作方法 19 | let original = arrayMethods[method]; 20 | _.def(arrayMethods, method, function () { 21 | let arguments$1 = arguments; 22 | let i = arguments.length; 23 | let args = new Array(i); 24 | 25 | while (i--) { 26 | args[i] = arguments$1[i] 27 | } 28 | // 执行数组方法 29 | let result = original.apply(this, args); 30 | // 因 arrayMethods 是为了作为 Observer 中的 value 的原型或者直接作为属性,所以此处的 this 一般就是指向 Observer 中的 value 31 | // 当然,还需要修改 Observer,使得其中的 value 有一个指向 Observer 自身的属性,__ob__,以此将两者关联起来 32 | let ob = this.__ob__; 33 | // 存放新增数组元素 34 | let inserted; 35 | // 为add 进arry中的元素进行observe 36 | switch (method) { 37 | case 'push': 38 | inserted = args; 39 | break; 40 | case 'unshift': 41 | inserted = args; 42 | break; 43 | case 'splice': 44 | // 第三个参数开始才是新增元素 45 | inserted = args.slice(2); 46 | break; 47 | } 48 | if (inserted) { 49 | ob.observeArray(inserted); 50 | } 51 | // 通知数组变化 52 | ob.dep.notify(); 53 | // 返回新数组长度 54 | return result; 55 | }) 56 | }) 57 | // arrayMethods所有的枚举属性名 58 | const arrayKeys = Object.getOwnPropertyNames(arrayMethods); 59 | const hasProto = '__proto__' in {}; 60 | 61 | class Observer { 62 | constructor(value) { 63 | this.value = value; 64 | this.dep = new Dep(); 65 | _.def(value, '__ob__', this); 66 | if (Array.isArray(value)) { 67 | let augment = hasProto ? _.protoAugment : _.copyAugment; 68 | augment(value, arrayMethods, arrayKeys); 69 | this.observeArray(value); 70 | } else { 71 | this.walk(value); 72 | } 73 | } 74 | 75 | walk(obj) { 76 | Object.keys(obj).forEach(key => { 77 | defineReactive$$1(obj, key, obj[key]); 78 | }); 79 | } 80 | 81 | observeArray(items) { 82 | for (let i = 0, l = items.length; i < l; i++) { 83 | observe(items[i]); 84 | } 85 | } 86 | } 87 | 88 | function defineReactive$$1 (obj, key, val) { 89 | let dep = new Dep(); 90 | let childOb = observe(val); 91 | Object.defineProperty(obj, key, { 92 | enumerable: true, 93 | configurable: true, 94 | get: function() { 95 | if (Dep.target) { 96 | dep.depend(); 97 | if (childOb) { 98 | childOb.dep.depend(); 99 | } 100 | } 101 | 102 | return val; 103 | }, 104 | set: function(newVal) { 105 | if (val === newVal || (newVal !== newVal && val !== val)) { 106 | return; 107 | } 108 | val = newVal; 109 | // 监听子属性 110 | childOb = observe(newVal); 111 | // 通知数据变更 112 | dep.notify(); 113 | } 114 | }) 115 | } 116 | 117 | function observe(value, asRootData) { 118 | if (!value || typeof value !== 'object') { 119 | return; 120 | } 121 | let ob; 122 | if (_.hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { 123 | ob = value.__ob__; 124 | } else { 125 | ob = new Observer(value); 126 | } 127 | return ob 128 | } 129 | 130 | export { 131 | Observer, 132 | defineReactive$$1, 133 | observe 134 | }; 135 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 写在开篇的话 2 | 3 | 此项目,将长期更新,会陆续讲解一些常用方法的`overwrite`,欢迎各位`star`。由于项目本身没有很复杂的结构,所以这边本地服务目前只用了`http-server`和基础的`webpack-dev-server`服务,后期会不断进行完善。 4 | 5 | ## overwrite 进展 6 | 7 | - [x] [数组去重](https://github.com/xuqiang521/overwrite/tree/master/src/my-unique) 8 | - [x] [深复制](https://github.com/xuqiang521/overwrite/tree/master/src/my-clone) 9 | - [x] [观察者模式](https://github.com/xuqiang521/overwrite/tree/master/src/my-observer) 10 | - [x] [Promise](https://github.com/xuqiang521/overwrite/tree/master/src/my-promise) 11 | - [x] [MVVM](https://github.com/xuqiang521/overwrite/tree/master/src/my-mvvm) 12 | - [x] [Ajax](https://github.com/xuqiang521/overwrite/tree/master/src/my-ajax) 13 | - [x] [Object.assign](https://github.com/xuqiang521/overwrite/tree/master/src/my-assign) 14 | - [x] [bind](https://github.com/xuqiang521/overwrite/tree/master/src/my-bind) 15 | - [x] [drag](https://github.com/xuqiang521/overwrite/tree/master/src/my-drag) 16 | - [x] [setTimeout](https://github.com/xuqiang521/overwrite/tree/master/src/my-setTimeout) 17 | - [x] [函数节流与函数防抖](https://github.com/xuqiang521/overwrite/tree/master/src/my-debounceThrottle) 18 | - [x] [Virtual Dom && diff](https://github.com/xuqiang521/overwrite/tree/master/src/my-virtual-dom) 19 | - [x] [copy 复制粘贴](https://github.com/xuqiang521/overwrite/tree/master/src/my-copy) 20 | 21 | 22 | ## blog 23 | 24 | - [x] [从指向看JavaScript](https://zhuanlan.zhihu.com/p/28058983) 25 | - [x] [vue移动端开发踩过的一些坑](https://zhuanlan.zhihu.com/p/30419351) 26 | - [x] [造一个属于自己的 UI 库](https://zhuanlan.zhihu.com/p/32030232) 27 | - [x] [揭秘组件库一二事](https://xuqiang521.github.io/2018/03/揭秘组件库一二事/) 28 | - [x] [初探 Nuxt.js 秘密花园](https://xuqiang521.github.io/2018/2018/04/初探-Nuxt.js-秘密花园/) 29 | - [x] [TypeScript + 大型项目实战](https://zhuanlan.zhihu.com/p/40322215) 30 | - [x] [细谈 vue 核心 - vdom 篇](https://zhuanlan.zhihu.com/p/61766666) 31 | - [x] [细谈 vue - slot 篇](https://zhuanlan.zhihu.com/p/64750738) 32 | - [x] [细谈 vue - transition 篇](https://zhuanlan.zhihu.com/p/67845420) 33 | - [x] [细谈 vue - transition-group 篇](https://zhuanlan.zhihu.com/p/68184865) 34 | - [x] [细谈 vue - 抽象组件实战篇](https://zhuanlan.zhihu.com/p/68416037) 35 | - [x] [5分钟谈前端面试](https://juejin.im/post/5d04fc1c51882559ef78e88f) 36 | - [x] [细谈 vue - component 篇](https://juejin.im/post/5d2d992af265da1bcd380b10) 37 | - [x] [「2019 JSConf.Asia - 尤雨溪」在框架设计中寻求平衡](https://juejin.im/post/5d45be46f265da03cf7a70d7) 38 | - [x] [「2019 JSConf.Asia - Kas Perch」WebAssembly - JS 的未来和 Web 多语言开发](https://juejin.im/post/5d4b17b0f265da03c926e436) 39 | - [x] [AST 与前端工程化实战](https://juejin.im/post/5d50d1d9f265da03aa25607b) 40 | - [x] [「2019 JSConf.Hawaii - Brie.Bunge」大规模应用 TypeScript](https://juejin.im/post/5d591d8a6fb9a06aee362f29) 41 | - [x] [探秘 Vue3.0 - Composition API 在真实业务中的尝鲜姿势](https://juejin.im/post/5d6e4986518825267a756a8d) 42 | 43 | ## 微信公众号 44 | 45 | 「合格前端」定期推送一些精选博文,内容包括但不仅限于前端,不定期进行技术直播分享。关注我,获取干货不迷路。 46 | 47 | ![](https://user-gold-cdn.xitu.io/2019/9/3/16cf6d598c492c4c?w=2800&h=800&f=png&s=431315) 48 | 49 | ## 社区 50 | 51 | [知乎](https://www.zhihu.com/people/qiangdada520/activities) 52 | 53 | [掘金](https://juejin.im/user/582e663467f3560063395f4c) 54 | 55 | [开源中国](https://my.oschina.net/qiangdada) 56 | 57 | [个人博客](https://xuqiang521.github.io/) 58 | 59 | 60 | ## 其他开源项目 61 | 62 | [Brickies/vui: A personal Vue UI component library](https://github.com/Brickies/vui) 63 | 64 | [Brickies/vue-template: 一个自定义的 vue-cli 模板](https://github.com/Brickies/vue-template) 65 | 66 | [nuxt-ssr-demo: 一个基于 Nuxt 的服务器端渲染 Demo](https://github.com/xuqiang521/nuxt-ssr-demo) 67 | 68 | [xuejs: A simple MVVM Library](https://github.com/xuqiang521/xuejs) 69 | 70 | [promise-catch-loader: 一个自动为 promise 注入通用 catch 代码的 webpack loader](https://github.com/xuqiang521/promise-catch-loader) 71 | 72 | -------------------------------------------------------------------------------- /modules/my-virtual-dom/list-diff.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Diff two list in O(N). 3 | * @param {Array} oldList - Original List 4 | * @param {Array} newList - List After certain insertions, removes, or moves 5 | * @return {Object} - {moves: } 6 | * - moves is a list of actions that telling how to remove and insert 7 | */ 8 | function listDiff (oldList, newList, key) { 9 | let oldMap = getKeyIndexAndFree(oldList, key) 10 | let newMap = getKeyIndexAndFree(newList, key) 11 | 12 | let newFree = newMap.free 13 | 14 | let oldKeyIndex = oldMap.keyIndex 15 | let newKeyIndex = newMap.keyIndex 16 | 17 | let moves = [] 18 | 19 | // a simulate list to manipulate 20 | let children = [] 21 | let i = 0 22 | let item 23 | let itemKey 24 | let freeIndex = 0 25 | 26 | // fist pass to check item in old list: if it's removed or not 27 | while (i < oldList.length) { 28 | item = oldList[i] 29 | itemKey = getItemKey(item, key) 30 | if (itemKey) { 31 | if (!newKeyIndex.hasOwnProperty(itemKey)) { 32 | children.push(null) 33 | } else { 34 | let newItemIndex = newKeyIndex[itemKey] 35 | children.push(newList[newItemIndex]) 36 | } 37 | } else { 38 | let freeItem = newFree[freeIndex++] 39 | children.push(freeItem || null) 40 | } 41 | i++ 42 | } 43 | 44 | let simulateList = children.slice(0) 45 | 46 | // remove items no longer exist 47 | i = 0 48 | while (i < simulateList.length) { 49 | if (simulateList[i] === null) { 50 | remove(i) 51 | removeSimulate(i) 52 | } else { 53 | i++ 54 | } 55 | } 56 | 57 | // i is cursor pointing to a item in new list 58 | // j is cursor pointing to a item in simulateList 59 | let j = i = 0 60 | while (i < newList.length) { 61 | item = newList[i] 62 | itemKey = getItemKey(item, key) 63 | 64 | let simulateItem = simulateList[j] 65 | let simulateItemKey = getItemKey(simulateItem, key) 66 | 67 | if (simulateItem) { 68 | if (itemKey === simulateItemKey) { 69 | j++ 70 | } else { 71 | // if remove current simulateItem make item in right place 72 | // then just remove it 73 | let nextItemKey = getItemKey(simulateList[j + 1], key) 74 | if (nextItemKey === itemKey) { 75 | remove(i) 76 | removeSimulate(j) 77 | j++ // after removing, current j is right, just jump to next one 78 | } else { 79 | // else insert item 80 | insert(i, item) 81 | } 82 | } 83 | // new item, just inesrt it 84 | } else { 85 | insert(i, item) 86 | } 87 | 88 | i++ 89 | } 90 | 91 | //if j is not remove to the end, remove all the rest item 92 | let k = 0; 93 | while (j++'" 122 | }], 123 | "max-params": [1, 6] 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /modules/my-ajax/ajax.js: -------------------------------------------------------------------------------- 1 | // import createXHR from './xhr' 2 | 3 | function createXHR() { 4 | if (typeof XMLHttpRequest !== 'undefined') { 5 | return new XMLHttpRequest() 6 | } else if (typeof ActiveXObject !== 'undefined') { 7 | if (typeof arguments.callee.activeXString !== 'undefined') { 8 | var versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp'] 9 | var i, len 10 | for (i = 0, len = versions.length; i < len; i++) { 11 | try { 12 | new ActiveXObject(versions[i]) 13 | arguments.callee.activeXString = versions[i] 14 | break 15 | } catch (error) { 16 | 17 | } 18 | } 19 | } 20 | return new ActiveXObject(arguments.callee.activeXString) 21 | } else { 22 | throw new Error('No XHR Object available.') 23 | } 24 | } 25 | 26 | function ajax(options) { 27 | var methods = ['get', 'post', 'put', 'delete'] 28 | var options = options || {} 29 | options.baseUrl = options.baseUrl || '' 30 | if (options.method && options.url) { 31 | return xhrConnection( 32 | options.method, 33 | options.baseUrl + options.url, 34 | maybeData(options.data), 35 | options 36 | ) 37 | } 38 | return methods.reduce(function (acc, method) { 39 | acc[method] = function (url, data) { 40 | return xhrConnection( 41 | method, 42 | options.baseUrl + url, 43 | maybeData(data), 44 | options 45 | ) 46 | } 47 | return acc 48 | }, {}) 49 | } 50 | 51 | function maybeData(data) { 52 | return data || null 53 | } 54 | 55 | function xhrConnection(type, url, data, options) { 56 | var returnMethods = ['then', 'catch', 'always'] 57 | var promiseMethods = returnMethods.reduce(function (promise, method) { 58 | promise[method] = function (callback) { 59 | promise[method] = callback 60 | return promise 61 | } 62 | return promise 63 | }, {}) 64 | var xhr = new createXHR() 65 | var featuredUrl = getUrlWithData(url, data, type) 66 | xhr.open(type, featuredUrl, true) 67 | xhr.withCredentials = options.hasOwnProperty('withCredentials') 68 | setHeaders(xhr, options.headers) 69 | xhr.addEventListener('readystatechange', ready(promiseMethods, xhr), false) 70 | xhr.send(objectToQueryString(data)) 71 | promiseMethods.abort = function () { 72 | return xhr.abort() 73 | } 74 | return promiseMethods 75 | } 76 | 77 | function getUrlWithData(url, data, type) { 78 | if (type.toLowerCase() !== 'get' || !data) { 79 | return url 80 | } 81 | var dataAsQueryString = objectToQueryString(data) 82 | var queryStringSeparator = url.indexOf('?') > -1 ? '&' : '?' 83 | return url + queryStringSeparator + dataAsQueryString 84 | } 85 | 86 | function setHeaders(xhr, headers) { 87 | headers = headers || {} 88 | if (!hasContentType(headers)) { 89 | headers['Content-Type'] = 'application/x-www-form-urlencoded' 90 | } 91 | Object.keys(headers).forEach(function (name) { 92 | (headers[name] && xhr.setRequestHeader(name, headers[name])) 93 | }) 94 | } 95 | 96 | function hasContentType(headers) { 97 | return Object.keys(headers).some(function (name) { 98 | return name.toLowerCase() === 'content-type' 99 | }) 100 | } 101 | 102 | function ready(promiseMethods, xhr) { 103 | return function handleReady() { 104 | if (xhr.readyState === xhr.DONE) { 105 | xhr.removeEventListener('readystatechange', handleReady, false) 106 | promiseMethods.always.apply(promiseMethods, parseResponse(xhr)) 107 | 108 | if (xhr.status >= 200 && xhr.status < 300) { 109 | promiseMethods.then.apply(promiseMethods, parseResponse(xhr)) 110 | } else { 111 | promiseMethods.catch.apply(promiseMethods, parseResponse(xhr)) 112 | } 113 | } 114 | } 115 | } 116 | 117 | function parseResponse(xhr) { 118 | var result 119 | try { 120 | result = JSON.parse(xhr.responseText) 121 | } catch (e) { 122 | result = xhr.responseText 123 | } 124 | return [result, xhr] 125 | } 126 | 127 | function objectToQueryString(data) { 128 | return isObject(data) ? getQueryString(data) : data 129 | } 130 | 131 | function isObject(data) { 132 | return Object.prototype.toString.call(data) === '[object Object]' 133 | } 134 | 135 | function getQueryString(object) { 136 | return Object.keys(object).reduce(function (acc, item) { 137 | var prefix = !acc ? '' : acc + '&' 138 | return prefix + encode(item) + '=' + encode(object[item]) 139 | }, '') 140 | } 141 | 142 | function encode(value) { 143 | return encodeURIComponent(value) 144 | } 145 | -------------------------------------------------------------------------------- /src/my-promise/README.md: -------------------------------------------------------------------------------- 1 | # Promise 2 | 3 | >overwrite `Promise()` 4 | 5 | 6 | ## 思路 7 | 8 | Promise是CommonJS的规范之一,拥有resolve、reject、done、fail、then等方法,能够帮助我们控制代码的流程 9 | 10 | 详细点击 [Promise解析](http://es6.ruanyifeng.com/#docs/promise) 11 | 12 | 源码来源 [Promise源码](https://github.com/stefanpenner/es6-promise/blob/master/lib/es6-promise.js) 13 | 14 | ## 图示 15 | 16 | ![](https://mdn.mozillademos.org/files/8633/promises.png) 17 | 18 | ## 进展 19 | - [x] `Promise` 20 | - [x] `Promise.prototype.then()` 21 | - [x] `Promise.prototype.catch()` 22 | - [x] `Promise.resolve()` 23 | - [x] `Promise.reject()` 24 | - [x] `Promise.all()` 25 | - [x] `Promise.race()` 26 | - [x] `Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject` 27 | 28 | ## Promise状态 29 | - pending(等待状态) 30 | - resolved(完成状态,又称fulfill) 31 | - rejected(已拒绝状态) 32 | 33 | ## 重写注意事项 34 | - promise必须实现then方法,then可以说是promise的核心,返回值也是一个promise对象,同一个promise的then可以调用多次 35 | 36 | - then方法接受两个参数,两个参数都是函数。一个是resolved时的回调,一个是rejected时的回调,第二个参数属于可选。 37 | 38 | - Promise.all() 将多个 Promise 实例,包装成一个新的 Promise 实例,类似与操作 39 | 40 | - Promise.race() 将多个 Promise 实例,包装成一个新的 Promise 实例,类似或操作 41 | 42 | ## 简单架子 43 | ```javascript 44 | // 异步的三个状态,pending,fulfill以及rejected 45 | var PENDING = 0; 46 | var FULFILLED = 1; 47 | var REJECTED = 2; 48 | /** 49 | * @class Promise 50 | * @param {[type]} resolver [function] 51 | */ 52 | function Promise(resolver) {}; 53 | Promise.prototype = { 54 | constructor: Promise, 55 | then: then 56 | }; 57 | /** 58 | * [initializePromise 初始化Promise并执行resolver回调] 59 | * @param {[type]} promise [Promise对象] 60 | * @param {[type]} resolver [resolver回调] 61 | */ 62 | function initializePromise(promise, resolver) {}; 63 | /** 64 | * [_resolve resolve处理] 65 | * @param {[type]} promise [Promise对象] 66 | * @param {[type]} value [回调参数] 67 | */ 68 | function _resolve (promise, value) {}; 69 | /** 70 | * [_resolve reject处理] 71 | * @param {[type]} promise [Promise对象] 72 | * @param {[type]} value [回调参数] 73 | */ 74 | function _reject(promise, reason) {}; 75 | /** 76 | * [then 异步回调] 77 | * @param {[function]} resolve [resolve回调] 78 | * @param {[function]} reject [reject回调] 79 | */ 80 | function resolve (object) {}; 81 | function reject (reason) {}; 82 | function then (resolve, reject) {}; 83 | /** 84 | * [nextTick 下一进程处理] 85 | * @param {Function} callback [回调函数] 86 | * @param {[type]} value [回调参数值] 87 | */ 88 | function nextTick (callback, value) {}; 89 | 90 | ``` 91 | 92 | ## JavaScript 语言特点 93 | 94 | 1. 单线程,而这个线程中拥有唯一的一个事件循环。(web worker 这里不参与讨论) 95 | 2. 代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列(task queue)来搞定另外一些代码的执行。 96 | 3. 一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。 97 | 4. 任务队列又分为 macro-task(宏任务)与 micro-task(微任务),在最新标准中,它们被分别称为 task 与 jobs 。 98 | 5. setTimeout/Promise 等我们称之为任务源。而进入任务队列的是他们指定的具体执行任务。 99 | ```javascript 100 | // setTimeout 中的回调函数才是进入任务队列的任务 101 | setTimeout(function() { 102 | console.log('xxxx'); 103 | }) 104 | ``` 105 | 6. 来自不同任务源的任务会进入到不同的任务队列。其中 setTimeout 与 setInterval 是同源的。 106 | 7. 事件循环的顺序,决定了 JavaScript 代码的执行顺序。它从 script (整体代码)开始第一次循环。之后全局上下文进入函数调用栈。直到调用栈清空(只剩全局),然后执行所有的 micro-task 。当所有可执行的 micro-task 执行完毕之后。循环再次从 macro-task 开始,找到其中一个任务队列执行完毕,然后再执行所有的 micro-task,这样一直循环下去。 107 | 8. 其中每一个任务的执行,无论是 macro-task 还是 micro-task,都是借助函数调用栈来完成。 108 | 109 | ## macrotask 与 microtask 类别具体分类 110 | 111 | ```js 112 | // macrotasks 113 | script(整体代码), setImmediate, setTimeout, setInterval, I/O, UI rendering 114 | 115 | // microtasks 116 | process.nextTick, Promises, Object.observe, MutationObserver 117 | ``` 118 | 119 | ## DEMO 120 | 121 | ```javascript 122 | setImmediate(function () { 123 | console.log(1); 124 | }, 0); 125 | setTimeout(function () { 126 | console.log(2); 127 | }, 0); 128 | new Promise(function (resolve) { 129 | console.log(3); 130 | resolve(); 131 | console.log(4); 132 | }).then(function () { 133 | console.log(5); 134 | }); 135 | console.log(6); 136 | process.nextTick(function () { 137 | console.log(7); 138 | }); 139 | console.log(8); 140 | // 3 4 6 8 7 5 1 2 141 | ``` 142 | 143 | ## 执行过程如下: 144 | 145 | 1. JavaScript引擎首先会从macrotask queue中取出第一个任务,即 script (整段代码) 146 | 2. 执行完毕后,将microtask queue中的所有任务取出,按顺序全部执; 147 | 3. 然后再从macrotask queue中取下一个, 148 | 执行完毕后, 149 | 4. 再次将microtask queue中的全部取出; 150 | 循环往复,直到两个queue中的任务都取完。 151 | 152 | ## 解释: 153 | 1. 代码开始执行时,所有这些代码在 `macrotask queue` 中,取出来执行之。 154 | 2. 后面遇到了 `setTimeout`,又加入到`macrotask queue`中, 155 | 3. 然后,遇到了 `promise.then`,放入到了另一个队列 `microtask queue`。 156 | 4. 整个`execution context stack` 执行完后, 157 | 5. 取 `microtask queue` 中的任务了。 158 | 159 | 因此 `promise.then` 的回调比 `setTimeout` 先执行。 160 | 161 | ## 参考 162 | 163 | [Event loops](https://html.spec.whatwg.org/multipage/webappapis.html#event-loops) 164 | 165 | ## question 166 | 167 | **Q:`process.nextTick` 也会放入 `microtask quque`,为什么优先级比 `promise.then` 高呢?** 168 | 169 | **A:process.nextTick 永远大于 promise.then** 170 | 在 Node 中,_tickCallback 在每一次执行完 TaskQueue 中的一个任务后被调用,而这个 _tickCallback 中实质上干了两件事: 171 | 1. nextTickQueue中所有任务执行掉(长度最大1e4,Node版本v6.9.1) 172 | 2. 第一步执行完后执行 `_runMicrotasks`函数,执行 `microtask` 中的部分(`promise.then` 注册的回调)所以很明显 `process.nextTick > promise.then`” 173 | 。 174 | -------------------------------------------------------------------------------- /src/Vue移动端开发的那些坑.md: -------------------------------------------------------------------------------- 1 | ### 1. IOS移动端 原始输入法问题 2 | 3 | IOS原始输入法,中文输入时,无法触发keyup事件,而keyup时对应的enter事件也无法触发 4 | 5 | 解决方案: 6 | 7 | 1. 将keyup监听替换成值的watch 8 | 2. 让使用者安装三方输入法,比如搜狗输入法(不太现实) 9 | 10 | ### 2. IOS移动端click事件300ms的延迟响应 11 | 12 | 移动设备上的web网页是有300ms延迟的,玩玩会造成按钮点击延迟甚至是点击失效。这是由于区分单击事件和双击屏幕缩放的历史原因造成的, 13 | 14 | 2007年苹果发布首款iphone上IOS系统搭载的safari为了将适用于PC端上大屏幕的网页能比较好的展示在手机端上,使用了双击缩放(double tap to zoom)的方案,比如你在手机上用浏览器打开一个PC上的网页,你可能在看到页面内容虽然可以撑满整个屏幕,但是字体、图片都很小看不清,此时可以快速双击屏幕上的某一部分,你就能看清该部分放大后的内容,再次双击后能回到原始状态。 15 | 16 | 双击缩放是指用手指在屏幕上快速点击两次,iOS 自带的 Safari 浏览器会将网页缩放至原始比例。 17 | 18 | 原因就出在浏览器需要如何判断快速点击上,当用户在屏幕上单击某一个元素时候,例如跳转链接,此处浏览器会先捕获该次单击,但浏览器不能决定用户是单纯要点击链接还是要双击该部分区域进行缩放操作,所以,捕获第一次单击后,浏览器会先Hold一段时间t,如果在t时间区间里用户未进行下一次点击,则浏览器会做单击跳转链接的处理,如果t时间里用户进行了第二次单击操作,则浏览器会禁止跳转,转而进行对该部分区域页面的缩放操作。那么这个时间区间t有多少呢?在IOS safari下,大概为300毫秒。这就是延迟的由来。造成的后果用户纯粹单击页面,页面需要过一段时间才响应,给用户慢体验感觉,对于web开发者来说是,页面js捕获click事件的回调函数处理,需要300ms后才生效,也就间接导致影响其他业务逻辑的处理。 19 | 20 | 解决方案: 21 | 22 | - fastclick可以解决在手机上点击事件的300ms延迟 23 | - zepto的touch模块,tap事件也是为了解决在click的延迟问题 24 | - 触摸事件的响应顺序为 touchstart --> touchmove --> touchend --> click,也可以通过绑定ontouchstart事件,加快对事件的响应,解决300ms延迟问题 25 | 26 | ### 3. 一些情况下对非可点击元素如(label,span)监听click事件,ios下不会触发,css增加cursor:pointer就搞定了。 27 | 28 | ### 4. 移动端input元素聚焦问题 29 | 30 | 问题出现场景重现: 项目需要写一个搜索组件,相关代码如下 31 | 32 | ```html 33 | 52 | ``` 53 | 当我进行keyup事件的时候,其对应的enter事件,不能实现失焦功能。而实际项目中需要实现失焦,那么只能通过操作$el节点了。 54 | 55 | 解决方案:在对应的enter事件时进行DOM操作 56 | 57 | ```javascript 58 | searchEnterFn (e) { 59 | document.getElementsByClassName('y-search-input')[0].blur() 60 | // dosomething yourself 61 | } 62 | ``` 63 | 64 | ### 5. fixed定位缺陷 65 | 66 | ios下fixed元素容易定位出错,软键盘弹出时,影响fixed元素定位 67 | android下fixed表现要比iOS更好,软键盘弹出时,不会影响fixed元素定位 68 | ios4下不支持position:fixed 69 | 解决方案: 可用iScroll插件解决这个问题 70 | 71 | ### 6. 阻止旋转屏幕时自动调整字体大小 72 | 73 | ```css 74 | * { 75 | -webkit-text-size-adjust: none; 76 | } 77 | ``` 78 | 79 | ### 7. calc的兼容处理 80 | 81 | CSS3中的calc变量在iOS6浏览器中必须加-webkit-前缀,目前的FF浏览器已经无需-moz-前缀。 82 | Android浏览器目前仍然不支持calc,所以要在之前增加一个保守尺寸: 83 | 84 | ```css 85 | div { 86 | width: 95%; 87 | width: -webkit-calc(100% - 50px); 88 | width: calc(100% - 50px); 89 | } 90 | ``` 91 | 92 | ### 8. 在移动端修改难看的点击的高亮效果,iOS和安卓下都有效 93 | 94 | ```css 95 | * { 96 | -webkit-tap-highlight-color: rgba(0,0,0,0); 97 | } 98 | ``` 99 | 不过这个方法在现在的安卓浏览器下,只能去掉那个橙色的背景色,点击产生的高亮边框还是没有去掉,有待解决! 100 | 101 | 一个CSS3的属性,加上后,所关联的元素的事件监听都会失效,等于让元素变得“看得见,点不着”。IE到11才开始支持,其他浏览器的当前版本基本都支持。详细介绍见这里:[https://developer.mozilla.org/zh-CN/docs/Web/CSS/pointer-events](https://developer.mozilla.org/zh-CN/docs/Web/CSS/pointer-events) 102 | 103 | ```css 104 | pointer-events: none; 105 | ``` 106 | 107 | ### 9. IOS下取消input在输入的时候英文首字母的默认大写 108 | 109 | ```html 110 | 111 | ``` 112 | 113 | ### 10. 禁止 IOS 弹出各种操作窗口 114 | 115 | ```css 116 | -webkit-touch-callout: none; 117 | ``` 118 | 119 | ### 11. 消除transition闪屏问题 120 | 121 | ```css 122 | /*设置内嵌的元素在 3D 空间如何呈现:保留 3D*/ 123 | -webkit-transform-style: preserve-3d; 124 | /*(设置进行转换的元素的背面在面对用户时是否可见:隐藏)*/ 125 | -webkit-backface-visibility: hidden; 126 | ``` 127 | 128 | ### 12. IOS系统中文输入法输入英文时,字母之间可能会出现一个六分之一的空格 129 | 130 | 解决方案:通过正则去除 131 | 132 | ```javascript 133 | this.value = this.value.replace(/\u2006/g, ''); 134 | ``` 135 | 136 | ### 13. 禁止IOS和Android用户选中文字 137 | 138 | ```css 139 | -webkit-user-select: none; 140 | ``` 141 | 142 | ### 14. CSS动画页面闪白,动画卡顿 143 | 144 | 解决方法: 145 | 1.尽可能地使用合成属性transform和opacity来设计CSS3动画,不使用position的left和top来定位 146 | 2.开启硬件加速 147 | 148 | ```css 149 | -webkit-transform: translate3d(0, 0, 0); 150 | -moz-transform: translate3d(0, 0, 0); 151 | -ms-transform: translate3d(0, 0, 0); 152 | transform: translate3d(0, 0, 0); 153 | ``` 154 | 155 | ### 15. input的placeholder会出现文本位置偏上的情况 156 | 157 | input 的placeholder会出现文本位置偏上的情况:PC端设置line-height等于height能够对齐,而移动端仍然是偏上,解决是设置line-height:normal 158 | 159 | ### 16. 往返缓存问题 160 | 161 | 点击浏览器的回退,有时候不会自动执行js,特别是在mobilesafari中。这与往返缓存(bfcache)有关系。 162 | 163 | 解决方法 : 164 | ```javascript 165 | window.onunload = function(){}; 166 | ``` 167 | 168 | ### 17. 微信端安卓手机window.location.reload()缓存问题 169 | 170 | 安卓的微信浏览器中reoad后请求的一直是第一次打开页面时请求的数据,请求被缓存 171 | 172 | 解决方法: 173 | ```javascript 174 | window.location.href = "window.location.href + 随机数" // 一定要加随机数,不然没啥用 175 | ``` 176 | 177 | 只针对微信浏览器作此设置 178 | ```javascript 179 | // true为微信浏览器,false则不是 180 | const IS_WEIXIN = window.navigator.userAgent.toLowerCase().match(/MicroMessenger/i) === 'micromessenger' 181 | ``` 182 | 183 | ### 17. 移动端白屏问题 184 | 185 | 1. 网络问题 186 | 2. 静态资源加载,缓存问题 187 | 188 | ```html 189 | 190 | ``` 191 | -------------------------------------------------------------------------------- /src/my-mvvm/test.js: -------------------------------------------------------------------------------- 1 | function observe (data) { 2 | if (!data || typeof data !== 'object') { 3 | return; 4 | } 5 | Object.keys(data).forEach(key => { 6 | observeProperty(data, key, data[key]) 7 | }) 8 | } 9 | function observeProperty (obj, key, val) { 10 | observe(val); 11 | Object.defineProperty(obj, key, { 12 | enumerable: true, // 可枚举 13 | configurable: true, // 可重新定义 14 | get: function () { 15 | return val; 16 | }, 17 | set: function (newVal) { 18 | if (val === newVal || (newVal !== newVal && val !== val)) { 19 | return; 20 | } 21 | console.log('数据更新啦 ', val, '=>', newVal); 22 | val = newVal; 23 | } 24 | }); 25 | } 26 | function Compile (el, value) { 27 | this.$val = value; 28 | this.$el = this.isElementNode(el) ? el : document.querySelector(el); 29 | if (this.$el) { 30 | this.compileElement(this.$el); 31 | } 32 | } 33 | Compile.prototype = { 34 | compileElement: function (el) { 35 | let self = this; 36 | let childNodes = el.childNodes; 37 | [].slice.call(childNodes).forEach(node => { 38 | let text = node.textContent; 39 | let reg = /\{\{((?:.|\n)+?)\}\}/; 40 | // 如果是element节点 41 | if (self.isElementNode(node)) { 42 | self.compile(node); 43 | } 44 | // 如果是text节点 45 | else if (self.isTextNode(node) && reg.test(text)) { 46 | // 匹配第一个选项 47 | self.compileText(node, RegExp.$1.trim()); 48 | } 49 | // 解析子节点包含的指令 50 | if (node.childNodes && node.childNodes.length) { 51 | self.compileElement(node); 52 | } 53 | }) 54 | }, 55 | // 指令解析 56 | compile: function (node) { 57 | let nodeAttrs = node.attributes; 58 | let self = this; 59 | 60 | [].slice.call(nodeAttrs).forEach(attr => { 61 | var attrName = attr.name; 62 | if (self.isDirective(attrName)) { 63 | var exp = attr.value; 64 | node.innerHTML = typeof this.$val[exp] === 'undefined' ? '' : this.$val[exp]; 65 | node.removeAttribute(attrName); 66 | } 67 | }); 68 | }, 69 | // {{ test }} 匹配变量 test 70 | compileText: function (node, exp) { 71 | node.textContent = typeof this.$val[exp] === 'undefined' ? '' : this.$val[exp]; 72 | }, 73 | // element节点 74 | isElementNode: function (node) { 75 | return node.nodeType === 1; 76 | }, 77 | // text纯文本 78 | isTextNode: function (node) { 79 | return node.nodeType === 3 80 | }, 81 | // x-XXX指令判定 82 | isDirective: function (attr) { 83 | return attr.indexOf('x-') === 0; 84 | } 85 | } 86 | 87 | console.log('=========修改__proto__=========='); 88 | // 获取Array原型 89 | const arrayProto = Array.prototype; 90 | const arrayMethods = Object.create(arrayProto); 91 | const newArrProto = []; 92 | [ 93 | 'push', 94 | 'pop', 95 | 'shift', 96 | 'unshift', 97 | 'splice', 98 | 'sort', 99 | 'reverse' 100 | ].forEach(method => { 101 | // 原生Array的原型方法 102 | let original = arrayMethods[method]; 103 | 104 | // 将push,pop等方法重新封装并定义在对象newArrProto的属性上 105 | // 这里需要注意的是封装好的方法是定义在newArrProto的属性上而不是其原型属性 106 | // newArrProto.__proto__ 没有改变 107 | newArrProto[method] = function mutator() { 108 | console.log('监听到数组的变化啦!'); 109 | 110 | // 调用对应的原生方法并返回结果(新数组长度) 111 | return original.apply(this, arguments); 112 | } 113 | }) 114 | 115 | let list = [1, 2]; 116 | // 将我们要监听的数组的原型指针指向上面定义的空数组对象 117 | // newArrProto的属性上定义了我们封装好的push等方法 118 | list.__proto__ = newArrProto; 119 | list.push(3); // 监听到数组的变化啦! 3 120 | 121 | // 这里的list2没有被重新定义原型指针,所以这里会正常执行原生Array上的原型方法 122 | let list2 = [1, 2]; 123 | list2.push(3); // 3 124 | console.log('===============ES6=============='); 125 | 126 | 127 | class NewArray extends Array { 128 | constructor(...args) { 129 | // 调用父类Array的constructor() 130 | super(...args) 131 | } 132 | push (...args) { 133 | console.log('监听到数组的变化啦!'); 134 | 135 | // 调用父类原型push方法 136 | return super.push(...args) 137 | } 138 | // ... 139 | } 140 | 141 | let list3 = [1, 2]; 142 | 143 | let arr = new NewArray(...list3); 144 | console.log(arr) 145 | // (2) [1, 2] 146 | 147 | arr.push(3); 148 | // 监听到数组的变化啦! 149 | console.log(arr) 150 | // (3) [1, 2, 3] 151 | 152 | console.log('=============prototype==========='); 153 | 154 | /** 155 | * 寄生式继承 继承原型 156 | * 传递参数 subClass 子类 157 | * 传递参数 superClass 父类 158 | */ 159 | function inheritObject(o){ 160 | //声明一个过渡函数 161 | function F(){} 162 | //过渡对象的原型继承父对象 163 | F.prototype = o; 164 | return new F(); 165 | } 166 | function inheritPrototype(subClass,superClass){ 167 | //复制一份父类的原型副本保存在变量 168 | var p = inheritObject(superClass.prototype); 169 | //修正因为重写子类原型导致子类的constructor 170 | p.constructor = subClass; 171 | //设置子类的原型 172 | subClass.prototype = p; 173 | } 174 | 175 | function ArrayOfMine () { 176 | var args = arguments 177 | , len = args.length 178 | , i = 0 179 | , args$1 = []; // 保存所有arguments 180 | for (; i < len; i++) { 181 | // 判断参数是否为数组,如果是则直接concat 182 | if (Array.isArray(args[i])) { 183 | args$1 = args$1.concat(args[i]); 184 | } 185 | // 如果不是数组,则直接push到 186 | else { 187 | args$1.push(args[i]) 188 | } 189 | } 190 | // 接收Array.apply的返回值,刚接收的时候arr是一个Array 191 | var arr = Array.apply(null, args$1); 192 | // 将arr的__proto__属性指向 ArrayOfMine的 prototype 193 | arr.__proto__ = ArrayOfMine.prototype; 194 | return arr; 195 | } 196 | inheritPrototype(ArrayOfMine, Array); 197 | // 重写父类Array的push,pop等方法 198 | ArrayOfMine.prototype.push = function () { 199 | console.log('监听到数组的变化啦!'); 200 | return Array.prototype.push.apply(this, arguments); 201 | } 202 | var list4 = [1, 2]; 203 | var newList = new ArrayOfMine(list4, 3); 204 | console.log(newList, newList.length, newList instanceof Array, Array.isArray(newList)); 205 | newList.push(4); 206 | console.log(newList, newList.length, newList instanceof Array, Array.isArray(newList)); 207 | 208 | // function Father() { 209 | // // 这里我们暂且就先假定参数只有一个 210 | // this.args = arguments[0]; 211 | // return this.args; 212 | // } 213 | // Father.prototype.push = function () { 214 | // this.args.push(arguments); 215 | // console.log('我是父类方法'); 216 | // } 217 | // function ArrayOfMine () { 218 | // Father.apply(this, arguments); 219 | // } 220 | // inheritPrototype(ArrayOfMine, Father); 221 | // // 重写父类Array的push,pop等方法 222 | // ArrayOfMine.prototype.push = function () { 223 | // console.log('监听到数组的变化啦!'); 224 | // return Father.prototype.push.apply(this, arguments); 225 | // } 226 | // var list4 = [1, 2]; 227 | // var newList = new ArrayOfMine(list4, 3); 228 | // console.log(newList, newList instanceof Father); 229 | // newList.push(3); 230 | // console.log(newList, newList instanceof Father); 231 | 232 | console.log('============数组特殊属性==========='); 233 | var arr1 = [1]; 234 | arr1[5] = 1; 235 | console.log(arr1.length === 6); // true 236 | // 以及 237 | var arr2 = [1,2,3]; 238 | arr2.length = 1 239 | console.log(arr2); 240 | // [1] 此时元素2,3被删除了 241 | -------------------------------------------------------------------------------- /src/my-promise/promise.js: -------------------------------------------------------------------------------- 1 | var PENDING = 0; 2 | var FULFILLED = 1; 3 | var REJECTED = 2; 4 | // 空操作 5 | function noop() {}; 6 | // isArray 7 | var _isArray = undefined; 8 | if (!Array.isArray) { 9 | _isArray = function (a) { 10 | return Object.prototype.toString.call(a) === '[object Array]'; 11 | } 12 | } 13 | else { 14 | _isArray = Array.isArray 15 | } 16 | var isArray = _isArray; 17 | 18 | /** 19 | * @class Promise 20 | * @param {[type]} resolver [function] 21 | */ 22 | function Promise(resolver) { 23 | this._state = PENDING; 24 | this._result = undefined; 25 | this._subscribers = []; 26 | // 初始化跑一遍resolver() 27 | if (noop !== resolver) { 28 | typeof resolver !== 'function' && needsResolver(); 29 | this instanceof Promise ? initializePromise(this, resolver) : needsNew(); 30 | } 31 | }; 32 | Promise.resolve = resolve; 33 | Promise.reject = reject; 34 | Promise.all = all; 35 | Promise.race = race; 36 | 37 | Promise.prototype = { 38 | constructor: Promise, 39 | then: then, 40 | 'catch': function _catch(onRejection) { 41 | return this.then(null, onRejection); 42 | } 43 | }; 44 | function needsResolver() { 45 | throw new TypeError('You must pass a resolver function as the first argument to the promise constructor') 46 | } 47 | function needsNew() { 48 | throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.") 49 | } 50 | /** 51 | * [initializePromise 初始化Promise并执行resolver回调] 52 | * @param {[type]} promise [Promise对象] 53 | * @param {[type]} resolver [resolver回调] 54 | */ 55 | function initializePromise(promise, resolver) { 56 | try { 57 | resolver(function resolvePromise(value) { 58 | _resolve(promise, value); 59 | }, function rejectPromise(reason) { 60 | _reject(promise, reason); 61 | }); 62 | } catch (e) { 63 | _reject(promise, e); 64 | } 65 | } 66 | /** 67 | * [_resolve resolve处理] 68 | * @param {[type]} promise [Promise对象] 69 | * @param {[type]} value [回调参数] 70 | */ 71 | function _resolve (promise, value) { 72 | if (promise._state !== PENDING) { 73 | return; 74 | } 75 | promise._result = value; 76 | promise._state = FULFILLED; 77 | } 78 | /** 79 | * [_resolve reject处理] 80 | * @param {[type]} promise [Promise对象] 81 | * @param {[type]} value [回调参数] 82 | */ 83 | function _reject(promise, reason) { 84 | if (promise._state !== PENDING) { 85 | return; 86 | } 87 | promise._result = reason; 88 | promise._state = REJECTED; 89 | } 90 | function ErrorObject () { 91 | this.error = null; 92 | } 93 | var TRY_CATCH_ERROR = new ErrorObject(); 94 | /** 95 | * [then 异步回调] 96 | * @param {[function]} resolve [resolve回调] 97 | * @param {[function]} reject [reject回调] 98 | */ 99 | function then (resolve, reject) { 100 | var _arguments = arguments; 101 | var parent = this; 102 | var child = new this.constructor(noop); 103 | 104 | var _state = parent._state; 105 | var callback = _arguments[_state - 1]; 106 | if (child._state !== PENDING) { 107 | // noop 108 | } else if (_state === FULFILLED) { 109 | _resolve(child, parent._result); 110 | } else if (_state === REJECTED) { 111 | _reject(child, parent._result); 112 | } 113 | if (typeof callback === 'function') { 114 | try { 115 | nextTick(callback, parent._result); 116 | } catch (e) { 117 | TRY_CATCH_ERROR.error = e; 118 | return TRY_CATCH_ERROR; 119 | } 120 | } 121 | return child; 122 | } 123 | 124 | function resolve (object) { 125 | var Constructor = this; 126 | // 如果传进来的参数是一个Promise对象,则直接返回该参数 127 | if (object && typeof object === 'object' && object.constructor === Constructor) { 128 | return object; 129 | } 130 | 131 | var promise = new Constructor(noop) 132 | _resolve(promise, object); 133 | return promise; 134 | } 135 | 136 | function reject (reason) { 137 | var Constructor = this; 138 | var promise = new Constructor(noop); 139 | _reject(promise, reason); 140 | return promise 141 | } 142 | 143 | function all (entries) { 144 | return new Enumerator(this, entries).promise; 145 | } 146 | 147 | function Enumerator (Constructor, input) { 148 | this._instanceConstructor = Constructor; 149 | this.promise = new Constructor(noop); 150 | 151 | if (isArray(input)) { 152 | this._input = input; 153 | this.length = input.length; 154 | 155 | this._result = new Array(this.length); 156 | 157 | if (this.length === 0) { 158 | _resolve(this.promise, this._result); 159 | } else { 160 | this._enumerate(); 161 | } 162 | } 163 | else { 164 | _reject(this.promise, validationError()) 165 | } 166 | } 167 | function validationError() { 168 | return new Error('Array Methods must be provided an Array'); 169 | } 170 | Enumerator.prototype._enumerate = function () { 171 | var _input = this._input; 172 | var promise = this.promise; 173 | for (var i = 0, l = _input.length; i < l; i++) { 174 | var currentPromise = _input[i]; 175 | if (!(currentPromise instanceof Promise) || currentPromise._state === PENDING) { 176 | currentPromise = resolve(currentPromise); 177 | promise._result[i] = currentPromise._result; 178 | } 179 | if (currentPromise._state === REJECTED) { 180 | promise._result = currentPromise._result; 181 | } 182 | promise._state = currentPromise._state; 183 | } 184 | } 185 | 186 | function race (entries) { 187 | var Constructor = this; 188 | 189 | if (!isArray(entries)) { 190 | return new Constructor(function(_, reject) { 191 | return reject(new TypeError('You must pass an array to race.')) 192 | }); 193 | } 194 | else { 195 | return new Constructor(function (resolve, reject) { 196 | var length = entries.length; 197 | for (var i = 0; i < length; i++) { 198 | Constructor.resolve(entries[i]).then(resolve, reject); 199 | } 200 | }); 201 | } 202 | } 203 | 204 | /** 205 | * [nextTick 下一进程处理] 206 | * @param {Function} callback [回调函数] 207 | * @param {[type]} value [回调参数值] 208 | */ 209 | function nextTick (callback, value) { 210 | setTimeout(callback, 0, value); 211 | } 212 | 213 | /** 214 | * var browserWindow = typeof window !== 'undefined' ? window : undefined; 215 | * var browserGlobal = browserWindow || {}; 216 | * var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver; 217 | * var isNode = typeof self === 'undefined' && typeof process !== 'undefined' && ({}).toString.call(process) === '[object process]'; 218 | * 219 | * // test for web worker but not in IE10 220 | * var isWorker = typeof Uint8ClampedArray !== 'undefined' && typeof importScripts !== 'undefined' && typeof MessageChannel !== 'undefined'; 221 | */ 222 | 223 | 224 | /** 225 | * 226 | // node 227 | function useNextTick() { 228 | // node version 0.10.x displays a deprecation warning when nextTick is used recursively 229 | // see https://github.com/cujojs/when/issues/410 for details 230 | return function () { 231 | return process.nextTick(flush); 232 | }; 233 | } 234 | 235 | // vertx 236 | function useVertxTimer() { 237 | if (typeof vertxNext !== 'undefined') { 238 | return function () { 239 | vertxNext(flush); 240 | }; 241 | } 242 | 243 | return useSetTimeout(); 244 | } 245 | 246 | function useMutationObserver() { 247 | var iterations = 0; 248 | var observer = new BrowserMutationObserver(flush); 249 | var node = document.createTextNode(''); 250 | observer.observe(node, { characterData: true }); 251 | 252 | return function () { 253 | node.data = iterations = ++iterations % 2; 254 | }; 255 | } 256 | 257 | // web worker 258 | function useMessageChannel() { 259 | var channel = new MessageChannel(); 260 | channel.port1.onmessage = flush; 261 | return function () { 262 | return channel.port2.postMessage(0); 263 | }; 264 | } 265 | 266 | function useSetTimeout() { 267 | // Store setTimeout reference so es6-promise will be unaffected by 268 | // other code modifying setTimeout (like sinon.useFakeTimers()) 269 | var globalSetTimeout = setTimeout; 270 | return function () { 271 | return globalSetTimeout(flush, 1); 272 | }; 273 | } 274 | var len = 0; 275 | var queue = new Array(1000); 276 | function flush() { 277 | for (var i = 0; i < len; i += 2) { 278 | var callback = queue[i]; 279 | var arg = queue[i + 1]; 280 | 281 | callback(arg); 282 | 283 | queue[i] = undefined; 284 | queue[i + 1] = undefined; 285 | } 286 | 287 | len = 0; 288 | } 289 | */ 290 | -------------------------------------------------------------------------------- /src/my-mvvm/observeArray.md: -------------------------------------------------------------------------------- 1 | ## 数组监听 2 | 3 | ## 一、整体思路 4 | 5 | 1. 定义变量arrayProto接收Array的prototype 6 | 2. 定义变量arrayMethods,通过`Object.create()`方法继承arrayProto 7 | 3. 重新封装数组中`push`,`pop`等常用方法。(这里我们只封装我们需要监听的数组的方法,并不做JavaScript原生Array中原型方法的重写的这么一件暴力的事情) 8 | 4. 更多的奇淫技巧探究 9 | 10 | ## 二、监听数组变化实现 11 | 12 | 这里我们首先需要确定的一件事情就是,我们只需要监听我们需要监听的数据数组的一个变更,而不是针对原生Array的一个重新封装。 13 | 14 | 其实代码实现起来会比较简短,这一部分代码我会直接带着注释贴出来 15 | 16 | ```JavaScript 17 | // 获取Array原型 18 | const arrayProto = Array.prototype; 19 | const arrayMethods = Object.create(arrayProto); 20 | const newArrProto = []; 21 | [ 22 | 'push', 23 | 'pop', 24 | 'shift', 25 | 'unshift', 26 | 'splice', 27 | 'sort', 28 | 'reverse' 29 | ].forEach(method => { 30 | // 原生Array的原型方法 31 | let original = arrayMethods[method]; 32 | 33 | // 将push,pop等方法重新封装并定义在对象newArrProto的属性上 34 | // 这里需要注意的是封装好的方法是定义在newArrProto的属性上而不是其原型属性 35 | // newArrProto.__proto__ 没有改变 36 | newArrProto[method] = function mutator() { 37 | console.log('监听到数组的变化啦!'); 38 | 39 | // 调用对应的原生方法并返回结果(新数组长度) 40 | return original.apply(this, arguments); 41 | } 42 | }) 43 | 44 | let list = [1, 2]; 45 | // 将我们要监听的数组的原型指针指向上面定义的空数组对象 46 | // newArrProto的属性上定义了我们封装好的push,pop等方法 47 | list.__proto__ = newArrProto; 48 | list.push(3); // 监听到数组的变化啦! 3 49 | 50 | // 这里的list2没有被重新定义原型指针,所以这里会正常执行原生Array上的原型方法 51 | let list2 = [1, 2]; 52 | list2.push(3); // 3 53 | ``` 54 | 目前为止我们已经实现了数组的监听。从上面我们看出,当我们将需要监听的数组的原型指针指向newArrProto对象上的时候(newArrProto的属性上定义了我们封装好的push,pop等方法)。这样做的好处很明显,不会污染到原生Array上的原型方法。 55 | 56 | ## 三、更多的奇淫技巧 57 | 58 | ### 1、分析实现的机制 59 | 60 | 从上面我们看出,其实我们做了一件非常简单的事情,首先我们将需要监听的数组的原型指针指向newArrProto,然后它会执行原生Array中对应的原型方法,与此同时执行我们自己重新封装的方法。 61 | 62 | 那么问题来了,这种形式咋这么眼熟呢?这不就是我们见到的最多的继承问题么?子类(newArrProto)和父类(Array)做的事情相似,却又和父类做的事情不同。但是直接修改`__proto__`隐式原型指向总感觉心里怪怪的(因为我们可能看到的多的还是prototype),心里不(W)舒(T)服(F)。 63 | 64 | ![](https://static.oschina.net/uploads/space/2017/0529/231513_HNS0_2912341.png) 65 | 66 | 那么接下来的事情就是尝试用继承(常见的prototype)来实现数组的变更监听。对于继承这一块可以参考我之前写过的一篇文章[浅析JavaScript继承](https://my.oschina.net/qiangdada/blog/745061)。 67 | 68 | ### 2、利用ES6的extends实现 69 | 70 | 首先这里我们会通过ES6的关键字extends实现继承完成Array原型方法的重写,咱总得先用另外一种方式来实现一下我们上面实现的功能,证明的确还有其他方法可以做到这件事。OK,废话不多说,直接看代码 71 | 72 | ```JavaScript 73 | class NewArray extends Array { 74 | constructor(...args) { 75 | // 调用父类Array的constructor() 76 | super(...args) 77 | } 78 | push (...args) { 79 | console.log('监听到数组的变化啦!'); 80 | 81 | // 调用父类原型push方法 82 | return super.push(...args) 83 | } 84 | // ... 85 | } 86 | 87 | let list3 = [1, 2]; 88 | 89 | let arr = new NewArray(...list3); 90 | console.log(arr) 91 | // (2) [1, 2] 92 | 93 | arr.push(3); 94 | // 监听到数组的变化啦! 95 | console.log(arr) 96 | // (3) [1, 2, 3] 97 | ``` 98 | 99 | ### 3、ES5及以下的方法能实现么? 100 | 101 | OK,终于要回到我们常见的带有prototype的继承了,看看它究竟能不能也实现这件事情呢。这里我们直接上最优雅的继承方式-寄生式组合继承,看看能不能搞定这件事情。代码如下 102 | 103 | ```JavaScript 104 | /** 105 | * 寄生式继承 继承原型 106 | * 传递参数 subClass 子类 107 | * 传递参数 superClass 父类 108 | */ 109 | function inheritObject(o){ 110 | //声明一个过渡函数 111 | function F(){} 112 | //过渡对象的原型继承父对象 113 | F.prototype = o; 114 | return new F(); 115 | } 116 | function inheritPrototype(subClass,superClass){ 117 | //复制一份父类的原型副本保存在变量 118 | var p = inheritObject(superClass.prototype); 119 | //修正因为重写子类原型导致子类的constructor指向父类 120 | p.constructor = subClass; 121 | //设置子类的原型 122 | subClass.prototype = p; 123 | } 124 | 125 | function ArrayOfMine (args) { 126 | Array.apply(this, args); 127 | } 128 | inheritPrototype(ArrayOfMine, Array); 129 | // 重写父类Array的push,pop等方法 130 | ArrayOfMine.prototype.push = function () { 131 | console.log('监听到数组的变化啦!'); 132 | return Array.prototype.push.apply(this, arguments); 133 | } 134 | var list4 = [1, 2]; 135 | var newList = new ArrayOfMine(list4); 136 | console.log(newList, newList.length, newList instanceof Array, Array.isArray(newList)); 137 | newList.push(3); 138 | console.log(newList, newList.length, newList instanceof Array, Array.isArray(newList)); 139 | ``` 140 | 目前我们这么看来,的的确确是利用寄生式组合继承完成了一个类的继承,那么console.log的结果又是如何的呢?是不是和我们预想的一样呢,直接看图说话吧 141 | 142 | ![](https://static.oschina.net/uploads/space/2017/0530/013806_7LRh_2912341.png) 143 | 144 | ![](https://static.oschina.net/uploads/space/2017/0530/013958_VQQC_2912341.png) 145 | 146 | 我擦嘞,这特么什么鬼,教练,我们说好的,不是这个结果。这是典型的买家秀和卖家秀吗? 147 | 148 | 那么我们来追溯一下为什么会是这种情况,我们预想中的情况应该是这样的 149 | 150 | ```JavaScript 151 | newList => [1, 2] newList.length => 2 Array.isArray(newList) => true 152 | ``` 153 | push执行之后的理想结果 154 | 155 | ```JavaScript 156 | newList => [1, 2, 3] newList.length => 3 Array.isArray(newList) => true 157 | ``` 158 | 159 | 我们先抛弃Array的apply之后的结果,我们先用同样的方式继承我们自定义的父类Father,代码如下 160 | 161 | ```JavaScript 162 | function inheritObject(o){ 163 | function F(){}; 164 | F.prototype = o; 165 | return new F(); 166 | } 167 | function inheritPrototype(subClass,superClass){ 168 | var p = inheritObject(superClass.prototype); 169 | p.constructor = subClass; 170 | subClass.prototype = p; 171 | } 172 | 173 | function Father() { 174 | // 这里我们暂且就先假定参数只有一个 175 | this.args = arguments[0]; 176 | return this.args; 177 | } 178 | Father.prototype.push = function () { 179 | this.args.push(arguments); 180 | console.log('我是父类方法'); 181 | } 182 | function ArrayOfMine () { 183 | Father.apply(this, arguments); 184 | } 185 | inheritPrototype(ArrayOfMine, Father); 186 | // 重写父类Array的push,pop等方法 187 | ArrayOfMine.prototype.push = function () { 188 | console.log('监听到数组的变化啦!'); 189 | return Father.prototype.push.apply(this, arguments); 190 | } 191 | var list4 = [1, 2]; 192 | var newList = new ArrayOfMine(list4, 3); 193 | console.log(newList, newList instanceof Father); 194 | newList.push(3); 195 | console.log(newList, newList instanceof Father); 196 | ``` 197 | 198 | 结果如图 199 | 200 | ![](https://static.oschina.net/uploads/space/2017/0530/021555_Lk7B_2912341.png) 201 | 202 | 结果和我们之前预想的是一样的,我们自己定义的类的话,这种做法是可以行的通的,那么问题就来了,为什么将父类改成Array就行不通了呢? 203 | 204 | 为了搞清问题,查阅各种资料后。得出以下结论: 205 | 因为`Array`构造函数执行时不会对传进去的this做任何处理。不止`Array`,`String`,`Number`,`Regexp`,`Object`等等JS的内置类都不行。。这也是著名问题 ES5及以下的JS无法完美继承数组 的来源,不清楚的小伙伴可以Google查查这个问题。那么,为什么不能完美继承呢? 206 | 207 | + 数组有个响应式的length,一方面它会跟进你填入的元素的下表进行一个增长,另一方面如果你将它改小的话,它会直接将中间的元素也删除掉 208 | 209 | ```JavaScript 210 | var arr1 = [1]; 211 | arr1[5] = 1; 212 | console.log(arr1.length === 6); // true 213 | // 以及 214 | var arr2 = [1,2,3]; 215 | arr2.length = 1 216 | console.log(arr2); 217 | // [1] 此时元素2,3被删除了 218 | ``` 219 | 220 | + 数组内部的`[[class]]` 属性,这个属性是我们用`Array.isArray(someArray)`和`Object.prototype.String.call(someArray)` 来判定someArray是否是数组的根源,而这又是内部引擎的实现,用任何JS方法都是无法改变的。而为啥要用这两种方法进行数组的判定,相信大家从前面的代码结果可以看出来,利用`instanceof`去判定是否为数组,结果是有问题的。 221 | 222 | 因为数组其响应式的length属性以及内部的`[[class]]`属性我们无法再JS层面实现,这就导致我们无法去用任何一个对象来“模仿”一个数组,而我们想要创建一个ArrayOfMine继承Array的话又必须直接用Array的构造函数,而上面我提到了Array构造函数执行时是不会对传进去的this做任何处理,也就是说这样你根本就不能继承他。而利用`__proto__`隐式原型的指针变更却能实现,因为他是一个非标准的属性(已在ES6语言规范中标准化),详请请点击链接[`__proto__`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto)。 223 | 224 | 所以要实现最上面我们实现的功能,我们还是需要用到`__proto__`属性。变更后代码如下 225 | 226 | ```JavaScript 227 | function inheritObject(o){ 228 | function F(){} 229 | F.prototype = o; 230 | return new F(); 231 | } 232 | function inheritPrototype(subClass,superClass){ 233 | var p = inheritObject(superClass.prototype); 234 | p.constructor = subClass; 235 | subClass.prototype = p; 236 | } 237 | 238 | function ArrayOfMine () { 239 | var args = arguments 240 | , len = args.length 241 | , i = 0 242 | , args$1 = []; // 保存所有arguments 243 | for (; i < len; i++) { 244 | // 判断参数是否为数组,如果是则直接concat 245 | if (Array.isArray(args[i])) { 246 | args$1 = args$1.concat(args[i]); 247 | } 248 | // 如果不是数组,则直接push到 249 | else { 250 | args$1.push(args[i]) 251 | } 252 | } 253 | // 接收Array.apply的返回值,刚接收的时候arr是一个Array 254 | var arr = Array.apply(null, args$1); 255 | // 将arr的__proto__属性指向 ArrayOfMine的 prototype 256 | arr.__proto__ = ArrayOfMine.prototype; 257 | return arr; 258 | } 259 | inheritPrototype(ArrayOfMine, Array); 260 | // 重写父类Array的push,pop等方法 261 | ArrayOfMine.prototype.push = function () { 262 | console.log('监听到数组的变化啦!'); 263 | return Array.prototype.push.apply(this, arguments); 264 | } 265 | var list4 = [1, 2]; 266 | var newList = new ArrayOfMine(list4, 3); 267 | console.log(newList, newList.length, newList instanceof Array, Array.isArray(newList)); 268 | newList.push(4); 269 | console.log(newList, newList.length, newList instanceof Array, Array.isArray(newList)); 270 | ``` 271 | 结果如图 272 | 273 | ![](https://static.oschina.net/uploads/space/2017/0530/101243_kMgZ_2912341.png) 274 | 275 | 自此,我所知道几种实现数组监听的方法便得于实现了。 276 | 277 | ## 总结 278 | 279 | 总结以上几点方案,基于[上篇文章](https://my.oschina.net/qiangdada/blog/906220)的基础,完整的数组监听代码如下 280 | ```JavaScript 281 | // Define Property 282 | function def (obj, key, val, enumerable) { 283 | Object.defineProperty(obj, key, { 284 | value: val, 285 | enumerable: !!enumerable, 286 | configurable: true, 287 | writable: true 288 | }) 289 | } 290 | // observe array 291 | let arrayProto = Array.prototype; 292 | let arrayMethods = Object.create(arrayProto); 293 | [ 294 | 'push', 295 | 'pop', 296 | 'shift', 297 | 'unshift', 298 | 'splice', 299 | 'sort', 300 | 'reverse' 301 | ].forEach(method => { 302 | // 原始数组操作方法 303 | let original = arrayMethods[method]; 304 | def(arrayMethods, method, function () { 305 | let arguments$1 = arguments; 306 | let i = arguments.length; 307 | let args = new Array(i); 308 | 309 | while (i--) { 310 | args[i] = arguments$1[i] 311 | } 312 | // 执行数组方法 313 | let result = original.apply(this, args); 314 | // 因 arrayMethods 是为了作为 Observer 中的 value 的原型或者直接作为属性,所以此处的 this 一般就是指向 Observer 中的 value 315 | // 当然,还需要修改 Observer,使得其中的 value 有一个指向 Observer 自身的属性,__ob__,以此将两者关联起来 316 | let ob = this.__ob__; 317 | // 存放新增数组元素 318 | let inserted; 319 | // 为add 进arry中的元素进行observe 320 | switch (method) { 321 | case 'push': 322 | inserted = args; 323 | break; 324 | case 'unshift': 325 | inserted = args; 326 | break; 327 | case 'splice': 328 | // 第三个参数开始才是新增元素 329 | inserted = args.slice(2); 330 | break; 331 | } 332 | if (inserted) { 333 | ob.observeArray(inserted); 334 | } 335 | // 通知数组变化 336 | ob.dep.notify(); 337 | // 返回新数组长度 338 | return result; 339 | }) 340 | 341 | }) 342 | ``` 343 | mvvm库的完整代码链接: 344 | 345 | `github`- [https://github.com/xuqiang521/overwrite/tree/master/my-mvvm](https://github.com/xuqiang521/overwrite/tree/master/my-mvvm) 346 | 347 | `码云`- [https://git.oschina.net/qiangdada_129/overwrite/tree/master/my-mvvm](https://git.oschina.net/qiangdada_129/overwrite/tree/master/my-mvvm) 348 | 349 | `在线JS Bin预览地址`-[http://jsbin.com/tixekufaha/edit?html,js,output](http://jsbin.com/tixekufaha/edit?html,js,output) 350 | 351 | ![](https://static.oschina.net/uploads/space/2017/0530/110300_SE7n_2912341.png) 352 | 353 | 喜欢的话走波`star`,`star`是我继续下去最大的动力了 354 | 355 | 以上便是这篇文章的所有内容了,如果有哪里写的有问题,还请各位小伙伴拍砖指出,共同进步学习! 356 | -------------------------------------------------------------------------------- /src/my-mvvm/mvvm.js: -------------------------------------------------------------------------------- 1 | // Define Property 2 | function def (obj, key, val, enumerable) { 3 | Object.defineProperty(obj, key, { 4 | value: val, 5 | enumerable: !!enumerable, 6 | configurable: true, 7 | writable: true 8 | }) 9 | } 10 | 11 | /** 12 | * @class 双向绑定类 MVVM 13 | * @param {[type]} options [description] 14 | */ 15 | function MVVM (options) { 16 | this.$options = options || {}; 17 | let data = this._data = this.$options.data; 18 | let self = this; 19 | 20 | Object.keys(data).forEach(key => { 21 | self._proxyData(key); 22 | }); 23 | observe(data, this); 24 | new Compile(options.el || document.body, this); 25 | } 26 | MVVM.prototype = { 27 | /** 28 | * [属性代理] 29 | * @param {[type]} key [数据key] 30 | * @param {[type]} setter [属性set] 31 | * @param {[type]} getter [属性get] 32 | */ 33 | _proxyData: function (key, setter, getter) { 34 | let self = this; 35 | setter = setter || 36 | Object.defineProperty(self, key, { 37 | configurable: false, 38 | enumerable: true, 39 | get: function proxyGetter() { 40 | return self._data[key]; 41 | }, 42 | set: function proxySetter(newVal) { 43 | self._data[key] = newVal; 44 | } 45 | }) 46 | }, 47 | $set: set, 48 | $delete: del 49 | } 50 | function set (target, key, val) { 51 | if (Array.isArray(target) && typeof key === 'number') { 52 | target.length = Math.max(target.length, key); 53 | target.splice(key, 1, val); 54 | return val; 55 | } 56 | if (hasOwn(target, key)) { 57 | target[key] = val; 58 | return val; 59 | } 60 | let ob = (target).__ob__; 61 | if (!ob) { 62 | target[key] = val; 63 | return val; 64 | } 65 | defineReactive$$1(ob.value, key, val); 66 | ob.dep.notify(); 67 | return val; 68 | } 69 | function del (target, key) { 70 | if (Array.isArray(target) && typeof key === 'number') { 71 | target.splice(key, 1); 72 | return; 73 | } 74 | let ob = (target).__ob__; 75 | if (!hasOwn(target, key)) { 76 | return; 77 | } 78 | delete target[key]; 79 | if (!ob) { 80 | return; 81 | } 82 | ob.dep.notify(); 83 | } 84 | // observe array 85 | let arrayProto = Array.prototype; 86 | let arrayMethods = Object.create(arrayProto); 87 | [ 88 | 'push', 89 | 'pop', 90 | 'shift', 91 | 'unshift', 92 | 'splice', 93 | 'sort', 94 | 'reverse' 95 | ].forEach(method => { 96 | // 原始数组操作方法 97 | let original = arrayMethods[method]; 98 | def(arrayMethods, method, function () { 99 | let arguments$1 = arguments; 100 | let i = arguments.length; 101 | let args = new Array(i); 102 | 103 | while (i--) { 104 | args[i] = arguments$1[i] 105 | } 106 | // 执行数组方法 107 | let result = original.apply(this, args); 108 | // 因 arrayMethods 是为了作为 Observer 中的 value 的原型或者直接作为属性,所以此处的 this 一般就是指向 Observer 中的 value 109 | // 当然,还需要修改 Observer,使得其中的 value 有一个指向 Observer 自身的属性,__ob__,以此将两者关联起来 110 | let ob = this.__ob__; 111 | // 存放新增数组元素 112 | let inserted; 113 | // 为add 进arry中的元素进行observe 114 | switch (method) { 115 | case 'push': 116 | inserted = args; 117 | break; 118 | case 'unshift': 119 | inserted = args; 120 | break; 121 | case 'splice': 122 | // 第三个参数开始才是新增元素 123 | inserted = args.slice(2); 124 | break; 125 | } 126 | if (inserted) { 127 | ob.observeArray(inserted); 128 | } 129 | // 通知数组变化 130 | ob.dep.notify(); 131 | // 返回新数组长度 132 | return result; 133 | }) 134 | 135 | }) 136 | // arrayMethods所有的枚举属性名 137 | const arrayKeys = Object.getOwnPropertyNames(arrayMethods); 138 | // 判断当前环境是否可以使用 __proto__ 139 | const hasProto = '__proto__' in {}; 140 | console.log(arrayMethods) 141 | // 直接将对象的 proto 指向 src这一组方法 142 | function protoAugment (target, src) { 143 | target.__proto__ = src; 144 | } 145 | 146 | // 遍历这一组方法,依次添加到对象中,作为隐藏属性(即 enumerable: false,不能被枚举) 147 | function copyAugment (target, src, keys) { 148 | for (let i = 0, l = keys.length; i < l; i++) { 149 | let key = keys[i]; 150 | def(target, key, src[key]); 151 | } 152 | } 153 | // 返回一个布尔值,指示对象是否具有指定的属性作为自身(不继承)属性 154 | function hasOwn (obj, key) { 155 | return hasOwnProperty.call(obj, key) 156 | } 157 | /** 158 | * @class 发布类 Observer that are attached to each observed 159 | * @param {[type]} value [vm参数] 160 | */ 161 | function Observer(value) { 162 | this.value = value; 163 | this.dep = new Dep(); 164 | def(value, '__ob__', this); 165 | if (Array.isArray(value)) { 166 | let augment = hasProto 167 | ? protoAugment 168 | : copyAugment; 169 | augment(value, arrayMethods, arrayKeys); 170 | this.observeArray(value); 171 | } else { 172 | this.walk(value); 173 | } 174 | } 175 | 176 | Observer.prototype = { 177 | walk: function (obj) { 178 | let self = this; 179 | Object.keys(obj).forEach(key => { 180 | defineReactive$$1(obj, key, obj[key]); 181 | }); 182 | }, 183 | observeArray: function (items) { 184 | for (let i = 0, l = items.length; i < l; i++) { 185 | observe(items[i]); 186 | } 187 | } 188 | } 189 | 190 | function defineReactive$$1 (obj, key, val) { 191 | let dep = new Dep(); 192 | let childOb = observe(val); 193 | Object.defineProperty(obj, key, { 194 | enumerable: true, 195 | configurable: true, 196 | get: function() { 197 | if (Dep.target) { 198 | dep.depend(); 199 | if (childOb) { 200 | childOb.dep.depend(); 201 | } 202 | } 203 | 204 | return val; 205 | }, 206 | set: function(newVal) { 207 | if (val === newVal || (newVal !== newVal && val !== val)) { 208 | return; 209 | } 210 | val = newVal; 211 | // 监听子属性 212 | childOb = observe(newVal); 213 | // 通知数据变更 214 | dep.notify(); 215 | } 216 | }) 217 | } 218 | 219 | function observe(value, asRootData) { 220 | if (!value || typeof value !== 'object') { 221 | return; 222 | } 223 | let ob; 224 | if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { 225 | ob = value.__ob__; 226 | } else { 227 | ob = new Observer(value); 228 | } 229 | return ob 230 | } 231 | 232 | /** 233 | * @class 依赖类 Dep 234 | */ 235 | let uid = 0; 236 | function Dep() { 237 | // dep id 238 | this.id = uid++; 239 | // array 存储Watcher 240 | this.subs = []; 241 | } 242 | Dep.target = null; 243 | Dep.prototype = { 244 | /** 245 | * [添加订阅者] 246 | * @param {[Watcher]} sub [订阅者] 247 | */ 248 | addSub: function (sub) { 249 | this.subs.push(sub); 250 | }, 251 | /** 252 | * [移除订阅者] 253 | * @param {[Watcher]} sub [订阅者] 254 | */ 255 | removeSub: function (sub) { 256 | let index = this.subs.indexOf(sub); 257 | if (index !== -1) { 258 | this.subs.splice(index ,1); 259 | } 260 | }, 261 | // 通知数据变更 262 | notify: function () { 263 | console.log('notify'); 264 | this.subs.forEach(sub => { 265 | // 执行sub的update更新函数 266 | sub.update(); 267 | }); 268 | }, 269 | // add Watcher 270 | depend: function () { 271 | Dep.target.addDep(this); 272 | } 273 | } 274 | /** 275 | * Watcher.prototype = { 276 | * get: function () { 277 | * Dep.target = this; 278 | * let value = this.getter.call(this.vm, this.vm); 279 | * Dep.target = null; 280 | * return value; 281 | * }, 282 | * addDep: function (dep) { 283 | * dep.addSub(this); 284 | * } 285 | * } 286 | */ 287 | /** 288 | * @class 指令解析类 Compile 289 | * @param {[type]} el [element节点] 290 | * @param {[type]} vm [mvvm实例] 291 | */ 292 | function Compile(el, vm) { 293 | this.$vm = vm; 294 | this.$el = this.isElementNode(el) ? el : document.querySelector(el); 295 | 296 | if (this.$el) { 297 | this.$fragment = this.nodeFragment(this.$el); 298 | this.compileElement(this.$fragment); 299 | // 将文档碎片放回真实dom 300 | this.$el.appendChild(this.$fragment) 301 | } 302 | } 303 | Compile.prototype = { 304 | compileElement: function (el) { 305 | let self = this; 306 | let childNodes = el.childNodes; 307 | [].slice.call(childNodes).forEach(node => { 308 | let text = node.textContent; 309 | let reg = /\{\{((?:.|\n)+?)\}\}/; 310 | 311 | // 如果是element节点 312 | if (self.isElementNode(node)) { 313 | self.compile(node); 314 | } 315 | // 如果是text节点 316 | else if (self.isTextNode(node) && reg.test(text)) { 317 | // 匹配第一个选项 318 | self.compileText(node, RegExp.$1); 319 | } 320 | // 解析子节点包含的指令 321 | if (node.childNodes && node.childNodes.length) { 322 | self.compileElement(node); 323 | } 324 | }); 325 | }, 326 | // 文档碎片,遍历过程中会有多次的dom操作,为提高性能我们会将el节点转化为fragment文档碎片进行解析操作 327 | // 解析操作完成,将其添加回真实dom节点中 328 | nodeFragment: function (el) { 329 | let fragment = document.createDocumentFragment(); 330 | let child; 331 | 332 | while (child = el.firstChild) { 333 | fragment.appendChild(child); 334 | } 335 | return fragment; 336 | }, 337 | // 指令解析 338 | compile: function (node) { 339 | let nodeAttrs = node.attributes; 340 | let self = this; 341 | 342 | [].slice.call(nodeAttrs).forEach(attr => { 343 | let attrName = attr.name; 344 | if (self.isDirective(attrName)) { 345 | let exp = attr.value; 346 | let dir = attrName.substring(2); 347 | // 事件指令 348 | if (self.isEventDirective(dir)) { 349 | compileUtil.eventHandler(node, self.$vm, exp, dir); 350 | } 351 | // 普通指令 352 | else { 353 | compileUtil[dir] && compileUtil[dir](node, self.$vm, exp); 354 | } 355 | 356 | node.removeAttribute(attrName); 357 | } 358 | }); 359 | }, 360 | // {{ test }} 匹配变量 test 361 | compileText: function (node, exp) { 362 | compileUtil.text(node, this.$vm, exp); 363 | }, 364 | // element节点 365 | isElementNode: function (node) { 366 | return node.nodeType === 1; 367 | }, 368 | // text纯文本 369 | isTextNode: function (node) { 370 | return node.nodeType === 3 371 | }, 372 | // x-XXX指令判定 373 | isDirective: function (attr) { 374 | return attr.indexOf('x-') === 0; 375 | }, 376 | // 事件指令判定 377 | isEventDirective: function (dir) { 378 | return dir.indexOf('on') === 0; 379 | } 380 | } 381 | // 定义$elm,缓存当前执行input事件的input dom对象 382 | let $elm; 383 | let timer = null; 384 | // 指令处理集合 385 | const compileUtil = { 386 | html: function (node, vm, exp) { 387 | this.bind(node, vm, exp, 'html'); 388 | }, 389 | text: function (node, vm, exp) { 390 | this.bind(node, vm, exp, 'text'); 391 | }, 392 | class: function (node, vm, exp) { 393 | this.bind(node, vm, exp, 'class'); 394 | }, 395 | model: function(node, vm, exp) { 396 | this.bind(node, vm, exp, 'model'); 397 | 398 | let self = this; 399 | let val = this._getVmVal(vm, exp); 400 | // 监听input事件 401 | node.addEventListener('input', function (e) { 402 | let newVal = e.target.value; 403 | $elm = e.target; 404 | if (val === newVal) { 405 | return; 406 | } 407 | // 设置定时器 完成ui js的异步渲染 408 | clearTimeout(timer); 409 | timer = setTimeout(function () { 410 | self._setVmVal(vm, exp, newVal); 411 | val = newVal; 412 | }) 413 | }); 414 | }, 415 | bind: function (node, vm, exp, dir) { 416 | let updaterFn = updater[dir + 'Updater']; 417 | 418 | updaterFn && updaterFn(node, this._getVmVal(vm, exp)); 419 | 420 | new Watcher(vm, exp, function(value, oldValue) { 421 | updaterFn && updaterFn(node, value, oldValue); 422 | }); 423 | }, 424 | // 事件处理 425 | eventHandler: function(node, vm, exp, dir) { 426 | let eventType = dir.split(':')[1]; 427 | let fn = vm.$options.methods && vm.$options.methods[exp]; 428 | 429 | if (eventType && fn) { 430 | node.addEventListener(eventType, fn.bind(vm), false); 431 | } 432 | }, 433 | /** 434 | * [获取挂载在vm实例上的value] 435 | * @param {[type]} vm [mvvm实例] 436 | * @param {[type]} exp [expression] 437 | */ 438 | _getVmVal: function (vm, exp) { 439 | let val = vm; 440 | exp = exp.split('.'); 441 | exp.forEach(key => { 442 | key = key.trim(); 443 | val = val[key]; 444 | }); 445 | return val; 446 | }, 447 | /** 448 | * [设置挂载在vm实例上的value值] 449 | * @param {[type]} vm [mvvm实例] 450 | * @param {[type]} exp [expression] 451 | * @param {[type]} value [新值] 452 | */ 453 | _setVmVal: function (vm, exp, value) { 454 | let val = vm; 455 | exps = exp.split('.'); 456 | exps.forEach((key, index) => { 457 | key = key.trim(); 458 | if (index < exps.length - 1) { 459 | val = val[key]; 460 | } 461 | else { 462 | val[key] = value; 463 | } 464 | }); 465 | } 466 | } 467 | // 指令渲染集合 468 | const updater = { 469 | htmlUpdater: function (node, value) { 470 | node.innerHTML = typeof value === 'undefined' ? '' : value; 471 | }, 472 | textUpdater: function (node, value) { 473 | node.textContent = typeof value === 'undefined' ? '' : value; 474 | }, 475 | classUpdater: function () {}, 476 | modelUpdater: function (node, value, oldValue) { 477 | if ($elm === node) { 478 | return false; 479 | } 480 | $elm = undefined; 481 | node.value = typeof value === 'undefined' ? '' : value; 482 | } 483 | } 484 | 485 | /** 486 | * @class 观察类 487 | * @param {[type]} vm [vm对象] 488 | * @param {[type]} expOrFn [属性表达式] 489 | * @param {Function} cb [回调函数(一半用来做view动态更新)] 490 | */ 491 | function Watcher(vm, expOrFn, cb) { 492 | this.vm = vm; 493 | expOrFn = expOrFn.trim(); 494 | this.expOrFn = expOrFn; 495 | this.cb = cb; 496 | this.depIds = {}; 497 | 498 | if (typeof expOrFn === 'function') { 499 | this.getter = expOrFn 500 | } 501 | else { 502 | this.getter = this.parseGetter(expOrFn); 503 | } 504 | this.value = this.get(); 505 | } 506 | Watcher.prototype = { 507 | update: function () { 508 | this.run(); 509 | }, 510 | run: function () { 511 | let newVal = this.get(); 512 | let oldVal = this.value; 513 | if (newVal === oldVal) { 514 | return; 515 | } 516 | this.value = newVal; 517 | // 将newVal, oldVal挂载到MVVM实例上 518 | this.cb.call(this.vm, newVal, oldVal); 519 | }, 520 | get: function () { 521 | Dep.target = this; // 将当前订阅者指向自己 522 | let value = this.getter.call(this.vm, this.vm); // 触发getter,将自身添加到dep中 523 | Dep.target = null; // 添加完成 重置 524 | return value; 525 | }, 526 | // 添加Watcher to Dep.subs[] 527 | addDep: function (dep) { 528 | if (!this.depIds.hasOwnProperty(dep.id)) { 529 | dep.addSub(this); 530 | this.depIds[dep.id] = dep; 531 | } 532 | }, 533 | parseGetter: function (exp) { 534 | if (/[^\w.$]/.test(exp)) return; 535 | 536 | let exps = exp.split('.'); 537 | 538 | // 简易的循环依赖处理 539 | return function(obj) { 540 | for (let i = 0, len = exps.length; i < len; i++) { 541 | if (!obj) return; 542 | obj = obj[exps[i]]; 543 | } 544 | return obj; 545 | } 546 | } 547 | } 548 | // trim() 对于各浏览器兼容的处理 549 | if (!String.prototype.trim) { 550 | String.prototype.trim = function () { 551 | return this.replace(/(^\s+)|(\s+$)/g,""); 552 | }; 553 | } 554 | -------------------------------------------------------------------------------- /src/my-mvvm/README.md: -------------------------------------------------------------------------------- 1 | # 实现一个属于我们自己的简易MVVM库 2 | 3 | ## 前言 4 | 本文所有代码都已经push到本人github个人仓库overwrite->my-mvvm 5 | 6 | 7 | 我们知道的,常见的数据绑定的实现方法 8 | 9 | + 数据劫持(vue):通过[`Object.defineProperty()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) 去劫持数据每个属性对应的`getter`和`setter` 10 | + 脏值检测(angular):通过特定事件比如`input`,`change`,`xhr请求`等进行脏值检测。 11 | + 发布-订阅模式(backbone):通过发布消息,订阅消息进行数据和视图的绑定监听。[my-observer](https://github.com/xuqiang521/overwrite/tree/master/my-observer) 12 | 13 | 一言不合先上代码和效果图吧
14 | 15 | `code` 16 | ```html 17 | 18 | 19 | 20 | 21 | 22 | 23 | example 24 | 25 | 26 | 27 |
28 |

{{b}}

29 | 30 | 31 |

{{ a }}

32 | 33 |
34 | 35 | 51 | 52 | ``` 53 | `效果图` 54 | 55 | ![](https://static.oschina.net/uploads/img/201705/21181809_ilWY.gif) 56 | 57 | 看完效果图之后,接下来我们直接搞事情吧 58 | 59 | ## 一、总体大纲 60 | 要实现一个我们自己的mvvm库,我们首先需要做的事情不是写代码,而是整理一下思路,捋清楚之后再动手绝对会让你事半功倍。先上流程图,我们对着流程图来捋思路 61 | >![](https://static.oschina.net/uploads/space/2017/0521/144435_clYy_2912341.png) 62 | 63 | 如上图所示,我们可以看到,整体实现分为四步 64 | 65 | 1. 实现一个`Observer`,对数据进行劫持,通知数据的变化 66 | 2. 实现一个`Compile`,对指令进行解析,初始化视图,并且订阅数据的变更,绑定好更新函数 67 | 3. 实现一个`Watcher`,将其作为以上两者的一个中介点,在接收数据变更的同时,让`Dep`添加当前`Watcher`,并及时通知视图进行`update` 68 | 4. 实现`MVVM`,整合以上三者,作为一个入口函数 69 | 70 | ## 二、动手时间 71 | 72 | 思路捋清楚了,接下来要做的事就是开始动手。 73 | 74 | ![](https://static.oschina.net/uploads/space/2017/0521/145702_pliL_2912341.png) 能动手的我决不动口 75 | 76 | ### 1、实现Observer 77 | 78 | 这里我们需要做的事情就是实现数据劫持,并将数据变更给传递下去。那么这里将会用到的方法就是`Object.defineProperty()`来做这么一件事。先不管三七二十一,咱先用用`Object.defineProperty()`试试手感。 79 | 80 | ```javascript 81 | function observe (data) { 82 | if (!data || typeof data !== 'object') { 83 | return; 84 | } 85 | Object.keys(data).forEach(key => { 86 | observeProperty(data, key, data[key]) 87 | }) 88 | } 89 | function observeProperty (obj, key, val) { 90 | observe(val); 91 | Object.defineProperty(obj, key, { 92 | enumerable: true, // 可枚举 93 | configurable: true, // 可重新定义 94 | get: function () { 95 | return val; 96 | }, 97 | set: function (newVal) { 98 | if (val === newVal || (newVal !== newVal && val !== val)) { 99 | return; 100 | } 101 | console.log('数据更新啦 ', val, '=>', newVal); 102 | val = newVal; 103 | } 104 | }); 105 | } 106 | 107 | ``` 108 | 调用 109 | ```javascript 110 | var data = { 111 | a: 'hello' 112 | } 113 | observe(data); 114 | ``` 115 | 效果如下 116 | 117 | ![](https://static.oschina.net/uploads/space/2017/0521/152411_aqgZ_2912341.png) 118 | 119 | 看完是不是发现`JavaScript`提供给我们的`Object.defineProperty()`方法功能巨强大巨好用呢。 120 | 121 | ![](https://static.oschina.net/uploads/space/2017/0521/152643_sRt0_2912341.png) 122 | 123 | 其实到这,我们已经算是完成了数据劫持,完整的`Observer`则需要将数据的变更传递给`Dep`实例,然后接下来的事情就丢给`Dep`去通知下面完成接下来的事情了,完整代码如下所示 124 | ```javascript 125 | /** 126 | * @class 发布类 Observer that are attached to each observed 127 | * @param {[type]} value [vm参数] 128 | */ 129 | function observe(value, asRootData) { 130 | if (!value || typeof value !== 'object') { 131 | return; 132 | } 133 | return new Observer(value); 134 | } 135 | 136 | function Observer(value) { 137 | this.value = value; 138 | this.walk(value); 139 | } 140 | 141 | Observer.prototype = { 142 | walk: function (obj) { 143 | let self = this; 144 | Object.keys(obj).forEach(key => { 145 | self.observeProperty(obj, key, obj[key]); 146 | }); 147 | }, 148 | observeProperty: function (obj, key, val) { 149 | let dep = new Dep(); 150 | let childOb = observe(val); 151 | Object.defineProperty(obj, key, { 152 | enumerable: true, 153 | configurable: true, 154 | get: function() { 155 | if (Dep.target) { 156 | dep.depend(); 157 | } 158 | if (childOb) { 159 | childOb.dep.depend(); 160 | } 161 | return val; 162 | }, 163 | set: function(newVal) { 164 | if (val === newVal || (newVal !== newVal && val !== val)) { 165 | return; 166 | } 167 | val = newVal; 168 | // 监听子属性 169 | childOb = observe(newVal); 170 | // 通知数据变更 171 | dep.notify(); 172 | } 173 | }) 174 | } 175 | } 176 | /** 177 | * @class 依赖类 Dep 178 | */ 179 | let uid = 0; 180 | function Dep() { 181 | // dep id 182 | this.id = uid++; 183 | // array 存储Watcher 184 | this.subs = []; 185 | } 186 | Dep.target = null; 187 | Dep.prototype = { 188 | /** 189 | * [添加订阅者] 190 | * @param {[Watcher]} sub [订阅者] 191 | */ 192 | addSub: function (sub) { 193 | this.subs.push(sub); 194 | }, 195 | /** 196 | * [移除订阅者] 197 | * @param {[Watcher]} sub [订阅者] 198 | */ 199 | removeSub: function (sub) { 200 | let index = this.subs.indexOf(sub); 201 | if (index !== -1) { 202 | this.subs.splice(index ,1); 203 | } 204 | }, 205 | // 通知数据变更 206 | notify: function () { 207 | this.subs.forEach(sub => { 208 | // 执行sub的update更新函数 209 | sub.update(); 210 | }); 211 | }, 212 | // add Watcher 213 | depend: function () { 214 | Dep.target.addDep(this); 215 | } 216 | } 217 | // 结合Watcher 218 | /** 219 | * Watcher.prototype = { 220 | * get: function () { 221 | * Dep.target = this; 222 | * let value = this.getter.call(this.vm, this.vm); 223 | * Dep.target = null; 224 | * return value; 225 | * }, 226 | * addDep: function (dep) { 227 | * dep.addSub(this); 228 | * } 229 | * } 230 | */ 231 | ``` 232 | 至此,我们已经实现了数据的劫持以及notify数据变化的功能了。 233 | 234 | ### 2、实现Compile 235 | 236 | 按理说我们应该紧接着实现`Watcher`,毕竟从上面代码看来,`Observer`和`Watcher`关联好多啊,但是,我们在捋思路的时候也应该知道了,`Watcher`和`Compile`也是有一腿的哦。所以咱先把`Compile`也给实现了,这样才能更好的让他们3P。 237 | 238 | ![](https://static.oschina.net/uploads/space/2017/0521/155557_8nge_2912341.png) 239 | 我不是老司机,我只是一个纯洁的开电动车的孩子😏 240 | 241 | 废话不多说,干实事。 242 | 243 | `Compile`需要做的事情也很简单 244 | 1. 解析指令,将指令模板中的变量替换成数据,对视图进行初始化操作 245 | 2. 订阅数据的变化,绑定好更新函数 246 | 3. 接收到数据变化,通知视图进行view update 247 | 248 | 咱先试着写一个简单的指令解析方法,实现解析指令初始化视图。 249 | 250 | `js部分` 251 | ```javascript 252 | function Compile (el, value) { 253 | this.$val = value; 254 | this.$el = this.isElementNode(el) ? el : document.querySelector(el); 255 | if (this.$el) { 256 | this.compileElement(this.$el); 257 | } 258 | } 259 | Compile.prototype = { 260 | compileElement: function (el) { 261 | let self = this; 262 | let childNodes = el.childNodes; 263 | [].slice.call(childNodes).forEach(node => { 264 | let text = node.textContent; 265 | let reg = /\{\{((?:.|\n)+?)\}\}/; 266 | // 如果是element节点 267 | if (self.isElementNode(node)) { 268 | self.compile(node); 269 | } 270 | // 如果是text节点 271 | else if (self.isTextNode(node) && reg.test(text)) { 272 | // 匹配第一个选项 273 | self.compileText(node, RegExp.$1.trim()); 274 | } 275 | // 解析子节点包含的指令 276 | if (node.childNodes && node.childNodes.length) { 277 | self.compileElement(node); 278 | } 279 | }) 280 | }, 281 | // 指令解析 282 | compile: function (node) { 283 | let nodeAttrs = node.attributes; 284 | let self = this; 285 | 286 | [].slice.call(nodeAttrs).forEach(attr => { 287 | var attrName = attr.name; 288 | if (self.isDirective(attrName)) { 289 | var exp = attr.value; 290 | node.innerHTML = typeof this.$val[exp] === 'undefined' ? '' : this.$val[exp]; 291 | node.removeAttribute(attrName); 292 | } 293 | }); 294 | }, 295 | // {{ test }} 匹配变量 test 296 | compileText: function (node, exp) { 297 | node.textContent = typeof this.$val[exp] === 'undefined' ? '' : this.$val[exp]; 298 | }, 299 | // element节点 300 | isElementNode: function (node) { 301 | return node.nodeType === 1; 302 | }, 303 | // text纯文本 304 | isTextNode: function (node) { 305 | return node.nodeType === 3 306 | }, 307 | // x-XXX指令判定 308 | isDirective: function (attr) { 309 | return attr.indexOf('x-') === 0; 310 | } 311 | } 312 | ``` 313 | `html部分` 314 | ```html 315 | 316 |
317 |

318 |

{{ a }}

319 |
320 | 321 | 327 | ``` 328 | 结果如图所示 329 | 330 | ![](https://static.oschina.net/uploads/space/2017/0521/170051_KmG2_2912341.png) 331 | 332 |

  按照步骤走的我已经实现了指令解析!
333 | 334 | 这里我们只是实现了指令的解析以及视图的初始化,并没有实现数据变化的订阅以及视图的更新。完整的Compile则实现了这些功能,详细代码如下 335 | 336 | ```javascript 337 | /** 338 | * @class 指令解析类 Compile 339 | * @param {[type]} el [element节点] 340 | * @param {[type]} vm [mvvm实例] 341 | */ 342 | function Compile(el, vm) { 343 | this.$vm = vm; 344 | this.$el = this.isElementNode(el) ? el : document.querySelector(el); 345 | 346 | if (this.$el) { 347 | this.$fragment = this.nodeFragment(this.$el); 348 | this.compileElement(this.$fragment); 349 | // 将文档碎片放回真实dom 350 | this.$el.appendChild(this.$fragment) 351 | } 352 | } 353 | Compile.prototype = { 354 | compileElement: function (el) { 355 | let self = this; 356 | let childNodes = el.childNodes; 357 | [].slice.call(childNodes).forEach(node => { 358 | let text = node.textContent; 359 | let reg = /\{\{((?:.|\n)+?)\}\}/; 360 | 361 | // 如果是element节点 362 | if (self.isElementNode(node)) { 363 | self.compile(node); 364 | } 365 | // 如果是text节点 366 | else if (self.isTextNode(node) && reg.test(text)) { 367 | // 匹配第一个选项 368 | self.compileText(node, RegExp.$1); 369 | } 370 | // 解析子节点包含的指令 371 | if (node.childNodes && node.childNodes.length) { 372 | self.compileElement(node); 373 | } 374 | }); 375 | }, 376 | // 文档碎片,遍历过程中会有多次的dom操作,为提高性能我们会将el节点转化为fragment文档碎片进行解析操作 377 | // 解析操作完成,将其添加回真实dom节点中 378 | nodeFragment: function (el) { 379 | let fragment = document.createDocumentFragment(); 380 | let child; 381 | 382 | while (child = el.firstChild) { 383 | fragment.appendChild(child); 384 | } 385 | return fragment; 386 | }, 387 | // 指令解析 388 | compile: function (node) { 389 | let nodeAttrs = node.attributes; 390 | let self = this; 391 | 392 | [].slice.call(nodeAttrs).forEach(attr => { 393 | var attrName = attr.name; 394 | if (self.isDirective(attrName)) { 395 | var exp = attr.value; 396 | var dir = attrName.substring(2); 397 | // 事件指令 398 | if (self.isEventDirective(dir)) { 399 | compileUtil.eventHandler(node, self.$vm, exp, dir); 400 | } 401 | // 普通指令 402 | else { 403 | compileUtil[dir] && compileUtil[dir](node, self.$vm, exp); 404 | } 405 | 406 | node.removeAttribute(attrName); 407 | } 408 | }); 409 | }, 410 | // {{ test }} 匹配变量 test 411 | compileText: function (node, exp) { 412 | compileUtil.text(node, this.$vm, exp); 413 | }, 414 | // element节点 415 | isElementNode: function (node) { 416 | return node.nodeType === 1; 417 | }, 418 | // text纯文本 419 | isTextNode: function (node) { 420 | return node.nodeType === 3 421 | }, 422 | // x-XXX指令判定 423 | isDirective: function (attr) { 424 | return attr.indexOf('x-') === 0; 425 | }, 426 | // 事件指令判定 427 | isEventDirective: function (dir) { 428 | return dir.indexOf('on') === 0; 429 | } 430 | } 431 | // 定义$elm,缓存当前执行input事件的input dom对象 432 | let $elm; 433 | let timer = null; 434 | // 指令处理集合 435 | const compileUtil = { 436 | html: function (node, vm, exp) { 437 | this.bind(node, vm, exp, 'html'); 438 | }, 439 | text: function (node, vm, exp) { 440 | this.bind(node, vm, exp, 'text'); 441 | }, 442 | class: function (node, vm, exp) { 443 | this.bind(node, vm, exp, 'class'); 444 | }, 445 | model: function(node, vm, exp) { 446 | this.bind(node, vm, exp, 'model'); 447 | 448 | let self = this; 449 | let val = this._getVmVal(vm, exp); 450 | // 监听input事件 451 | node.addEventListener('input', function (e) { 452 | let newVal = e.target.value; 453 | $elm = e.target; 454 | if (val === newVal) { 455 | return; 456 | } 457 | // 设置定时器 完成ui js的异步渲染 458 | clearTimeout(timer); 459 | timer = setTimeout(function () { 460 | self._setVmVal(vm, exp, newVal); 461 | val = newVal; 462 | }) 463 | }); 464 | }, 465 | bind: function (node, vm, exp, dir) { 466 | let updaterFn = updater[dir + 'Updater']; 467 | 468 | updaterFn && updaterFn(node, this._getVmVal(vm, exp)); 469 | 470 | new Watcher(vm, exp, function(value, oldValue) { 471 | updaterFn && updaterFn(node, value, oldValue); 472 | }); 473 | }, 474 | // 事件处理 475 | eventHandler: function(node, vm, exp, dir) { 476 | let eventType = dir.split(':')[1]; 477 | let fn = vm.$options.methods && vm.$options.methods[exp]; 478 | 479 | if (eventType && fn) { 480 | node.addEventListener(eventType, fn.bind(vm), false); 481 | } 482 | }, 483 | /** 484 | * [获取挂载在vm实例上的value] 485 | * @param {[type]} vm [mvvm实例] 486 | * @param {[type]} exp [expression] 487 | */ 488 | _getVmVal: function (vm, exp) { 489 | let val = vm; 490 | exp = exp.split('.'); 491 | exp.forEach(key => { 492 | key = key.trim(); 493 | val = val[key]; 494 | }); 495 | return val; 496 | }, 497 | /** 498 | * [设置挂载在vm实例上的value值] 499 | * @param {[type]} vm [mvvm实例] 500 | * @param {[type]} exp [expression] 501 | * @param {[type]} value [新值] 502 | */ 503 | _setVmVal: function (vm, exp, value) { 504 | let val = vm; 505 | exps = exp.split('.'); 506 | exps.forEach((key, index) => { 507 | key = key.trim(); 508 | if (index < exps.length - 1) { 509 | val = val[key]; 510 | } 511 | else { 512 | val[key] = value; 513 | } 514 | }); 515 | } 516 | } 517 | // 指令渲染集合 518 | const updater = { 519 | htmlUpdater: function (node, value) { 520 | node.innerHTML = typeof value === 'undefined' ? '' : value; 521 | }, 522 | textUpdater: function (node, value) { 523 | node.textContent = typeof value === 'undefined' ? '' : value; 524 | }, 525 | classUpdater: function () {}, 526 | modelUpdater: function (node, value, oldValue) { 527 | // 不对当前操作input进行渲染操作 528 | if ($elm === node) { 529 | return false; 530 | } 531 | $elm = undefined; 532 | node.value = typeof value === 'undefined' ? '' : value; 533 | } 534 | } 535 | ``` 536 | ![](https://static.oschina.net/uploads/space/2017/0521/171502_2qPh_2912341.png) 537 | 538 | 好了,到这里两个和Watcher相关的“菇凉”已经出场了 539 | 540 | ### 3、实现Watcher 541 | 作为一个和Observer和Compile都有关系的“蓝银”,他做的事情有以下几点 542 | 543 | * 通过Dep接收数据变动的通知,实例化的时候将自己添加到dep中 544 | * 属性变更时,接收dep的notify,调用自身update方法,触发Compile中绑定的更新函数,进而更新视图 545 | 546 | 这里的代码比较简短,所以我决定直接上代码 547 | ```javascript 548 | /** 549 | * @class 观察类 550 | * @param {[type]} vm [vm对象] 551 | * @param {[type]} expOrFn [属性表达式] 552 | * @param {Function} cb [回调函数(一半用来做view动态更新)] 553 | */ 554 | function Watcher(vm, expOrFn, cb) { 555 | this.vm = vm; 556 | expOrFn = expOrFn.trim(); 557 | this.expOrFn = expOrFn; 558 | this.cb = cb; 559 | this.depIds = {}; 560 | 561 | if (typeof expOrFn === 'function') { 562 | this.getter = expOrFn 563 | } 564 | else { 565 | this.getter = this.parseGetter(expOrFn); 566 | } 567 | this.value = this.get(); 568 | } 569 | Watcher.prototype = { 570 | update: function () { 571 | this.run(); 572 | }, 573 | run: function () { 574 | let newVal = this.get(); 575 | let oldVal = this.value; 576 | if (newVal === oldVal) { 577 | return; 578 | } 579 | this.value = newVal; 580 | // 将newVal, oldVal挂载到MVVM实例上 581 | this.cb.call(this.vm, newVal, oldVal); 582 | }, 583 | get: function () { 584 | Dep.target = this; // 将当前订阅者指向自己 585 | let value = this.getter.call(this.vm, this.vm); // 触发getter,将自身添加到dep中 586 | Dep.target = null; // 添加完成 重置 587 | return value; 588 | }, 589 | // 添加Watcher to Dep.subs[] 590 | addDep: function (dep) { 591 | if (!this.depIds.hasOwnProperty(dep.id)) { 592 | dep.addSub(this); 593 | this.depIds[dep.id] = dep; 594 | } 595 | }, 596 | parseGetter: function (exp) { 597 | if (/[^\w.$]/.test(exp)) return; 598 | 599 | let exps = exp.split('.'); 600 | 601 | // 简易的循环依赖处理 602 | return function(obj) { 603 | for (let i = 0, len = exps.length; i < len; i++) { 604 | if (!obj) return; 605 | obj = obj[exps[i]]; 606 | } 607 | return obj; 608 | } 609 | } 610 | } 611 | ``` 612 | ![](https://static.oschina.net/uploads/space/2017/0521/174048_nYlu_2912341.png) 613 | 614 | 没错就是Watcher这么一个简短的“蓝银”却和Observer和Compile两位“菇凉”牵扯不清 615 | 616 | ### 4、实现MVVM 617 | 618 | 可以说MVVM是Observer,Compile以及Watcher的“boss”了,他才不会去管他们员工之间的关系,只要他们三能给干活,并且干好活就行。他需要安排给Observer,Compile以及Watche做的事情如下 619 | 620 | * Observer实现对MVVM自身model数据劫持,监听数据的属性变更,并在变动时进行notify 621 | * Compile实现指令解析,初始化视图,并订阅数据变化,绑定好更新函数 622 | * Watcher一方面接收Observer通过dep传递过来的数据变化,一方面通知Compile进行view update 623 | 624 | 具体实现如下 625 | ```javascript 626 | /** 627 | * @class 双向绑定类 MVVM 628 | * @param {[type]} options [description] 629 | */ 630 | function MVVM (options) { 631 | this.$options = options || {}; 632 | let data = this._data = this.$options.data; 633 | let self = this; 634 | 635 | Object.keys(data).forEach(key => { 636 | self._proxyData(key); 637 | }); 638 | observe(data, this); 639 | new Compile(options.el || document.body, this); 640 | } 641 | MVVM.prototype = { 642 | /** 643 | * [属性代理] 644 | * @param {[type]} key [数据key] 645 | * @param {[type]} setter [属性set] 646 | * @param {[type]} getter [属性get] 647 | */ 648 | _proxyData: function (key, setter, getter) { 649 | let self = this; 650 | setter = setter || 651 | Object.defineProperty(self, key, { 652 | configurable: false, 653 | enumerable: true, 654 | get: function proxyGetter() { 655 | return self._data[key]; 656 | }, 657 | set: function proxySetter(newVal) { 658 | self._data[key] = newVal; 659 | } 660 | }) 661 | } 662 | } 663 | ``` 664 | 至此,一个属于我们自己的`mvvm`库也算是完成了。由于本文的代码较多,又不太好分小部分抽离出来讲解,所以我将代码的解析都直接写到了代码中。文中一些不够严谨的思考和错误,还请各位小伙伴们拍砖指出,大家一起纠正一起学习。 665 | 666 | ## 三、源码链接 667 | 668 | `github`-[https://github.com/xuqiang521/overwrite](https://github.com/xuqiang521/overwrite) 669 | 670 | `码云`-[https://git.oschina.net/qiangdada_129/overwrite](https://git.oschina.net/qiangdada_129/overwrite) 671 | 672 | ![](https://static.oschina.net/uploads/space/2017/0521/180826_KrqV_2912341.png) 673 | 674 | 如果喜欢欢迎各位小伙伴们`star`,`overwrite`将不断更新哦 675 | --------------------------------------------------------------------------------