揭秘js框架中的常用套路

栏目: jQuery · 发布时间: 7年前

内容简介:我们每天都在使用各种各样的框架,这些框架伴随着我们每天的工作。通过使用这些框架的目的是为了解放我们,很少人去真正关心这些框架的背后都做了些什么。我也使用了不少的框架,通过这些流行框架也让我学习到了一些知识,就想把这些东西分享出来。每个标题都是一个独立的主题,完全可以根据需要挑有兴趣的阅读。经常使用jquery的小伙伴对下面的代码应该一点都不陌生:

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

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

字符串转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 方法可以接受对象、对象的属性名、 gettersetter 。我们要做的就是编写这两个方法的实现逻辑。运行上面的代码,我们就能得到想要的结果:

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>;
    }
}

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Mission Python

Mission Python

Sean McManus / No Starch Press / 2018-9-18 / GBP 24.99

Launch into coding with Mission Python, a space-themed guide to building a complete computer game in Python. You'll learn programming fundamentals like loops, strings, and lists as you build Escape!, ......一起来看看 《Mission Python》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

MD5 加密
MD5 加密

MD5 加密工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具