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

常用类型判断以及一些有用的工具方法 #2

Open
lessfish opened this issue May 21, 2016 · 17 comments
Open

常用类型判断以及一些有用的工具方法 #2

lessfish opened this issue May 21, 2016 · 17 comments

Comments

@lessfish
Copy link
Owner

lessfish commented May 21, 2016

Why underscore

最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中。

阅读一些著名框架类库的源码,就好像和一个个大师对话,你会学到很多。为什么是 underscore?最主要的原因是 underscore 简短精悍(约 1.5k 行),封装了 100 多个有用的方法,耦合度低,非常适合逐个方法阅读,适合楼主这样的 JavaScript 初学者。从中,你不仅可以学到用 void 0 代替 undefined 避免 undefined 被重写等一些小技巧 ,也可以学到变量类型判断、函数节流&函数去抖等常用的方法,还可以学到很多浏览器兼容的 hack,更可以学到作者的整体设计思路以及 API 设计的原理(向后兼容)。

之后楼主会写一系列的文章跟大家分享在源码阅读中学习到的知识。

欢迎围观~ (如果有兴趣,欢迎 star & watch~)您的关注是楼主继续写作的动力

类型判断

第一篇跟大家简单地聊了下为什么 underscore.js 用 void 0 代替了 undefined,意外地收到了不错的反响,有朋友私信我说以前还真不知道这回事,也有人催促我赶紧继续下一篇解读文章。今天就跟大家聊一聊 underscore.js 中一些 JavaScript 常用类型检查方法,以及一些工具类的判断方法。

我们先说个老生常谈的问题,JavaScript 中数组类型的判断方法,事实上,我在 Javascript中判断数组的正确姿势 一文中已经详细分析了各种判断方式的优缺点,并给出了正确的判断代码:

function isArray(a) {
  Array.isArray ? Array.isArray(a) : Object.prototype.toString.call(a) === '[object Array]';
}

而 underscore 其实也正是这么做的:

// Is a given value an array?
// Delegates to ECMA5's native Array.isArray
// 判断是否为数组
_.isArray = nativeIsArray || function(obj) {
  return toString.call(obj) === '[object Array]';
};

nativeIsArray 正是 ES5 中 Array.isArray 方法,如果支持则优先调用;而 toString 变量就保存了 Object.prototype.toString。

如何判断对象?underscore 把类型为 function 和 object 的变量都算作对象,当然得除去 null。

// Is a given variable an object?
// 判断是否为对象
// 这里的对象包括 function 和 object
_.isObject = function(obj) {
  var type = typeof obj;
  return type === 'function' || type === 'object' && !!obj;
};

再看 'Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error' 这些类型的判断,其实都可以用 Object.prototype.toString.call 来判断,所以写在了一起:

// Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError.
// 其他类型判断
_.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) {
  _['is' + name] = function(obj) {
    return toString.call(obj) === '[object ' + name + ']';
  };
});

但是看 isArguments 方法,在 IE < 9 下对 arguments 调用 Object.prototype.toString.call,结果是 [object Object],而并非我们期望的 [object Arguments]。咋整?我们可以用该元素是否含有 callee 属性来判断,众所周时,arguments.callee 能返回当前 arguments 所在的函数。

// Define a fallback version of the method in browsers (ahem, IE < 9), where
// there isn't any inspectable "Arguments" type.
// _.isArguments 方法在 IE < 9 下的兼容
// IE < 9 下对 arguments 调用 Object.prototype.toString.call 方法
// 结果是 [object Object]
// 而并非我们期望的 [object Arguments]。
// so 用是否含有 callee 属性来判断
if (!_.isArguments(arguments)) {
  _.isArguments = function(obj) {
    return _.has(obj, 'callee');
  };
}

工具类判断方法

接下来看下一些常用的工具类判断方法。

判断一个元素是否是 DOM 元素,非常简单,只需要保证它不为空,且 nodeType 属性为 1:

// Is a given value a DOM element?
// 判断是否为 DOM 元素
_.isElement = function(obj) {
  // 确保 obj 不是 null 
  // 并且 obj.nodeType === 1
  return !!(obj && obj.nodeType === 1);
};

如何判断一个元素为 NaN?NaN 其实是属于 Number 类型,Object.prototype.toString.call(NaN) 返回的是 "[object Number]",而且 NaN 不等于本身,利用这两点即可进行判断:(2016-06-21 该实现有 BUG,详见 https://github.com/hanzichi/underscore-analysis/issues/13)

// Is the given value `NaN`? (NaN is the only number which does not equal itself).
// 判断是否是 NaN
// NaN 是唯一的一个 `自己不等于自己` 的 number 类型
_.isNaN = function(obj) {
  return _.isNumber(obj) && obj !== +obj;
};

当然,underscore 还有很多其他的有用的工具类判断方法,具体可以看源码 https://github.com/hanzichi/underscore-analysis/blob/master/underscore-1.8.3.js/src/underscore-1.8.3.js#L1192-L1263 这部分。

如果您觉得我分享的东西对您有所帮助,请关注我的 Repo https://github.com/hanzichi/underscore-analysis

@lessfish lessfish added bug and removed bug labels May 21, 2016
@jweboy
Copy link

jweboy commented May 26, 2016

mark~

@EdwardZZZ
Copy link

_.isNaN() 更新了,用的是native的isNaN方法。

@lessfish
Copy link
Owner Author

lessfish commented Jun 8, 2016

@EdwardZZZ 确实,感谢指出~

@FarmanYu
Copy link

isNaN有Native的

@lessfish
Copy link
Owner Author

@FarmanYu 对的,edge 版本已经用 native 的函数判断了

@pod4g
Copy link

pod4g commented Jun 21, 2016

有2点疑问:

1、isNaN有原生的,直接使用即可,为什么前面还要判断isNumber?这样做是为了排除那些不常见的边界情况?

2、_.isNumber(obj) && obj !== +obj; ,只有_.isNumber(obj)true才会执行第二个判断,执行到第二个判断就说明一定是number类型的,但是为什么又要把obj用+号转一次呢?

@lessfish
Copy link
Owner Author

lessfish commented Jun 21, 2016

@pod4g 好问题。

首先我们明确下 underscore 中 _.isNaN 方法的作用,引用文档 http://underscorejs.org/#isNaN:

Note: this is not the same as the native isNaN function, which will also return true for many other not-number values, such as undefined.

也就是说,_.isNaN 如果要返回 true,必须传入一个 Number 类型。

isNaN(undefined);
=> true
_.isNaN(undefined)
=> false

这就是为什么要判断 isNumber 的原因。事实上可以看看 edge 版本的源代码:

// Is the given value `NaN`?
_.isNaN = function(obj) {
return _.isNumber(obj) && isNaN(obj);
};

这就非常清晰了,需要满足 isNumber 和 isNaN 两个条件。

话说回来,满足 isNumber 后,为什么要用 + 号转一次呢?

var a = new Number(NaN);
a !== a;
=> false
a !== +a;
=> true

很显然,我们的变量 a,调用 _.isNaN,希望返回的是 true。

事实上,_.isNaN 可能和 Number.isNaN 更接近一点。

@pod4g
Copy link

pod4g commented Jun 21, 2016

@hanzichi 原来是这样的。谢谢!underscore变了思路。我受原生isNaN的影响,觉得 不是一个数字 就意味着不是一个数字,例如,undefined确实不是一个数字。。underscore收窄了范围(不进行强制类型转换了)。

再次感谢楼主!

@hankxdev
Copy link

hankxdev commented Jul 5, 2016

一直想提醒一下,Github不是BBS,若非有实质内容,诸如“谢谢楼主”,“感谢”这类的信息可否不要发布在issues讨论里面。

@aleen42
Copy link

aleen42 commented Jul 12, 2016

@hanzichi hey, 我想把你的這些分析, 寫成英文, 並放在自己的筆記中以供學習. 可以嗎? 鏈接: https://aleen42.gitbooks.io/personalwiki/content/Programming/JavaScript/Framework/underscore/type_inference_and_tutorials/type_inference_and_tutorials.html

@lessfish
Copy link
Owner Author

@aleen42 可以,不过希望能指明出处,谢谢~

@tudewutong
Copy link

判断Dom元素的方法,如果对象里有nodeType属性呢。。。
_.isElement({nodeType:1})

@lessfish
Copy link
Owner Author

@tudewutong 这确实是个问题 ...

@Baoyx007
Copy link

Baoyx007 commented Sep 7, 2016

_.isElement = function(obj) {
  // 确保 obj 不是 null 
  // 并且 obj.nodeType === 1
  return !!(obj && obj.nodeType === 1);
};

这里为什么要加两个! 呢?

@lessfish
Copy link
Owner Author

lessfish commented Sep 8, 2016

@Baoyx007 强制转为布尔值,因为函数返回的是一个布尔值(虽然大对数情况下不加 !! 也问题不大,毕竟会隐式转换)

@shaunzeng
Copy link

could you also explain why in _.where method, _.isMatch is passed in that returns a boolean. How the _.filter method functions when the predicate is a boolean? thanks!!

@jingaier
Copy link

var _ = function (obj) {
if (obj instanceof _) return obj;
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};
楼主你好,看源码,这段能给解读下吗?为何要这样写?

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

No branches or pull requests