note icon indicating copy to clipboard operation
note copied to clipboard

JS-类型&类型判断

Open yaofly2012 opened this issue 6 years ago • 7 comments

一、类型种类

基于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 avatar Feb 09 '20 08:02 yaofly2012

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 avatar Feb 09 '20 13:02 yaofly2012

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 avatar Feb 09 '20 13:02 yaofly2012

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 avatar Feb 09 '20 13:02 yaofly2012

练习

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

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

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

yaofly2012 avatar Feb 12 '20 10:02 yaofly2012

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 avatar Oct 19 '20 12:10 yaofly2012

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 avatar Oct 19 '20 13:10 yaofly2012

深入浅出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)); // 

yaofly2012 avatar Oct 30 '20 09:10 yaofly2012