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

这些Zepto中实用的方法集 #5

Open
qianlongo opened this issue Aug 26, 2017 · 0 comments
Open

这些Zepto中实用的方法集 #5

qianlongo opened this issue Aug 26, 2017 · 0 comments

Comments

@qianlongo
Copy link
Owner

qianlongo commented Aug 26, 2017

前言

时间过得可真快,转眼间2017年已去大半有余,你就说吓不吓人,这一年你成长了多少,是否荒度了很多时光,亦或者天天向上,收获满满。今天主要写一些看Zepto基础模块时,比较实用的部分内部方法,在我们日常工作或者学习中也会用的到。

源码仓库
原文链接

1. 将数组铺平(flatten)

面试或者工作中经常遇到要将多维数组铺平成一维数组。例如将[1, 2, [3], [4], [5]]最后变成[1, 2, 3, 4, 5]

function flatten(array) { 
  return array.length > 0 ? $.fn.concat.apply([], array) : array 
}

这里先将$.fn.concat理解成原生数组的concat方法,我们会发现,其实他只能铺平一层。例如

[1, 2, [3], [4, [5]]] => [1, 2, 3, 4, [5]]

那怎样才能将多层嵌套的数组完全铺平为一层呢?这里介绍两种方式。

方式1

let flatten = (array) => {
  return array.reduce((result, val) => {
    return result.concat(Array.isArray(val) ? flatten(val) : val)
  }, [])
}

测试

let testArr1 = [1, 2, 3, 4]
let testArr2 = [1, [2], 3, [4, [5, [6, [7]]]]]

console.log(flatten(testArr1)) // => [1, 2, 3, 4]
console.log(flatten(testArr2)) // => [1, 2, 3, 4, 5, 6, 7]

方式2

let flatten = (array) => {
  let result = []
  let idx = 0

  array.forEach((val, i) => {
    if (Array.isArray(val)) {
      let value = flatten(val)
      let len = value.length
      let j = 0
      result.length += len

      while ( j < len) {
        result[idx++] = value[j++]
      }
    } else {
      result[idx++] = val
    }
  })

  return result
}

同样和上面得到的结果一致

2. 数组去重(uniq)

数组去重可谓是老生常谈的话题了,方式有非常多。好久之前写过一篇关于去重的文章,欢迎查看。

let uniq = function (array) {
  return filter.call(array, function (item, idx) {
    return array.indexOf(item) == idx
  })
}

结合数组的filter方法,查看数组的某项出现的索引是不是与idx相等,不相等,肯定出现过2次以上,即将其过滤掉。其实结合es6中的Set数据结构,可以很方便的做到数组去重。

let uniq = (array) => {
  return  [...new Set(array)]
}

测试

let testArr = [1, 1, 2, 3, 0, -1, -1]
console.log(uniq(testArr)) // => [1, 2, 3, 0, -1]

3. 连字符转驼峰(camelize)

这个方法挺实用的,可以将a-b-c这种形式转换成aBC,当然下划线的数量可以是多个,a---b-----c => aBC

let camelize = function (str) {
  return str.replace(/-+(.)?/g, function (match, chr) {
    return chr ? chr.toUpperCase() : ''
  })
}

4. 判断是否为document对象(isDocument)。

通过dom元素的nodeType属性可以知道其属于哪种元素类型。结合下面这张表(developer.mozilla.org/en-US/docs/Web/API/Node/nodeType),其实不仅仅可以写出判断是否为document对象,还可以判断是否为元素对象等。

function isDocument (obj) {
  return obj != null && obj.nodeType == obj.DOCUMENT_NODE
}

5. 判断obj是否为类数组(likeArray)

什么是类数组对象呢?

类数组对象:

  1. 含有指向对象元素的数字索引下标以及length属性标志属性的个数
  2. 不含有数组的push、concat等方法

常见的类数组对象有auguments,document.getElementsByClassName等api获取的dom集合,符合上述条件的对象等。

function likeArray(obj) {
  // !!obj 直接过滤掉了false,null,undefined,''等值
  // 然后obj必须包含length属性
  var length = !!obj && 'length' in obj && obj.length,
  // 获取obj的数据类型
    type = $.type(obj)
  // 不能是function类型,不能是window
  // 如果是array则直接返回true
  // 或者当length的数据类型是number,并且其取值范围是0到(length - 1)这里是通过判断length - 1 是否为obj的属性
  return 'function' != type && !isWindow(obj) && (
    'array' == type || length === 0 ||
    (typeof length == 'number' && length > 0 && (length - 1) in obj)
  )
}

代码上了注释,主要我们来对比一下underscore中是如何判断是否为类数组的。

var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
var getLength = property('length');
var isArrayLike = function(collection) {
  var length = getLength(collection);
  return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
};

underscore中判断类数组比较宽松一些,MAX_ARRAY_INDEX是JavaScript 中能精确表示的最大数字,主要判断对象的length属性是否为数字类型,并且是否大于0且在MAX_ARRAY_INDEX范围内。

zepto中类数组判断就比较严格了,因为window和函数其实都有length属性,这里把他们给过滤掉了。

6. 判断是否为window对象

window对象的window属性指向其本身,我们来直接看下mdn上的解释。

function isWindow (obj) {
  return obj != null && obj == obj.window
}

但实际上下面的代码也会被认为是window对象。

let a = {}
a.window = a

a === a.window // true
isWindow(a) // true

7. 判断数据类型

利用Object.prototype.toString方法来做数据类型的判断。

let class2type = {}
let toString = class2type.toString

  // Populate the class2type map
$.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
  class2type["[object " + name + "]"] = name.toLowerCase()
})

最后class2type会变成

class2type = {
  "[object Boolean]": "boolean",
  "[object Array]": "array",
  "[object Number]": "number"
  ...
} 

接着就是type函数的定义了

function type(obj) {
  return obj == null ? String(obj) :
  class2type[toString.call(obj)] || "object"
}

首先如果传入的obj是null或者undefined,则用String函数返货null或者undefined,而toString.call(obj)返回的正是形如[object Array],所以再结合上面的class2type变量,正好就可以得到例如。

type([]) => array
type(1) => number

8. 判断是够为纯粹的对象(isPlainObject)

有时候我们想要符合这样条件的对象。但是js中没有直接给到能够判断是否为纯粹的对象的方法。

// 对象字面量形式
let obj = {
  name: 'qianlongo'
}
// 通过Object构造函数创建
let person = new Object({
  name: 'qianlongo',
  sex: 'boy'
})

zepto中是如何判断的呢?

// 判断obj是否为纯粹的对象,必须满足
// 首先必须是对象  --- isObject(obj)
// 不是window对象 --- !isWindow(obj)
// 并且原型要和 Object 的原型相等

function isPlainObject(obj) {
  return isObject(obj) && !isWindow(obj) && Object.getPrototypeOf(obj) == Object.prototype
}

Object.getPrototypeOf() 方法返回指定对象的原型(即, 内部[[Prototype]]属性的值),如果没有继承属性,则返回 null 。

9. 判断是否为空对象(isEmptyObject)

// 判断是否为空对象
// 使用for in遍历,只要obj有属性则认为不是空对象

$.isEmptyObject = function (obj) {
  var name
  for (name in obj) return false
  return true
}

主要是通过走一遍for循环,来确定,所以会将以下数据也认为是空对象。

  1. null
  2. undefined
  3. []
  4. ''
  5. 1024(数字)
  6. true or false
  7. {}
  8. new Person() // 自定义的构造函数

所以这里判断空对象的初衷到底是不是只为了判断形如{},new Object()

结尾

暂时就更新这些,后续在阅读源码的过程中会陆续补充

参考资料

读Zepto源码之内部方法

jQuery.isPlainObject

对jQuery.isPlainObject()的理解

Object.getPrototypeOf()

文章记录

  1. 原来你是这样的jsonp(原理与具体实现细节)

  2. 谁说你只是"会用"jQuery?

  3. 向zepto.js学习如何手动触发DOM事件

  4. mouseenter与mouseover为何这般纠缠不清?

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

No branches or pull requests

1 participant