Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

thief系列之三:从实现链式调用中看类数组对象与级联 #69

Open
youngwind opened this issue Apr 24, 2016 · 0 comments
Open
Labels

Comments

@youngwind
Copy link
Owner

youngwind commented Apr 24, 2016

问题

让我们回顾一下之前我们已经完成的功能。

  1. 获取dom元素
  2. 添加类
  3. 删除类

但是,还存在这样的问题。如图
2016-04-24 6 27 15

跟jquery对比我们就会发现目前我们的代码还有以下问题:

  1. 选择器返回的是一个对象,而jquery返回的是一个数组
  2. jquery支持链式调用,也就是$('h1').addClass('aaa').removeClass('aaa'),但是我们这样用的时候就会报错。

类数组对象

我们先来看第一个问题。
我的代码之前是通过把dom元素存储在this.dom中,this还有addClass、removeClass等方法,this是一个对象。但是,jquery返回的居然是一个数组,而且关键是这个数组还能有addClass这些方法!我觉得这是让我非常疑惑的地方。
我去查看jquery的源码,发现了这个

jQuery.fn = jQuery.prototype = {
    // The default length of a jQuery object is 0
    length: 0,
    splice: arr.splice
};

我很奇怪为什么需要定义一个length属性呢?prototype不是一个对象吗?而且splice方式不是array才有的方法吗?
后来google到这篇文章,让我豁然开朗。http://mao.li/javascript/array-like-objects-in-javascript/

没错,问题的核心就是类数组对象:这是一个对象,但是它具有数组的特性。举个例子。

 var a = {
    id: 1,
    name: "youngwind"
  };
  for (var i = 0; i < a.length; i++) {
    console.log(a[i])
  }

这段代码运行的结果是:没有任何输出。
因为a是一个对象,而且它没有length属性,a.length是undefined,所以循环不会被执行。

我们来改造一下这段代码

var a = {
    0: 1,
    1: "youngwind",
    length: 2,
  };
  for (var i = 0; i < a.length; i++) {
    console.log(a[i])
  }

执行结果:输出1,youngwind
到目前为止依然没什么大问题。因为对象获取属性的值本来就可以通过[]来获取。

我再改一下代码。

var a = {
    0: 1,
    1: "youngwind"
  };
  for (var i = 0; i < a.length; i++) {
    console.log(a[i])
  }
  Array.prototype.push.call(a, 'blabal')
  console.log(a);

程序执行结果:循环没有输出。最后输出{0: "blabal", 1: "youngwind", length: 1}
有没有觉得很奇怪?为什么a明明是一个对象,却可以使用数组的push方法?而且调用之后第0项被重置,还多了一个length属性!而且length属性为毛是1啊!我明明有两个属性啊!

犀牛书上7.11章节给出了答案:
这个一个类数组对象,其实在js中,对象和数组的区别并没有看起来那么大
所谓数组背后的实现也不过是key值为非负整数的对象而已。这就不难解释为什么对象可以使用数组的方法了,虽然使用的结果会出现很怪异的情况,就像上面的例子。

ok,我们就到这儿。不去深究更多的数组和对象的细微区别。现在我想知道的是:
如何让一个对象表现得像数组?也就是说,如果让T('selector')返回的对象在浏览器中被解析成数组,而且我通过数组的各种方法去操作这个对象的时候,不会出现怪异的情况。

jquery的源码和刚刚那篇文章给了我们指引。
再改代码

var a = {
    0: 1,
    1: "youngwind",
    length: 2,
    splice: [].splice
  };
  for (var i = 0; i < a.length; i++) {
    console.log(a[i])
  }

结果如图所示
2016-04-24 7 19 22

ok,这样我们终于可以返回一个类数组了!这也为后面的链式调用做好了准备。

链式调用

所谓链式调用,也就是《js语言精粹》中提到的级联,每次执行完方法都返回this,这样就可以一个接口一个接口的调用下去了。有了上面的准备,this既是一个拥有各种方法的对象,也是一个可以直接遍历的类数组,所以我们可以直接return this作为每个函数的返回了。下面给出代码。

T.prototype = {
    splice: [].splice,
    length: 0,
    init: function (selector) {
      var ele = document.querySelectorAll(selector);
      for (var i = 0; i < ele.length; i++) {
        this[i] = ele[i];
      }
      this.length = ele.length;
      return this;
    },
    addClass: function (className) {
      for (var i = 0; i < this.length; i++) {
        this[i].classList.add(className);
      }
      return this;

    },
    removeClass: function (className) {
      for (var i = 0; i < this.length; i++) {
        this[i].classList.remove(className);
      }
      return this;
    }
  };

效果图
2016-04-24 7 18 38

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant