未加星标

揭秘js框架中的常用套路

字体大小 | |
[前端(javascript) 所属分类 前端(javascript) | 发布者 店小二04 | 时间 2018 | 作者 红领巾 ] 0人收藏点击收藏

我们每天都在使用各种各样的框架,这些框架伴随着我们每天的工作。通过使用这些框架的目的是为了解放我们,很少人去真正关心这些框架的背后都做了些什么。我也使用了不少的框架,通过这些流行框架也让我学习到了一些知识,就想把这些东西分享出来。

每个标题都是一个独立的主题,完全可以根据需要挑有兴趣的阅读。

字符串转DOM

经常使用jquery的小伙伴对下面的代码应该一点都不陌生:

var text = $('<div>hello, world</div>'); $('body').append(text)

以上代码执行的结果就是在页面增加了一个div节点。抛开jQuery, 代码可能会变得稍稍复杂:

var strToDom = function(str) { var temp = document.createElement('div'); temp.innerHTML = str; return temp.childNodes[0]; } var text = strToDom('<div>hello, world</div>'); document.querySelector('body').appendChild(text);

运行这段代码,跟使用jQuery的结果是一模一样的,哈哈jQuery也不过如此嘛。如果你这么想你就错了。看下下面两种代码运行的会有什么区别:

var tableTr = $('<tr><td>Simple text</td></tr>'); $('body').append(tableTr); var tableTr = strToDom('<tr><td>Simple text</td></tr>'); document.querySelector('body').appendChild(tableTr);

表面上看没任何的问题,如果用开发者工具看页面结构的话,会有如下发现:


揭秘js框架中的常用套路

strToDom 仅仅创建了一个文本节点,而不是一个真正的tr标签。原因是包含HTML元素的字符串通过解析器在浏览器中运行,解析器忽略了标签没有放置在正确的上下文中,我们只能得到一个文本节点。

jQuery 成功的解决了这个问题,如果深入研究代码的话,会发现下面的Map结构:

var wrapMap = { option: [1, '<select multiple="multiple">', '</select>'], legend: [1, '<fieldset>', '</fieldset>'], area: [1, '<map>', '</map>'], param: [1, '<object>', '</object>'], thead: [1, '<table>', '</table>'], tr: [2, '<table><tbody>', '</tbody></table>'], col: [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'], td: [3, '<table><tbody><tr>', '</tr></tbody></table>'], _default: [1, '<div>', '</div>'] }; wrapMap.optgroup = wrapMap.option; wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; wrapMap.th = wrapMap.td;

每一个元素,需要特殊处理数组分配。这个想法是为了构建正确的DOM元素和依赖的嵌套级别获取我们所需要的东西。例如, tr 元素,我们需要创建一个表 tbody 子。所以,我们有两个级别的嵌套。

有了这个Map映射表后,我们必须拿到我们最终需要的标签。下面代码演示了如何从 <tr><td>hello word</td></tr> 中取到 tr :

var match = /<\s*\w.*?>/g.exec(str); var tag = match[0].replace(/</g, '').replace(/>/g, '');

剩下的就是根据合适的上下文返回DOM元素, 最终我们将 strToDom 进行最终的修改:

var strToDom = function(str) { var wrapMap = { option: [1, '<select multiple="multiple">', '</select>'], legend: [1, '<fieldset>', '</fieldset>'], area: [1, '<map>', '</map>'], param: [1, '<object>', '</object>'], thead: [1, '<table>', '</table>'], tr: [2, '<table><tbody>', '</tbody></table>'], col: [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'], td: [3, '<table><tbody><tr>', '</tr></tbody></table>'], _default: [1, '<div>', '</div>'] }; wrapMap.optgroup = wrapMap.option; wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; wrapMap.th = wrapMap.td; var element = document.createElement('div'); var match = /<\s*\w.*?>/g.exec(str); if(match != null) { var tag = match[0].replace(/</g, '').replace(/>/g, ''); var map = wrapMap[tag] || wrapMap._default, element; str = map[1] + str + map[2]; element.innerHTML = str; // Descend through wrappers to the right content var j = map[0]+1; while(j--) { element = element.lastChild; } } else { // if only text is passed element.innerHTML = str; element = element.lastChild; } return element; }

通过 match != null 判断是创建的标签还是文本节点。这一次我们通过浏览器可以创建一个有效的DOM树。最后通过使用while循环,直到取到我们想要的标签,最后返回这个标签。

AngularJS 依赖注入

当我们开始使用AngularJS时,它的双向数据绑定让人印象深刻。此外另一个神奇特征就是依赖注入。下面是一个简单的例子:

function TodoCtrl($scope, $http) { $http.get('users/users.json').success(function(data) { $scope.users = data; }); } Ember Computed属性

Computed 计算属性是 Vue 中常用的一个功能,但你理解它是怎么工作的吗?先看一个例子:

App.Person = Ember.Object.extend({ firstName: null, lastName: null, fullName: function() { return this.get('firstName') + ' ' + this.get('lastName'); }.property('firstName', 'lastName') }); var ironMan = App.Person.create({ firstName: "Tony", lastName: "Stark" }); ironMan.get('fullName') // "Tony Stark"

Person 对象具有firstName和lastName属性。computed属性fullName返回包含person全名的连接字符串。令人奇怪的地方在于fullName的函数使用了.property方法。 我们看一下 property的代码:

Function.prototype.property = function() { var ret = Ember.computed(this); // ComputedProperty.prototype.property expands properties; no need for us to // do so here. return ret.property.apply(ret, arguments); };

通过添加新属性调整全局函数对象的原型。在类定义期间运行一些逻辑是一种很好的方法。

Ember使用getter和setter来操作对象的数据。这就简化了计算属性的实现,因为我们之前还有一层要处理实际的变量。但是,如果我们能够将计算属性与普通javascript对象一起使用,那就更有趣了。例如:

var User = { firstName: 'Tony', lastName: 'Stark', name: function() { // getter + setter } }; console.log(User.name); // Tony Stark User.name = 'John Doe'; console.log(User.firstName); // John console.log(User.lastName); // Doe

name作为一个常规属性,本质上就是一个获取或设置firstName和lastName的函数。

JavaScript有一个内置的特性,可以帮助我们实现这个想法:

var User = { firstName: 'Tony', lastName: 'Stark' }; Object.defineProperty(User, "name", { get: function() { return this.firstName + ' ' + this.lastName; }, set: function(value) { var parts = value.toString().split(/ /); this.firstName = parts[0]; this.lastName = parts[1] ? parts[1] : this.lastName; } });

Object.defineProperty 方法可以接受对象、对象的属性名、 getter 和 setter 。我们要做的就是编写这两个方法的实现逻辑。运行上面的代码,我们就能得到想要的结果:

console.log(User.name); // Tony Stark User.name = 'John Doe'; console.log(User.firstName); // John console.log(User.lastName); // Doe

Object.defineProperty 虽然是我们想要的,但显然我们不想每次都这么写。在理想的情况下,我们希望提供一个接口。在本节中,我们将编写一个名为 Computize 的函数,它将处理对象并以某种方式将name函数转换为具有相同名称的属性。

var Computize = function(obj) { return obj; } var User = Computize({ firstName: 'Tony', lastName: 'Stark', name: function() { ... } });

我们想使用name方法作为setter,同时作为getter。这类似于Ember的计算属性。

现在,我们将自己的逻辑添加到函数对象的原型中:

Function.prototype.computed = function() { return { computed: true, func: this }; };

这样就可以在每个Function定义后直接调用computed函数了。

name: function() { ... }.computed()

name属性不再是一个函数,而变成一个对象: { computed: true, func: this } 。其中 computed 等于 true , func 属性指向原本的函数。

真正神奇的事情发生在Computize helper的实现中。它遍历对象的所有属性,对所有的计算属性使用object.defineproperty:

var Computize = function(obj) { for(var prop in obj) { if(typeof obj[prop] == 'object' && obj[prop].computed === true) { var func = obj[prop].func; delete obj[prop]; Object.defineProperty(obj, prop, { get: func, set: func }); } } return obj; }

注意: 我们将计算属性name删除了,原因是Object.defineProperty在某些浏览器下仅对未定义的属性起作用。

下面是使用.computed()函数的用户对象的最终版本:

var User = Computize({ firstName: 'Tony', lastName: 'Stark', name: function() { if(arguments.length > 0) { var parts = arguments[0].toString().split(/ /); this.firstName = parts[0]; this.lastName = parts[1] ? parts[1] : this.lastName; } return this.firstName + ' ' + this.lastName; }.computed() }); React Templates

你应该用过大名鼎鼎的 React , 它的思想建立在一切皆是组件的基础之上。组件的定义是什么?看下面的代码:

class Hello extends React.component { render() { return <div>Hello {this.props.name}</div>; } }

本文前端(javascript)相关术语:javascript是什么意思 javascript下载 javascript权威指南 javascript基础教程 javascript 正则表达式 javascript设计模式 javascript高级程序设计 精通javascript javascript教程

tags: gt,lt,var,wrapMap,lastName,table,firstName,tr,tbody,function,name,div,User,computed
分页:12
转载请注明
本文标题:揭秘js框架中的常用套路
本站链接:https://www.codesec.net/view/586426.html


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 前端(javascript) | 评论(0) | 阅读(79)