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

JS-类型&类型判断 #97

Open
yaofly2012 opened this issue Feb 9, 2020 · 7 comments
Open

JS-类型&类型判断 #97

yaofly2012 opened this issue Feb 9, 2020 · 7 comments

Comments

@yaofly2012
Copy link
Owner

yaofly2012 commented Feb 9, 2020

一、类型种类

基于typeof返回值进行分类:

  1. number
  2. string
  3. boolean
  4. null
    虽然typeof null === 'object',但也作为一个类型
  5. undefined
  6. object
  7. function
  8. Symbol
    ES6新增类型

其中:

  1. 值类型(基本类型):number, string, boolean, null, undefined, Symbol
  2. 引用类型:object, function

值类型和引用类型区别

  1. 值类型变量值大小固定,不可修改(immutable),操作(比较,赋值)都是基于值的;
    字符串变量虽然长度不一,但是具体的某个字符串的大小是固定的。
  2. 引用类型变量值大小不定,且是可变的(mutable), 操作(比较,赋值)都是基于引用的。
var a = { name: 'john'}
var b = a;
b.age = 12;
console.log(a.age) // 12
  1. 从存储角度看到值类型的变量值存在栈里,而引用类型的变量值存储在堆里。

二、类型转化

2.1 装箱和拆箱

基本类型(除了null, undefined)都有对应引用类型对象。并且会根据使用场景会自动地把基本类型转成对象(装箱),或者把对象转成基本类型(拆箱)。

2.1.1 装箱

'hello'.length; // 
true.toString(); //
(1).toFixed(); // 为了区分数字小数点,这里添加个括号。或者这样写:1['toFixed']()

显示装箱
除了调用基本类型对应的引用类型构造函数外,可以利用Object方法进行装箱。

Object(1) // 等价 new Number(1)
Object(false) // 等价new Boolean(false)
Object('hello') // 等价new String('hello')

// null, undefined没有对应的引用类型
Object(null) // 空对象`{}`
Object(undefined) // 空对象`{}`

PS:可以利用Object(null)判断参数是否为非null的对象。

function isObj(target) {
  return Object(target) === target;
}

2.1.2 拆箱

当需要基本类型时,对象也会自动转成基本类型(拆箱)

1. 转number(比如算术运算符中的对象,需要转成number)

  • 如果定义了valueOf方法并且该方法返回原始值,则调用该方法,并转成number;
    内置对象中只有Boolean, String, Number, Date重写了valueOf方法,其他大部分都还是默认的valueOf行为。
  • 否则,如果定义了toString方法并且该方法返回原始值,则调用该方法,并转成number;
  • 其他情况报类型错误

TypeError: Cannot convert object to primitive value

2. 转string比如+运算中的对象(比如模板字符串中)

  • 如果定义了toString方法并且该方法返回原始值,则调用该方法,并转成string;
  • 否则,如果定义了valueOf方法并且该方法返回原始值,则调用该方法,并转成string;
  • 其他情况报类型错误

TypeError: Cannot convert object to primitive value

注意:转number和转string的过程正好相反。

3. 转boolean(比如逻辑运算中)

  1. 所有对象(不包含null)都是true
    即使是new Boolean(false)对象
  2. **注意: ** new Boolean(false)Boolean(false)返回值是不一样的,前者是对象,后者是基本值。

4. Demo

var obj = {
    toString: function() {
        return 1
    },
    valueOf: function() {
        return 2
    }
}
console.log(`${obj}`) // '1',  调用`toString`
console.log(obj + 1)  // 3, 调用`valueOf`
console.log(obj + '1') // '21', 调用`valueOf`, 然后再转成字符串‘2’

// Demo2
var obj2 = {
    toString: function() {
        return {} // 返回对象
    },
    valueOf: function() {
        return 2
    }
}
console.log(`${obj2 }`) // '2',  调用`valueOf`(因为toString返回值不是原始值)

// Demo3
var obj3 = {
    toString: function() {
        return 1
    },
    valueOf: function() {
        return {}
    }
}
console.log(`${obj3 }`) // '1',  调用`toString`
console.log(obj3 + 1)  // 2, 调用`toString`, 因为valueOf返回值不是原始值
console.log(obj3 + '1') // '11', 调用`toString`, 然后再转成字符串‘1’

2.2 参与运算的类型转换

运算符对表达式的类型有要求,不满足要求的会进行隐式的类型转换。但有些运算符可能支持多种类型的数据,这里就涉及一些优先类型转换规则:

  1. 算术运算符+(string和number都可以)
    当其中一个是字符串时,会把另一个原始值(拆箱之后)转成字符串。
  2. 比较运算符(>, >=, <=, <)(stringnumber都可以)
    都是string时才进行字符串比较,其他转number进行比较
  3. 相等运算符(==!=)(基本类型和引用类型都可以)
  • 相同类型的直接判断值(同===);
  • nullundefined值不会发生类型转换,即效果同===,但是特例null == undefined为true;
null == false; // false
null == 0; // false
null == ''// false
undefined == false; // false
undefined == 0; // false
undefined == '' // false
undefined  == null // true

nullundefined没有封装类型,也没有方法(不存在valueOf, toString方法)

  • 对象转成基本类型(number场景的转换规则)参与==比较;
  • 基本类型(除了null, undefiend)都转成Number进行比较,即String和bool不会互转,都会统一转成Number。
false == ''; // true
false == 0; // true
'' == 0; // true

2.2.2 特例

  1. 空字符串转number是0,即+'' === 0;
    注意+[] === 0,因为空数组的toString返回是空字符串。

2.2.3 总结下:

  • 运算中的拆包都走转Number拆箱规则;
  • 只有+中字符串优先级高些,其他基本上都是转number;
  • 分析类型转换时,可能不能一步明确目标类型,可能会涉及多次类型转换,比如上例中的对象和字符串加操作。

2.3 显示类型转换

2.3.1 使用构造函数Boolean, Number, String, Object可以显示的转成指定的引用类型。

new Number('1') // => Number {1}
new Boolean([]) // =>  Boolean {true}
new Object(3) // => Number{3}
  1. 有原始值的对象,如Number/Boolean/String的函数调用和new方式调用返回值不一样;
Number('1') // => 原始值 1
new Number('1') // 对象 Number{1}
  1. 没有原始值的内置对象,如Array, Object, RegExp的函数调用等价new方式调用。
  2. Object函数调用这么智能啊,可以根据实参类型转成对应的对象格式。nullundefined不存在对应的对象,Object(null)Object(undefined)返回空对象{}

2.3.2 一元运算符显示转化

+'1' // => 1
!!1 // => true
'' + 1 // => '1'
  1. 这里是借助有些运算符只支持某一种类型数据的原理;
  2. 逻辑非!运算符不涉及拆包过程(所有对象都是true)。

2.3.3 内置方法

  1. parseInt(string[, radix])
  • radix只是表示参数1的进制,返回值是10机制,不是radix指定返回值的进制
    实参null, undefined, 0是无效值,就当做没有传值。
    非int值会转成Int。
  • parseInt是逐个字符解析实参string的,遇到非法字符或者字符串结尾,就把已经解析的结果返回,如果第一个字符无法解析则返回NaN
parseInt('a1') // NaN
parseInt('1a') // 1,这个跟`+'1a'`结果不一样
parseInt('123', 5) // 将'123'看作5进制数,返回十进制数38 => 1*5^2 + 2*5^1 + 3*5^0 = 38
parseInt("546", 2);   // 除了“0、1”外,其它数字都不是有效二进制数字
  1. parseFloat(string)

三、类型判断

1.typeof虽然简单方便,但不能区分null和object以及各种常用内置对象;

typeof null // "object"
typeof /abc/ // "object"
typeof [] // "object"
typeof (function(){}) // "function"

image

但是如果只是识别函数,也可以使用typeof (常见于第三方库/util中)。

原理:???

In the first implementation of JavaScript, JavaScript values were represented as a type tag and a value

2. 更常规的用法调用Object.prototype.toString方法。

console.log(Object.prototype.toString.call(1)); // [object Number]
console.log(Object.prototype.toString.call('')); // [object String]
console.log(Object.prototype.toString.call(null)); // [object Null]
console.log(Object.prototype.toString.call(undefined)); // [object Undefined]
console.log(Object.prototype.toString.call(true)); // [object Boolean]
console.log(Object.prototype.toString.call( {})); // [object Object]
console.log(Object.prototype.toString.call([])); // [object Array]
console.log(Object.prototype.toString.call(function(){})); // [object Function]

underescorejs 源码片段

var ObjProto = Object.prototype;
var toString = ObjProto.toString;
// Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError, isMap, isWeakMap, isSet, isWeakSet.
  _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error', 'Symbol', 'Map', 'WeakMap', 'Set', 'WeakSet'], function(name) {
    _['is' + name] = function(obj) {
      return toString.call(obj) === '[object ' + name + ']';
    };
  });

原理

  • 内置对象都有默认的toString格式字符串(转字符串标签)。

toString() returns "[object type]", where type is the object type.

这里的type应该是指对象默认描述字符串

  • toString方式更多的是判断各种对象类型,所以对于基本类型会有装箱的操作(除了null, undefined)。从ES5开始null, undefined也可以调用了
console.log(Object.prototype.toString.call(null)); // [object Null]
console.log(Object.prototype.toString.call(undefined)); // [object Undefined]

缺点

var myObj = {
    get [Symbol.toStringTag]() {
        return 'MyObj'
    }
}

console.log(myObj[Symbol.toStringTag]) // "MyObj"
console.log(Object.prototype.toString.call(myObj)); "[object MyObj]"
  • 无法区分NaNInfinity

3. 区分NaN, Infinity

NaN, Infinity都是number值(它俩类型一样),使用方法2是不能区分的。

Object.prototype.toString.call(Infinity) // "[object Number]"
Object.prototype.toString.call(NaN) // "[object Number]"

还好Number有相关静态方法isNaN, isInfinity(宿主也有同名的全局方法)。

4. Array.isArray

Array.isArray() exists because of one particular problem in browsers: each frame has its own global environment

instanceof都存在这个问题,为啥单独给只提供个Array.isArray?
其他方式或多或少有点问题,Array.isArray更靠谱些。

四、参考

  1. MDN Primitive
  2. 相等性判断
  3. MDN Object.prototype.toString
@yaofly2012 yaofly2012 added the JS label Feb 9, 2020
@yaofly2012
Copy link
Owner Author

yaofly2012 commented Feb 9, 2020

String

一、String

1.1 字符串length & 字符数量

注意,注意,注意:字符的length属性表示的是字符串长度是以UTF-16码点为单位的(两个字节),并不是字符的数量

console.log('A你Z'.length) // 4

原因:

在JS出生的时候,Unicode编码规范最大只有两个字节(BMP字符),所以那个时候length也就是字符的数量。

如何获取字符串的数量?

ES6方法

  1. 利用字符串是可迭代对象
// 展开操作符
console.log([...'A你Z'].length) // 3

// for-of
var count = 0;
for(let c of 'A你Z') {
    count++
}
console.log(count) // 3
  1. 利用codePointAt
var str = 'A你Z'
var strArr = [];
var maxCharCode = Math.pow(2, 16) -1;
for(var i = 0, len = str.length; i < len; ++i) {
    var charCode = str.codePointAt(i);
    if(charCode <= maxCharCode ) {
        strArr.push(str[i])
    } else {
        strArr.push(str[i] + str[++i]) // 非BMP字符
    }
}

console.log(strArr.length) // 3

ES3/5方法

只能用charCodeAt hack ES6的方式了。

字符串有最大长度吗?

String.length有关于字符串长度的描述:

ECMAScript 2016 (ed. 7) established a maximum length of 2^53 - 1 elements. Previously, no maximum length was specified. In Firefox, strings have a maximum length of 230 - 2 (~1GB). In versions prior to Firefox 65, the maximum length was 228 - 1 (~256MB).

在之前呢?如果字符串应该以数组的形式存储的,那应该受最大索引的限制(Math.(2. 32) - 1),如果这样的话大概最大
将近4G((Math.pow(2, 32) -1) / 1024 / 1024 / 1024)。字符串要不存储的,估计计算机内存直接被耗尽了。
试了下,结果浏览器直接报内存不足。。。

Array.from({ length: Math.pow(2, 32) - 1}).fill().join('0')

1.2 字符串索引

length属性一样,字符串的索引也是以UTF-16为单位的。

console.log('A你Z'[1].charCodeAt(0).toString(16)) // d87e

1.3 字符串和数组

字符可以视为字符构成的数组,很多操作跟数组类似:

  • 都可以通过索引访问元素;
  • 存在类似的方法;
  • 并且字符串String也是可迭代对象。
var arr = [...'hello']; // ["h", "e", "l", "l", "o"]

字符串转数组

var str = 'A你Z'

console.log(Array.from('A你Z')) //  ["A", "你", "Z"]
console.log([...'A你Z']) //  ["A", "你", "Z"]
console.log('A你Z'.split(''))  // ["A", "�", "�", "Z"]
console.log(Array.prototype.slice.call('A你Z')) //  ["A", "�", "�", "Z"]

// codePointAt
var strArr = [];
for(var i = 0, len = str.length; i < len; ++i) {
    var charCode = str.codePointAt(i);
    if(charCode < Math.pow(2, 16)) {
        strArr.push(str[i])
    } else {
        strArr.push(str[i] + str[++i]) // 非BMP字符
    }
}

console.log(strArr)  //  ["A", "你", "Z"]
  1. ES3/5的方法(比如split('')Array.prototype.slice.call)是基于索引的,针对非BMP的字符是无法正常转数组的。

  2. ES6基于迭代器的方法是基于Unicode码点的。

1.4 字符大小

字符可以直接进行逻辑运算的。利用逐个比较每个字符的UTF-16码点值进行比较的。Array.prototype.sort方法也是利用这个方式比较字符串的。

The default sort order is ascending, built upon converting the elements into strings, then comparing their sequences of UTF-16 code units values.

注意比较是UTF-16码点值不是Unicode码点值

1.5 字符加法 & 字符拼接

  1. 可以直接使用+进行字符串拼接。
  2. concat方法
  3. 利用Array.prototype.join方法。

1.6 字符串字面量

  1. 正常格式"abc",单引号和双引号都行;
  2. Unicode格式\uHHHH(十六进制)。
    “\u007A” === "z"
    两个UTF-16字节,如果展示非BMP字符得用两个\uHHHH(代理对)。
console.log('\uD83D\uDE80')
  1. ES6新引入的Unicode格式"\u{...}"(1个或多个十六进制Unicode码点值)。
    '\u{7A}' === 'z'
    展示非BMP字符更方便了。
console.log('\uD83D\uDE80')
console.log('\u{1F680}');

二、字符存储 & UTF-16

2.1 两个字节存储一个字符

字符串的每个字符都是采用UTF-16进行编码存储的。即采用两个字节存储一个字符,理论上最多可表示2^16 (65,536)个字符,实际上表示字符的没使用这么多,因为有些区域的码点值有特殊的用途。

UTF-16码点值:每个字符对应的编码值,即charCodeAt方法的返回值。
BMP(Basic Multilingual Plane)区域:在Unicode字符码表里两个字节表示区域叫做BMP区域。

在BMP区域UTF-16码点值和Unicode码点值是一样的。

2.2 非BMP区域字符存储

采用两个UTF-16码点值表示(即4个字节)一个字符。这两个码点值叫代理对

  • 左侧的叫高位代理对,取值范围[0xD800, 0xDBFF]
  • 右侧的叫低位代理对,取值范围[0xDC00, 0xDFFF]

高位代理的码点值为啥小于低位代理码点值

image

  1. 高位代理的码点值小于低位代理码点值。
  2. 低位代理后面还有BMP字符。

这导致非BMP字符不能正确的进行逻辑大小比较和排序。

代理对和unicode码点值转换

有专门的转化公式。在利用charCodeAt hackcodePointAt时也得利用这个公式。

三、APIs

3.1 replace/replaceAll

本质copy是字符串,并同时进行替换操作。是字符串格式化的利器,功能也很强大。

  1. replace(regexp|substr, newSubstr|function)
    copy的同时,对字符进行一次或者多次(取决于正则是否带g)匹配替换。

  2. replaceAll
    copy的同时,对字符进行多次(此时如果是正则,则必须带g)匹配替换。

replaceAllreplace差异主要体现在非正则的实参上,正则实参本质没差异(你品,你细品)。

⚠️⚠️⚠️实际项目里最好不要使用replaceAll,这个方法的兼容性太差:
image

参数1: 匹配的字符串或则正则表达式

参数2:被替换的字符串或则字符串生成函数

  1. 字符串
    除了是普通字符串,还支持字符串占位符

  2. 字符串生成函数function(str, $1, $2...., offset, input, groups)
    参数和String.match单次匹配返回值, RegExp.exec返回值都比较类似。
    但是注意的是分组后面的实参offfset是表示字符串的下标,不是正则的lastIndex。并且在匹配领宽度字符时存在差异了:

var regExp = /(?=1)/g;

var match = regExp.exec('312121212')
console.log(regExp.lastIndex, match.index) // 1 1

match = regExp.exec('312121212')
console.log(regExp.lastIndex, match.index) // 1 1

var offsetArr = []
'312121212'.replace(/(?=1)/g, function(str, offset) {
    offsetArr.push(offset);
})
console.log(offsetArr) // [1, 3, 5, 7]

exec在匹配零宽度正则时感觉就是bug的存在,此时如果lastIndex==0还好理解(即领宽度不占用匹配字符宽度)。

3.2 match / matchAll

  1. matchAll(regexp)比较简单,就是返回所以匹配的字符集合
  • 返回值不是数组,是个可迭代对象,没有匹配成功,则是个空可迭代对象;
  • 返回值可迭代对象每个元素,跟RegExp.prototype.exec返回值一样。
  1. match(regexp)对字符进行一次或者多次(取决于实参提供的正则是否具g)匹配
  • 如果是多次匹配,则返回值类似matchAll(regexp)
    区别是match(regexp)返回的是个数组;

  • 如果是单次匹配,则返回值同RegExp.exec

3.3 split

3.4 迷人三剑客:charAt/charCodeAt/codePointAt

  1. charAt(index) 获取指定索引位置的字符

  2. charCodeAt 获取指定索引位置的UTF-16码点值(范围[0, 65535 ])
    charAt(index)是类似的,不过返回值不同。一个是字符,一个是字符对应的码点值。

  3. codePointAt 获取指定索引位置的Unicode码点值

  • ES2015引入的
  • 如果指定的索引位置字符是BMP字符或则不是UTF-16代理对的起点,则返回值同charCodeAt
var str = 'A你Z'
console.log(str.charCodeAt(1)) // 55422
console.log(str.codePointAt(1)) // 194564

console.log(str.charCodeAt(2)) // 56324 
console.log(str.codePointAt(2)) // 56324

charAt(index)& []下标获取区别

两者在功能上是等价的。

var str = 'A你Z'
console.log(str[1] === str.charAt(1))

chartAt是标准规范的访问方式,String又是伪数组,ES5新增了[]访问方式。一句话后者只是前者的语法糖,并且是在ES5中才实现的。
如果非要说些两者的区别,那就得是参数类型:

  1. charAt的参数是整型Number,其范围是[0, length-1], 不在该范围内的,则返回空串;
  • 如果参数不是整型,则会先转成整型Number
  • 如果参数转换结果为NaN,则视为0(相当于执行NaN >>> 0)。
  1. [index]下标方式中index是个字符串(跟一般对象通过属性访问方式一样)
  • 如果不是字符串则转成字符串,
  • 如果访问不到,则返回undefined
'abc'.charAt(true) // 'b'
'abc'[true] // undefined

'abc'.charAt(1.2) // 'b'
'abc'[1.2] // undefined

如何获取指定位置的完整字符

这个问题本身就存在问题。一般我们指定的“位置”是索引,但是把索引映射到字符的位置不是很明显。
如果是明确“获取第几个字符”,则可以利用迭代器遍历或者先把字符串转成字符数组再提取。

参考

  1. string-charatx-or-stringx
  2. JavaScript’s internal character encoding: UCS-2 or UTF-16?
  3. UTF-8 遍地开花
  4. 软件工程师必须知道Unicode和字符集
  5. ES2015 .New string features

@yaofly2012
Copy link
Owner Author

yaofly2012 commented Feb 9, 2020

Number

一、语法

1.1 概述

  1. JS数字不区分整形和浮点数,统一采用IEEE 754标准64位方式存储。
  2. JS虽然没有整形但是有些操作是基于32整数进行的
  • 位运算
  • 数组索引最大长度
  • setTimeout/setInterval的最大delay参数值

1.2 NaN

NaN是个全局对象属性,表示一个特殊的数字Not a Number

  1. 类型是Number,但不具有数字的运算特性,甚至相等性判断中跟自己也不相等。
console.log(NaN === NaN) // false
console.log(NaN !== NaN) // true
  1. 常见产生NaN的场景:
  • 0/0, Infinity / Infinity, Infinity * 0;
  • NaN参与的数学运算;
  • 非数字的字符串,undefined转Number操作(如+'hello'+undefined
  1. 为啥需要NaN这个奇葩
  • 不只是JS才有NaN,它来自标准 IEEE-754
  • 计算机中的浮点数其实是有限的,对于那些无法存储的浮点数统一用NaN表示;
  • NaN更多的是表示一个逻辑结果,而导致NaN的逻辑由很多,所以 这也是NaN不等于自身的原因吧。

1.3 Infinity

Infinity是个全局对象属性,表示一个特殊的数字:无穷大。

console.log(Infinity); /* Infinity */  
console.log(Infinity === Infinity) // true
console.log(Infinity + 1 ); /* Infinity */  
console.log(Math.pow(10, 1000)); /* Infinity */  
console.log(Math.log(0) ); /* -Infinity */  
console.log(1 / Infinity );  /* 0 */  
console.log(1 / 0 ); /* Infinity */ 

1.3.1 利用Infinity识别-00

ES6中引入同值相等算法是可以区分-00的,在此之前可以利用Infinity识别:

// JS中0可以作为被除数
console.log(1/0 === Infinity) // true
console.log(1/-0 === -Infinity) // true

1.4 字面量

Number字面量有多重写法。

  1. ES3/5支持十进制,十进制科学计数法,16进制(hex)字面量;
  2. ES2015支持二进制(binary ),八进制( octal)直面量。
var a = 11; // 十进制
var b = 2E2; // 十进制科学计数法,E大小写不区分
var c = 0xf; // 16进制,x大小写不区分

var d = 0b110; // 二进制,b大小写不区分
var e = 0o10; // 8进制,o大小写不区分
var f = 010; // 8进制,此时`o`可以省略

1.5 APIs

ES2015

1. Number.MAX_SAFE_INTEGER

Math.pow(2, 53) - 1
53 = 利用52位mantissa区域+1位省略。整数是需要连续的,所以表示整数时不能使用指数位区域。

const x = Number.MAX_SAFE_INTEGER + 1;
const y = Number.MAX_SAFE_INTEGER + 2;

console.log(Number.MAX_SAFE_INTEGER);
// expected output: 9007199254740991

console.log(x +3);
// expected output: 9007199254740992

console.log(x === y); // true

2. isNaN函数和Number.isNaN方法区别

都是用于判断一个值是否为NaN。但是Number.isNaN更严谨。

  1. Number.isNaN不会进行参数转换,而 isNaN会默认把实参先转成Number(用于类型判断的方法居然进行了类型转换,果断不能用)
console.log(Number.isNaN('a')); // false
console.log(isNaN('a')); // true
  1. Number.isNaN是ES2015引入的,兼容性差些,但是也要使用,不支持就用polyfill:
Number.isNaN = Number.isNaN || function isNaN(input) {
    return typeof input === 'number' && input !== input;
}

2. isFinite函数和Number.isFinite方法区别

isNaN函数和Number.isNaN方法区别类似。

  1. isFinite会对实参进行类型转换,而Number.isFinite则不会。

  2. Number.isFinite是ES2015引入的

if (Number.isFinite === undefined) Number.isFinite = function(value) {
    return typeof value === 'number' && isFinite(value);
}

3. Number.isInteger

  1. XXX.0也是作为整数;
Number.isInteger(5.0);       // true
  1. polyfill
Number.isInteger = Number.isInteger || function(value) {
  return typeof value === 'number' && 
    isFinite(value) && 
    Math.floor(value) === value;
};

二、存储:了解IEEE双精度浮点数

2.1、复习10进制转2进制

  1. 整数部分:除2取余,逆序
  2. 小数部分:乘2取整,正序
    在线工具

2.2、了解IEEE 754双精度浮点数规范

2.2.1 通过2进制的科学计数法表示

和10进制的科学计数法类似,二进制的科学技术法格式为1.xxx*2^N。其中需要留意下二进制科学计数法的整数部分都是1,所以在存储时省略整数部分1。
image

2.2.2 格式:符号位+指数位+尾数位

  • 符号位S:第 1 位是正负数符号位(sign),0代表正数,1代表负数
  • 指数位E:中间的 11 位存储指数(exponent),用来表示次方数
    科学计数法中指数E是可以为负数的,在表示负的指数时IEEE754标准引入了一个偏移量1023,在存储指数时加上该偏移量把负数E转成正数。这就导致11位的指数能够表示指数的范围是[-1023, 1024]。
  • 尾数位M:最后的 52 位是尾数(mantissa),超出的部分自动进一舍零,没有填满的部分自动补0

如10进制数400.12,用10进制科学计数法表示为:4.0012*10^2,。其中"0012"就是尾数部分。
最终可表示为(图片来源):
image
其中S,E,M都是实际存储科学计数法的值。
如10进制4.5转成2进制为:

// Step1 转成二进制
100.1
// Step2 转成二进制科学计数
1.001*2^2
S = 0
E = 2 + 1023 = 2015
M = 001 // 整数1被省略了

形象的查看存储情况,可参考这里

2.2.3 有限集合

IEEE754能表示的实数数量是有限的,并且浮点数也不是连续的
假设MAX_VALUE, MIN_VALUE分别表示其表示的最大正数和最小正数,那有限集合可表示为(并且数字不是连续的):

[-MAX_VALUE, -MIN_VALUE] U [MIN_VALUE, -MAX_VALUE]

2.3 几个有趣的问题

1. 0.1 + 0.2 !== 0.3

0.1,0.2和0.3在转成二进制时都是无限循环的,在存储时会失去精度,0.1+0.2在运算时也会失去精度,导致结果不等于0.3。

2. 给一个数字加上一个非0的增量还等于本身这个数字

1 + Number.EPSILON/2 === 1

这个增量如果小于JS能表示的最小浮点数就会视为0,加上这样的数等于加上0。

3. JS最大整数为啥是2^53-1而不是2^52-1

整数需要连续性,所以表示整数时不能使用指数位E区域,只有尾数M区域可表示连续的数据
尾数占用52个bit,再加上省略的那个bit(见2.1)正好53个bit。

4. JS可以精确的表示哪些小数呢

小数部分是这种格式的都可以精确表示。

1/Math.pow(2, N), 其中N是(0, 1024)区间的整数。

如分数1/2,1/4, 1/8。

三、大数处理

大数处理已经有成熟的库了decimal js了,并且ES2015也引入了新的数据类型BigInt。如果让自己实现该怎么做呢?

整体思路:就是用字符串存储大数。

3.1 加法

  1. 低位对齐,逐个累加,进位;
  2. 符号位:一正一负转减法。
function getSign(a) {
    return /^-/.test(a) ? 1 : 0;
}

function add(a, b) {
    a = a + '';
    b = b + '';
    var aSign = getSign(a);
    var bSign = getSign(b);
    
    // 有一个负数,转成减法
    if(aSign^bSign) {
        return aSign ? minus(b, a) : minus(a, b); 
    }

    // 负数相加
    if(aSign) {
        a = a.substring(1);
        b = b.substring(1);
    }

    var maxLength = Math.max(a.length, b.length);
     a = a.padStart(maxLength , 0);
     b = b.padStart(maxLength , 0);
    var result = [];
    var overflow = 0;
    for(var i = maxLength - 1; i >= 0; i--) {
        var sum = +a[i] + (+b[i]) + overflow;
        overflow = Math.floor(sum / 10);
        if(overflow) {
            sum %= 10;
        }
        result[i] = sum;
    }

    // 最后一位
    if(overflow) {
        result.unshift(overflow);
    }
    
    // 存在符号
    if(aSign) {
        result.unshift('-');
    }

    return result.join('');
}

console.time(1)
console.log(add('9007199254740991', '1234567899999999999'))
console.timeEnd(1)

decimal js对比了下性能差不多。

3.2 减法

跟加法类似,但是有几点比较特殊。

  1. 低位对齐,逐位减,不够则借1。
  2. 符号位:
  • 一正一负转加法;
  • 都是负数转成正数减正数
  1. 小数减去大数,要转成大数减小数+负号。
function minus(a, b) {
    a = a + '';
    b = b + '';
    var aSign = getSign(a);
    var bSign = getSign(b);

    // a-(-b), -a-b转成加法
    if(aSign^bSign) {
        return add(aSign, bSign ? b.substring(1) : '-' + b);
    }
    
    // 都是负号,转成b-a
    if(aSign) {
        return minus(b, a);
    }

    // 剩下的就是正整数相减了
    var maxLength = Math.max(a.length, b.length);
    a = a.padStart(maxLength, 0);
    b = b.padStart(maxLength, 0);

    result = [];
    var overflow = 0, left;
    for(let i = maxLength - 1; i >= 0; --i) {
        left = +a[i] - (+b[i]) - overflow;        
        overflow = left < 0 ? 1 : 0;
        if(overflow) {
           left += 10;
        }
        result.unshift(left);
    }

    // 最高位也发生借1,则转成大数减小数
    if(overflow) {
        return '-' + minus(b, a);        
    }

    return result.join('')
}

3.3 相乘

相乘就是累加啊。有几点比较特殊。

  1. 出现0/NaN/Infinity,, 则返回0/NaN/Infinity;
  2. 竖式相乘+累加。
function multi(a, b) {
    a = a + '';
    b = b + '';
    var aSign = getSign(a);
    var bSign = getSign(b);

    if(aSign) {
        a = a.substring(1);      
    }
    
    if(bSign) {
        b = b.substring(1);
    }
    
    var sumTotal = 0
    for(let i = a.length - 1; i >= 0; --i) {
        // 低位补0
        var sum = Array(a.length - 1 - i).fill(0);
        var overflow = 0;
        for(let j = b.length - 1; j >= 0; --j) {
            var c = +a[i] * (+b[j]) + overflow;
            overflow = Math.floor(c/10);
            if(overflow) {
                c = c % 10;
            }
            sum.unshift(c);
        }       
        // 高位也溢出了 
        if(overflow) {
           sum.unshift(overflow); 
        }
        // 累加
        sumTotal = add(sumTotal, sum.join(''));
    }
    
    // 处理负号为
    if(aSign^bSign) {
        sumTotal = '-' + sumTotal;
    }

    return sumTotal
}

测试发现跟decimal js性能差很多,因为进行了多次大数相加处理。
有啥优化方式?Google了下原来有相关个算法leetcode 43.字符串相乘

var mul = function(num1, num2) {
  if(isNaN(num1) || isNaN(num2)) return '' //判断输入是不是数字
  var len1 = num1.length,
    len2 = num2.length
  var ans = []
  for (var i = len1 - 1; i >= 0; i--) {    //这里倒过来遍历很妙,不需要处理进位了
    for (var j = len2 - 1; j >= 0; j--) {
      var index1 = i + j
      var index2 = i + j + 1
      var mul = num1[i] * num2[j] + (ans[index2] || 0)
      ans[index1] = Math.floor(mul / 10) + (ans[index1] || 0)
      ans[index2] = mul % 10
    }
  }
  
  var result = ans.join('')
  return +result === 0 ? '0' : result.replace(/^0+/,'')
}

算法分析优化版竖式

参考

  1. 抓住数据的小尾巴 - JS浮点数陷阱及解法
  2. 该死的IEEE-754浮点数,说「约」就「约」,你的底线呢?以JS的名义来好好查查你
  1. IEEE 754 计算器
  2. IEEE 754 二进制表示
  3. MDN Number
  4. JS魔法堂:彻底理解0.1 + 0.2 === 0.30000000000000004的背后
    顺便可以复习下数字存储原码,反码,补码。

@yaofly2012
Copy link
Owner Author

undefined

一、概述

ECMAScript中Undefined(首字母大写)类型定义是:有且只有一个undefined(首字母小写)值的类型。任何没有赋值的变量的值都是undefined。

var a; 
console.log(a); // undefined

但Undefined类型只存在于规范中,实际实现中并没有定义Undefined类型
在浏览器上下文中undefined是全局变量window的成员变量(一般是只读的),既然undefined是全局属性变量,那他肯定不是保留字了。所以我们也可以定义名为undefined属性了,并且老的JS中可以重写window.undefiend属性值:

var a;
console.log(a === window.undefined); // true
;(function(){
    var undefined = 'hello';
    console.log(undefined); // hello
    console.log(a === window.undefined); // true
    console.log(a === undefined); // false
})();

二、void 0替代undefined

void运算符返回的是undefined。

var a; 
;(function(){
    var undefined = 'hello';
    console.log(a === void 0); // true
    console.log(a === undefined); // false
})();

代码中常常看到使用void 0替代undefined(打包工具也会自动转换),这是为啥呢?:
underscorejs isUndefined

 // Is a given variable undefined?
  _.isUndefined = function(obj) {
    return obj === void 0;
  };

2.1 void 0 更安全

如上文,undefined值可以作为变量的,万一值被重新了,那岂不是凌乱了。

2.2 void 0 体积小

好多代码压缩工具都会把undefined替换成void 0。字符串“void 0” 比“undefined”更短一些(额,少了3个字符)。

参考

  1. ecma-262
  2. MDN undefined

@yaofly2012
Copy link
Owner Author

练习

1. 输出以下代码的执行结果并解释为什么

var a = {n: 1};
var b = a;
a.x = a = {n: 2};

console.log(a.x) 	
console.log(b.x)

@yaofly2012 yaofly2012 changed the title JS-类型 JS-类型&类型判断 Oct 19, 2020
@yaofly2012
Copy link
Owner Author

yaofly2012 commented Oct 19, 2020

typeof 内部

根据变量的类型标签(type tag)获取类型信息的。JS是动态类型的变量。每个变量在存储时除了存储变量值还需要存储变量的类型。JS里使用32位(bit)存储变量信息。低位的1~3个bit存储变量类型信息:
mozilla

#define JSVAL_OBJECT            0x0     /* untagged reference to object */
#define JSVAL_INT               0x1     /* tagged 31-bit integer value */
#define JSVAL_DOUBLE            0x2     /* tagged reference to double */
#define JSVAL_STRING            0x4     /* tagged reference to string */
#define JSVAL_BOOLEAN           0x6     /* tagged boolean value */
.... XXXX X000 // object
.... XXXX XXX1 // int
.... XXXX X010 // double
.... XXXX X100 // string
.... XXXX X110 // boolean
  1. 只有int类型的type tag使用1个bit,并且取值为1,其他都是3个bit, 并且低位为0。这样可以通过type tag低位取值判断是否为int数据。
  2. 相当于使用2个bit区分这四个类型:object, double, string, boolean

如何标记null, undefinedfunction的呢?

  1. undefined特殊处理,赋值特殊的值(-2^30);
  2. null 采用机器码NULL指针值(取值也是0x00),这也导致了一个无法修复的bug
typeof null // "object"
  1. function
    函数也是对象(可调用对象),所以函数的type tag也是000。但是函数作为JS的一等公民,也被视为一种变量类型。typeof逻辑内部也特殊处理了:如果对象具有([[call]]内部方法)[],则返回function

typeof执行逻辑

  1. 先获取变量的类型信息;
  2. 根据类型信息返回对应的类型字符串信息,如图:
    image

获取变量类型的逻辑直接贴代码吧:

JS_TypeOfValue(JSContext *cx, jsval v)
{
    // #define JSVAL_VOID              INT_TO_JSVAL(0 - JSVAL_INT_POW2(30))
    JSType type = JSTYPE_VOID;
    JSObject *obj;
    JSObjectOps *ops;
    JSClass *clasp;

    CHECK_REQUEST(cx);
    // #define JSVAL_IS_VOID(v)        ((v) == JSVAL_VOID)
    if (JSVAL_IS_VOID(v)) {
	type = JSTYPE_VOID;
    } else if (JSVAL_IS_OBJECT(v)) {
	obj = JSVAL_TO_OBJECT(v);
	if (obj &&
	    (ops = obj->map->ops,
	     ops == &js_ObjectOps
	     ? (clasp = OBJ_GET_CLASS(cx, obj),
		clasp->call || clasp == &js_FunctionClass)
	     : ops->call != 0)) { // 具有call属性
	    type = JSTYPE_FUNCTION;
	} else {
	    type = JSTYPE_OBJECT;
	}
    } else if (JSVAL_IS_NUMBER(v)) { // int 和double都是number类型
	type = JSTYPE_NUMBER;
    } else if (JSVAL_IS_STRING(v)) {
	type = JSTYPE_STRING;
    } else if (JSVAL_IS_BOOLEAN(v)) {
	type = JSTYPE_BOOLEAN;
    }
    return type;
}

参考

  1. MDN 操作符:typeof
  2. ECMA The typeof Operator
  3. The history of “typeof null”
  4. Categorizing values in JavaScript

@yaofly2012
Copy link
Owner Author

Object.prototype.toString()内部

1. 内部属性[[Class]]

用于标记对象具体类型的描述字符串,用于区分各种对象。一般内置对象会有这个值:

"Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp", and "String".

2. Object.prototype.toString()语法

返回对象的默认的字符串表示。

思考
什么样的字符串能代表一个对象呢?
类型,对象的子类型。估计这也是返回值的格式是[object subType]的原因。

  • object表示是个对象类型。
  • subType区分各种类型的内置对象。

3. Object.prototype.toString()逻辑

  1. If the this value is undefined, return "[object Undefined]".
  1. If the this value is null, return "[object Null]".
  2. Let O be ! ToObject(this value).
  3. Let isArray be ? IsArray(O).
  4. If isArray is true, let builtinTag be "Array".
  5. Else if O has a [[ParameterMap]] internal slot, let builtinTag be "Arguments".
  6. Else if O has a [[Call]] internal method, let builtinTag be "Function".
  7. Else if O has an [[ErrorData]] internal slot, let builtinTag be "Error".
  8. Else if O has a [[BooleanData]] internal slot, let builtinTag be "Boolean".
  9. Else if O has a [[NumberData]] internal slot, let builtinTag be "Number".
  10. Else if O has a [[StringData]] internal slot, let builtinTag be "String".
  11. Else if O has a [[DateValue]] internal slot, let builtinTag be "Date".
  12. Else if O has a [[RegExpMatcher]] internal slot, let builtinTag be "RegExp".
  13. Else, let builtinTag be "Object".
  14. Let tag be ? Get(O, @@toStringTag).
  15. If Type(tag) is not String, set tag to builtinTag.
  16. Return the string-concatenation of "[object ", tag, and "]".

综上:

  1. 特殊处理null, undefined
    它俩没有对应的构造函数,无法装箱成对象
  2. 基本类型数据,转成对象
  3. 获取对象的[[Class]]内部属性值,和Symbol.toStringTag属性值"toStringTag"
  4. 如果"toStringTag"是字符串,则返回[object, toStringTag],否则返回[object, [[Class]]
Object.defineProperty(String.prototype, Symbol.toStringTag, {
    get() {
        return 'MyString'
    }
})

var a = "test"
Object.prototype.toString.call(a); // "[object MyString]"

参考

  1. MDN Object.prototype.toString()
  2. 知乎 javascript中的类型判断
  3. ecma262 Object.prototype.toString ( )

@yaofly2012
Copy link
Owner Author

yaofly2012 commented Oct 30, 2020

深入浅出JS类型判断

JS中判断数据类型的方式有很多

  • typeof
  • Object.prototype.toString
  • instanceof
  • Array.isArray

一、回顾

JS数据类型分为基本类型和引用类型。
基本类型

  • undefined
  • null
  • Number
  • String
  • Boolean
  • Symbol

引用类型

  • Object
  • Function

函数是一种特殊的对象,即可调用的对象。

二、typeof

2.1 语法

typeof操作符可以区分基本类型,函数和对象。

console.log(typeof null) // object
console.log(typeof undefined) // undefined
console.log(typeof 1) // number
console.log(typeof 1.2) // number
console.log(typeof "hello") // string
console.log(typeof true) // boolean
console.log(typeof Symbol()) // symbol
console.log(typeof (() => {})) // function
console.log(typeof {}) // object
console.log(typeof []) // object
console.log(typeof /abc/) // object
console.log(typeof new Date()) // object
  1. typeof有个明显的bug就是typeof nullobject;
  2. typeof无法区分各种内置的对象,如Array, Date等。

2.2 原理

JS是动态类型的变量,每个变量在存储时除了存储变量值外,还需要存储变量的类型。JS里使用32位(bit)存储变量信息。低位的1~3个bit存储变量类型信息,叫做类型标签(type tag)

.... XXXX X000 // object
.... XXXX XXX1 // int~~~~
.... XXXX X010 // double
.... XXXX X100 // string
.... XXXX X110 // boolean
  1. 只有int类型的type tag使用1个bit,并且取值为1,其他都是3个bit, 并且低位为0。这样可以通过type tag低位取值判断是否为int数据;
  2. 为了区分int,还剩下2个bit,相当于使用2个bit区分这四个类型:object, double, string, boolean
  3. 但是nullundefinedFunction并没有分配type tag

如何识别Function

函数并没有单独的type tag,因为函数也是对象。typeof内部判断如果一个对象实现了[[call]]内部方法则认为是函数。

如何识别undefined

undefined变量存储的是个特殊值JSVAL_VOID(0-2^30)typeof内部判断如果一个变量存储的是这个特殊值,则认为是undefined

#define JSVAL_VOID              INT_TO_JSVAL(0 - JSVAL_INT_POW2(30))

如何识别null

null变量存储的也是个特殊值JSVAL_NULL,并且恰巧取值是空指针机器码(0),正好低位bit的值跟对象的type tag是一样的,这也导致著名的bug:

typeof null // object

很不幸,这个bug也不修复了,因为第一版JS就存在这个bug了。祖传代码,不敢修改啊。

有很多方法可以判断一个变量是一个非null的对象,之前遇到一个比较经典的写法:

// 利用Object函数的装箱功能
function isObject(obj) {
    return Object(obj) === obj;
}

isObject({}) // true
isObject(null) // false

三、Object.prototype.toString

一般使用Object.prototype.toString区分各种内置对象。

3.2 语法

console.log(Object.prototype.toString.call(1)); // [object Number],隐式类型转换
console.log(Object.prototype.toString.call('')); // [object String],隐式类型转换
console.log(Object.prototype.toString.call(null)); // [object Null],特殊处理
console.log(Object.prototype.toString.call(undefined)); // [object Undefined],特殊处理
console.log(Object.prototype.toString.call(true)); // [object Boolean],隐式类型转换
console.log(Object.prototype.toString.call( {})); // [object Object]
console.log(Object.prototype.toString.call([])); // [object Array]
console.log(Object.prototype.toString.call(function(){})); // [object Function]
  1. 如果实参是个基本类型,会自动转成对应的引用类型;
    Object.prototype.toString不能区分基本类型的,只是用于区分各种对象;
  2. nullundefined不存在对应的引用类型,内部特殊处理了;

3.3 原理

内部属性[[Class]]

每个对象都有个内部属性[[Class]],内置对象的[[Class]]的值都是不同的("Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp", "String"),并且目前[[Class]]属性值只能通过Object.prototype.toString访问。

Symbol.toStringTag属性

其实Object.prototype.toString内部先访问对象的Symbol.toStringTag属性值拼接返回值的。

var a = "hello"
console.log(Object.prototype.toString.call(a)); // "[object String]"

// 修改Symbol.toStringTag值
Object.defineProperty(String.prototype, Symbol.toStringTag, {
    get() {
        return 'MyString'
    }
})

console.log(Object.prototype.toString.call(a)); // "[object MyString]"

如果哪个货偷偷修改了内置对象的Symbol.toStringTag属性值,那Object.prototype.toString也就不能正常工作了。

3.4 Object.prototype.toString内部逻辑

综上可以总结Object.prototype.toString的内部逻辑:

  1. 如果实参是undefined, 则返回"[object Undefined]";
  2. 如果实参是null, 则返回"[object Null]";
  3. 把实参转成对象
  4. 获取对象的Symbol.toStringTag属性值subType
  • 如果subType是个字符串,则返回[object subType]
  • 否则获取对象的[[Class]]属性值type,并返回[object type]

四、instanceof

4.1 语法

object instanceof constructorFunc

instanceof 操作符判断构造函数constructorFuncprototype属性是否在对象object的原型链上。

Object.create({}) instanceof Object // true
Object.create(null) instanceof Object // false

Function instanceof Object // true
Function instanceof Function // true
Object instanceof Object // true
  1. 作为类型判断的一种方式,instanceof 操作符不会对变量object进行隐式类型转换
"" instanceof String; // false,基本类型不会转成对象
new String('') instanceof String; // true
  1. 对于没有原型的对象或则基本类型直接返回false
1 instanceof Object // false
Object.create(null) instanceof Object // false
  1. constructorFunc必须是个对象。并且大部分情况要求是个构造函数(即要具有prototype属性)
// TypeError: Right-hand side of 'instanceof' is not an object
1 instanceof 1

// TypeError: Right-hand side of 'instanceof' is not callable
1 instanceof ({})

// TypeError: Function has non-object prototype 'undefined' in instanceof check
({}) instanceof (() => {})

4.2 intanceof的缺陷

不同的全局执行上下文的对象和函数都是不相等的,所以对于跨全局执行上下文intanceof就不能正常工作了。

<!DOCTYPE html>
<html>
    <head></head>
    <body>
        <iframe src=""></iframe>
        <script type="text/javascript">
            var iframe = window.frames[0];
            var iframeArr = new iframe.Array();

            console.log([] instanceof iframe.Array) // false
            console.log(iframeArr instanceof Array)  // false
            console.log(iframeArr instanceof iframe.Array)  // true       
        </script>
    </body>
</html>

4.3 原理

Symbol.hasInstance函数

instanceof操作符判断构造函数constructorFuncprototype属性是否在对象object的原型链上。但是可以利用Symbol.hasInstance自定义instanceof操作逻辑。

var obj = {}

// 自定义Symbol.hasInstance方法
Object.defineProperty(obj, Symbol.hasInstance, {
  value: function() {
      return true;
  }
});

1 instanceof obj // true

当然了这个举例没有任何实际意义。只是说明下Symbol.hasInstance的功能。Symbol.hasInstance本意是自定义构造函数判断实例对象的方式,不要改变instanceof 的含义。

原型链

4.4 instanceof内部逻辑

综上可以梳理instanceof内部逻辑

object instanceof constructorFunc
  1. 如果constructorFunc不是个对象,或则是null,直接抛TypeError异常;
  2. 如果constructorFunc[Symbole.hasInstance]方法,则返回!!constructorFunc[Symbole.hasInstance](object )
  3. 如果constructorFunc不是函数,直接抛TypeError异常;
  4. 遍历object的原型链,逐个跟constructorFunc.prototype属性比较:
  • 如果object没有原型,则直接返回false;
  • 如果constructorFunc.prototype不是对象,则直接抛TypeError异常。

五、内置的类型判断方法

5.1 Array.isArray

ES5引入了方法Array.isArray专门用于数组类型判断。Object.prototype.toStringinstanceof都不够严格

var arr = []
Object.defineProperty(Array.prototype, Symbol.toStringTag, {
    get() {
        return 'myArray'
    }
})

console.log(Object.prototype.toString.call(arr)); // [object myArray]
console.log(Array.isArray(arr)); // true

console.log(Array.prototype instanceof Array); // false
console.log(Array.isArray(Array.prototype)); // true

不过现实情况下基本都是利用Object.prototype.toString作为Array.isArray的polyfill:

if (!Array.isArray) {
  Array.isArray = function(arg) {
    return Object.prototype.toString.call(arg) === '[object Array]';
  };
}

六、内置对象的prototype属性类型判断

内置的对象Number, String, Boolean, Object, Function, Date, RegExp, Array都是各自类型对象的构造函数,并且他们的prototype属性都是各自实例对象的原型。但是这些内置对象的prototype属性又是什么类型呢?

6.1 Number.prototype

Number.prototype也是个数字,类似Number(0),但是Number.prototype并不是Number的实例。

var prototype = Number.prototype

console.log(prototype == 0); // true

console.log(prototype instanceof Number); // false
console.log(Object.prototype.toString.call(protoype)); // [object Number]

6.2 String.prototype

String.prototype也是字符串,类似"",但是String.prototype并不是String的实例。

var prototype = String.prototype

console.log(prototype == ''); // true
console.log(prototype instanceof String); // false
console.log(Object.prototype.toString.call(prototype)); // [object String]

6.3 Boolean.prototype

Boolean.prototype也是Boolean,类似false,但是Boolean.prototype并不是Boolean的实例。

var prototype = Boolean.prototype

console.log(prototype == false); // true
console.log(prototype instanceof Boolean); // false
console.log(Object.prototype.toString.call(prototype)); // [object Boolean]

6.4 Object.prototype

Object.prototype也是Object,类似Object.create(null)的值(原型为null的空对象),但是Object.prototype并不是Object的实例。

var prototype = Object.prototype

Object.getPrototypeOf(prototype); // null
console.log(prototype instanceof Object); // false
console.log(Object.prototype.toString.call(prototype)); // [object Object]

6.5 Function.prototype

Function.prototype也是Function,是个空函数,但是Function.prototype并不是Function的实例。

var prototype = Function.prototype

console.log(prototype()) // undefined
console.log(prototype instanceof Function); // false
console.log(Object.prototype.toString.call(prototype)); // [object Function]

6.6 Array.prototype

Array.prototype也是Array,是个空数组,但是Array.prototype并不是Array的实例。

var prototype = Array.prototype

console.log(prototype instanceof Array); // false
console.log(Array.isArray(prototype)) // true
console.log(Object.prototype.toString.call(prototype)); // [object Array]

6.6 RegExp.prototype

RegExp.prototype并不是RegExp的实例。但是关于RegExp.prototypeRegExp还是对象存在兼容性问题,有些浏览器下RegExp.prototype也是RegExp,并且是个总返回true的正则。

var prototype = RegExp.prototype
console.log(prototype.test('hello')) // true
console.log(prototype instanceof RegExp); // false
// Chrome v84返回"[object Object]", IE返回"[object RegExp]"
console.log(Object.prototype.toString.call(prototype)); // 

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

1 participant