前端系统课程 - 28. jQuery 不过如此

封装函数

尝试将一个功能,封装为一个函数,利用函数的参数,将数据抽象化后,函数便拥有了更高的适用性。例如来封装一个获取某个节点的所有兄弟节点的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
function getSibilings(node) {
var childs = node.parent.children,
sibilings = {
length: 0
}
for (var i = 0; i < childs.length; i++){
if(childs[i] !== node){
sibilings[length] = childs[i];
sibilings.lengeh++;
}
}
return sibilings;
}

上面的示例中,把要找兄弟节点的那个节点,当作参数传入函数内,然后获取到它的父节点,再获取父节点的所有子元素集合,循环这个集合,排除目标节点后,将其余的保存为一个对象并返回。

命名空间

为了防止全局变量污染造成的麻烦,就要尽量少用全局变量,那么用什么办法能让要全局使用的属性不是全局变量呢?可以使用一个全局变量,把需要统一的全局属性,挂载到这个全局变量中即可,这就是命名空间。

简易 jQuery

  • 在某个对象的原型上增加一个方法,那么这个对象的实例都会拥有这个方法。

  • 修改原型一般是不推荐的,因为你不知道是否有别人是否会修改同名属性;这时可以新添加一个全局属性来补充原型。例如为 Node 对象的某个实例增加方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    window.NodeUp = function (nodeOrSelector) {
    var nodes = {length: 0};
    if (typeof nodeOrSelector === 'string') {
    nodeOrSelector = document.querySelectorAll(nodeOrSelector);
    for (var i = 0; i < nodeOrSelector.length; i++) {
    nodes[i] = nodeOrSelector[i];
    nodes.length++;
    }
    } else if(nodeOrSelector instanceof Node) {
    nodes[0] = nodeOrSelector;
    nodes.length++;
    }

    nodes.addClass = function (classes) {
    var isArr = Array.isArray(classes);
    for (var i = 0; i < nodes.length; i++) {
    if (!isArr) {
    nodes[i].classList.add(classes);
    } else {
    for (var j = 0; j < classes.length; j++) {
    nodes[i].classList.add(classes[j]);
    }
    }
    }
    };

    nodes.setText = function (text) {
    if (text) {
    if (!(typeof text === 'string')) {
    return;
    }
    for (var i = 0; i < nodes.length; i++) {
    nodes[i].textContent = text;
    }
    } else {
    var str;
    for (var j = 0; j < nodes.length; j++) {
    str += nodes[j].textContent;
    }
    return str;
    }
    }

    return nodes;
    };

    window.$ = NodeUp;
  • 上面的简单例子中,如果把 NodeUp 改成 jQuery,可以吗?当然……参数接收一个字符串选择器或者 DOM 对象,就会返回一个新的对象,这个新对象函数中已经被添加了新的属性和方法;当传入某个参数使其实例化后,这个实例对象,就可以使用这些新属性和方法了。

  • 如果简化了代码可能会觉得哪里不对,其实是因为忽略了实例化时传入参数的过程,例如下面的简化例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    window.NodeUp = function (node) {
    return {
    next: function () {
    return node.nextSibling;
    },
    addClass: function (class) {
    node.classList.add(class);
    }
    }
    }

    var nodeUp = NodeUp(testObj);
    nodeUp.addClass('test');

    上面代码中的 NodeUp 返回的是一个拥有两个方法的对象,那么这个对象是包装后的 DOM 对象吗?不是。当 NodeUp 实例化时,不管有没有参数传入,实例化对象都可以使用这两个方法;只不过这两个方法又依赖于传进来的参数,也就是需要一个 DOM 对象,所以实例化后,就看起来好像是传入的这个 DOM 对象拥有了这两个方法,其实并不是,只是通过参数来告诉函数,调用这两个方法的时候,要施加给谁;而前面较为复杂的那段代码,才是真正的把方法添加给了包装后的 DOM 对象。要想证明这点很简单,上个例子中的对象可以剥离出 DOM 对象,而这个例子中的对象是不行的。

  • jQuery 中的 set 和 get 两种类型的方法,大都是根据是否传参来判断执行何种操作的。

课后拾遗

  • jQuery 文件有三种,分别是:

    • 以 .js 结尾:这种是未压缩的文件,又称为“开发版”;这种版本与源码没什么区别,注释、各种参数介绍都很齐全,是在开发的时候方便开发者“追根溯源”的,一旦有需要查看源代码,便可以找到相应的位置和注释。

    • 以 .min.js 结尾:这种是压缩过的文件,又称为“线上版”或“生产版”;这种版本将代码中能缩短的标识符尽量缩短以精简代码,并删除了注释等信息,从而将文件体积压缩到最小,使下载时消耗更少的流量,下载速度更快。

    • 以 .min.map 结尾:这种是索引文件(source map),一般与第一种配合使用,文件中记录了代码转换和压缩前的状态和位置信息,如果压缩后的文件运行时发生了错误,可以依靠这个文件进行排错;这个功能需要浏览器支持。

  • jQuery 对象和 DOM 对象的联系和区别:

    • 联系:说是联系,其实是转换方法。将 DOM 对象传入 jQuery,可以把 DOM 对象变成 jQuery 对象,例如:$(div),此时 jQuery 会把 DOM 对象“包装”(封装)成一个 jQuery 对象;而使用 jQuery 提供的 get() 方法,或直接使用下标,就可以把 jQuery 对象变成 DOM 对象,例如:$div.get(0)$div[0],由于 jQuery 对象是一个伪数组,所以要通过下标 0 来获取第一个元素,即便其中只有一个元素;虽然只有一个元素时 get() 方法不传参也能获取到,但这样获取到的和原对象是不一样的。

    • 区别:其实很简单,就是原型链的不同,所以它们拥有不同的 API,也因此不能混用。

  • 拥有 id 属性的标签,在 JavaScript 中默认成为 window 对象的属性,相当于一个全局变量;也正因为如此,所以 id 的值不要与 window 对象已有的属性重名;这也是尽量不用全局变量的原因之一。