lodash源码系列——isSymbol

763 阅读3分钟

_.isSymbol(value)

检查value是否是原始Symbol或者对象。

返回true or false。

对Symbol不太了解的同学可以参考我的这篇文章----> ES6之Symbol

自己实现

function isSymbol(value) {
    return typeof value === 'symbol'
}

源码

function isSymbol(value) {
    return typeof value == 'symbol' ||
        (isObjectLike(value) && baseGetTag(value) == symbolTag)
}

分析

为什么使用==?

首先有个疑问,为什么typeof value == 'symbol'用的是双等号而不用严格相等===呢?

typeof返回值的类型是字符串,那么这里其实应该是严格相等的,而且用==是会进行隐式的类型转换的,这里没有必要。从规范上来说,也应该是尽量减少==的使用,使用===。个人认为,这里应该使用===。

为什么后续还要进行类对象的判断?

(isObjectLike(value) && baseGetTag(value) == symbolTag)

先看isObjectLike(value),顾名思义就是检查value是否是类对象。如果一个值是类对象,那么它不应该是null,而且typeof后的结果是"object"。具体的实现和源码可以参考我的这篇文章中的isObjectLike部分---->lodash源码系列——isObject和isObjectLike

再看symbolTag的定义:

var symbolTag = '[object Symbol]'

可以看出,当value为类对象,并且baseGetTag(value)的返回值为'[object Symbol]'时,isSymbol返回true。

那么,为什么要进行这些后续的类对象的判断?原来是因为,创建一个显示包装对象从ES6开始不再被支持了,现在可以用如下代码来模拟:

let objSym = Object(Symbol())
typeof objSym                                   // "object"
_.isSymbol(objSym)                              // true

objSym的类型是object,但是_.isSymbol()返回的却是true。对类对象的检测就是检测的这种情况。

baseGetTag方法

由上面可以看出,baseGetTag这个方法主要用来获取value的类型标签。

源码:

function baseGetTag(value) {
    if (value == null) {
        return value === undefined ? undefinedTag : nullTag
    }
    return (symToStringTag && symToStringTag in Object(value)) ?
        getRawTag(value) :
        objectToString(value)
}

前面已经对value进行了isObjectLike的判断,故此处其实无需对value==null再进行判断,个人认为这么做的原因其实是因为这是一个lodash的内部方法,这个方法独立来看的话还是需要进行判断的。

undefinedTag、nullTag:

var undefinedTag = '[object Undefined]'
var nullTag = '[object Null]'

故当value为null或undefined时,返回的是'[object Undefined]'或'[object Null]'

否则,当value!= null时,继续往下看:

    return (symToStringTag && symToStringTag in Object(value)) ?
        getRawTag(value) :
        objectToString(value)

symToStringTag定义为:

var symToStringTag = Symbol ? Symbol.toStringTag : undefined;

Symbol.toStringTag是一个内置symbol,它通常作为对象的属性键使用,对应的属性值应该为字符串类型,这个字符串用来表示该对象的自定义类型标签。具体的内容可以参考我的另一篇文章---->Symbol的toStringTag属性

当前环境支持Symbol,则symToStringTag为Symbol.toStringTag,否则为undefined。

objectToString方法

symToStringTag=undefined时,对value调用objectToString方法:

function objectToString(value) {
    return nativeObjectToString.call(value);
}

其实调用的是nativeObjectToString:

var nativeObjectToString = objectProto.toString;

objectProto:

var objectProto = Object.prototype

所以归根结底,objectToString方法就是Object.prototype.toString.call(value)

也就是说,当对象没有Symbol.toStringTag属性时,可以通过调用toString()返回特定的类型标签。

symToStringTag=Symbol.toStringTag时,并且Object(value)不存在symToStringTag属性时,仍然调用objectToString用来返回value的类型标签。

getRawTag方法

symToStringTag=Symbol.toStringTag时,并且Object(value)存在symToStringTag属性时,对value调用getRawTag方法。

function getRawTag(value) {
    // 判断symToStringTag是否为类对象value自身具有的属性
    var isOwn = hasOwnProperty.call(value, symToStringTag),
        // 取出类对象value的symToStringTag属性值赋值给tag
        tag = value[symToStringTag];
    try {
        // 修改value的symToStringTag属性对应的值为undefined
        value[symToStringTag] = undefined;
        var unmasked = true;
    } catch (e) {}
    // result为对value调用toString()的返回值
    var result = nativeObjectToString.call(value);
    if (unmasked) {
        // symToStringTag是value自身就具有的属性
        if (isOwn) {
            // 修改value的symToStringTag属性对应的值为tag
            value[symToStringTag] = tag;
        }
        // symToStringTag不是value自身就具有的属性
        else {
            delete value[symToStringTag];
        }
    }
    return result;
}

归根结底,返回的结果还是对value调用toString()方法的返回值。

总结

当一个value的类型是'symbol',或者类型是'object'但是toString()的返回值为'[object Symbol]'时,_.isSymbol(value)返回true,否则返回false。