关于js,几个你不知道的小问题

一、为什么有的编程规范要求用 void 0 代替 undefined?

Undefined类型表示未定义,它的类型只有一个值, 就是undefined。任何变星在赋值前是Undefined类型、值为undefined,一般我们可以用全局变量undefined (就是名为 undefined的这个变量)来表达这个值,或者 void运算来把任一个表达式变成 undefined值。

但是呢,因为JavaScript的代码undefined是一个变量, 而并非是一个关键字, 这是JavaScript语言公认的设计失误之一,所以,我们为了避免无意中被篡改,我建议使用void 0来获取undefined值。

Undefined跟null有一定的表意差别,null 表示的是:“定义了但是为空" 。所以,在实际编程时,我们一般不会把变量赋值为undefined,这样可以保证所有值为undefined的变量,都是从未赋值的自然状态。

二、字符串有最大长度吗?

String用于表示文本数据。

String 有最大长度是2^53- 1

这在一般开发中都是够用的,但是有趣的是,这个所谓最大长度,并不完全是你理解中的字符数。

因为String的意义并非"字符串”,而是字符串的UTF16编码,我们字符串的操作charAt、charCodeAt、length 等方法针对的都是UTF16编码。所以,字符串的最大长度,实际上是受字符串的编码长度影响的。

注意:字符是以Unicode 表示的,每一个Unicode 的码点表示一个字符。理论上Unicode的范围是无限的。UTF 是Unicode 的编码方式,规定了码点在计算机中的表示方法,常见的有UTF16和UTF8。


JavaScript中的字符串是永远无法变更的,一旦字符串构造出来,无法用任何方式改变字符串的内容,所以字符串具有值类型的特征。

JavaScript字符串把每个UTF16单元当作一个字符来处理,所以处理非BMP (超出U+0000-U+FFFF范围)的字符时,你应该格外小心。

JavaScript这个设计继承自Java,最新标准中是这样解释的,这样设计是为了“性能和尽可能实现起来简单”。因为现实中很少用到BMP之外的字符。

三、0.1+0.2=0.3吗?

JavaScript中的Number类型有18437736874454810627(即264-253+3)个值。

JavaScript中的Number类型基本符合IEEE 754-2008规定的双精度浮点数规则,但是
JavaScript为了表达几个额外的语言场景(比如不让除以0出错,而引入了无穷大的概念) ,规定了几个例外情况:

  • NaN,占用了9007199254740990,这原本是符合 IEEE 规则的数字
  • Infinity:无穷大
  • -Infinity:负无穷大

另外,值得注意的是, JavaScript中有+0和-0,在加法类运算中它们没有区别,但是除法的场合则需要特别留意区分,”忘记检测除以 -0,而得到负无穷大”的情况经常会导致错误,而区分+0和-0的方式,正是检测1/x是Infinity还是-Infinity。

根据双精度浮点数的定义,Number 类型中有效的整数范围是-0x1 ffffff至01ffffffff所以Number无法精确表示此范围外的整数。

同样根据浮点数的定义,非整数的Number类型无法用== (===也不行)来比较, 一段著名的代码,这也正是我们第三题的问题,为什么在JavaScript中,0.1 +0.2不能=0.3:

console.log( 0.1 + 0.2 == 0.3)//false

这里输出false。说明两边不相等,这是浮点运算的特点。浮点运算的精度问题导致等式左右的结果并不是严格相等,而是相差了个微小的值。

所以在这里错误的是比较的方法,正确的比较方法是使用js提供的最小精度值:

  console.log( Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON);
检查等式左右两边差的绝对值是否小于最小精度,才是正确的比较浮点数的方法。

四、ES6的Symbol是个什么东西?

是ES6引入的新类型,是一切非字符串的对象key的集合。

Symbol 可以具有字符串类型的描述,但是即使描述相同,Symbol也不相等

创建symbol 是使用全局的symbol函数

    var mySymbol = Symbol("my symbol");

五、为什么给对象添加的方法能用在基本类型上?

在js中,对象的定义是“属性的集合””。属性分为数据属性和访问器属性,二者都是key-value 结构,key可以是字符串或是Symbol类型。

提到对象,我们必须要提到一个概念:类。

因为C++和Java的成功,在这两门语言中,每个类都是一个类型, 二者几乎等同,以至于很多人常常会把JavaScript的“类”与类型混淆。

事实上,JavaScript中的”类” 仅仅是运行时对象的-个私有属性,而JavaScript中是无法自定义类型的。

JavaScript中的几个基本类型,都在对象类型中有一个"亲戚”。它们是

  • number
  • string
  • boolean
  • symbol

所以,我们必须认识到3与new Number(3)是完全不同的值,它们一个是Number类型,一个是对象类型。
Number、String 和Boolean,三个构造器是两用的,当跟new搭配时,它们产生对象,当直接调用时,它们表示强制类型转换。
Symbol函数比较特殊,直接用new调用它会抛出错误,但它仍然是Symbol对象的构造器。
JavaScript语言设计上试图模糊对象和基本类型之间的关系,我们日常代码可以把对象的方法在基本类型上使用,比如:

    console.log("abc".charAt(0)); //a

甚至在原型上添加方法,都可以应用于基本类型,

//在symbol原型上添加了hello方法,在任何symbol类型变量都可以调用
    Symbol.prototype.hello = () => console.log("hello");

    var a = Symbol("a");
    console.log(typeof a); //symbol,a 并非对象
    a.hello(); //hello,有效
运算符提供了装箱操作,他会根据基础类型构造一个临时对象,使得我们能在基础类型上调用相应对象的方法

装箱转换

基本类型—>对象

每一种基本类型Number、String、 Boolean、 Symbol 在对象中都有对应的类,所谓装箱转换,正是把基本类型转换为对应的对象,它是类型转换中-种相当重要的种类。

产生装箱的方法 call

前文提到,全局的Symbol函数无法使用new来调用,但我们仍可以利用装箱机制来得到-个Symbol对象,我们可以利用一个函数的 call 方法来强迫产生装箱。

我们定义一个函数,函数里面只有return this,然后我们调用函数的call方法到一个Symbol类型的值上,这样就会产生-个symbolObject。

我们可以用console.log看一下这个东西的type of,它的值是object,我们使用 symbolObject instanceof 可以看到,它是Symbol这个类的实例,我们找它的 constructor 也是等于 Symbol 的,所以我们无论从哪个角度看,它都是Symbol装箱过的对象:

    var symbolObject = (function(){ return this; }).call(Symbol("a"));

    console.log(typeof symbolObject); //object
    console.log(symbolObject instanceof Symbol); //true
    console.log(symbolObject.constructor == Symbol); //true

装箱产生的缺点及解决办法(使用内置的object函数)

装箱机制会频繁产生临时对象,在一些对性能要求较高的场景下,我们应该尽量避免对基本类型做装箱转换。
使用内置的Object函数,我们可以在JavaScript代码中显式调用装箱能力。

    var symbolObject = Object(Symbol("a"));

    console.log(typeof symbolObject); //object
    console.log(symbolObject instanceof Symbol); //true
    console.log(symbolObject.constructor == Symbol); //true

每一类对象都有私有的class 属性,这些属性可以用Object.prototype.toString获取:

    var symbolObject = Object(Symbol("a"));

    console.log(Object.prototype.toString.call(symbolObject)); //[object Symbol]

注意:
  1. 在JavaScript中,没有任何方法可以更改私有的Class属性,因此Object.prototype.toString是可以准确识别对象对应的基本类型的方法,它比instanceof更加准确。
  2. 但需要注意的是,call 本身会产生装箱操作,所以需要配合typeof来区分基本类型还是对象类型。

拆箱转换ToPrimitive

对象类型—>基本类型
对象到String和Number的转换都遵循“先拆箱再转换”的规则。

通过拆箱转换,把对象变成基本类型,再从基本类型转换为对应的String或者Number。

拆箱转换会尝试调用valueOf和toString来获得拆箱后的基本类型。如果valueOf和toString都不存在,或者没有返回基本类型,则会产生类型错误TypeError。

    var o = {
        valueOf : () => {console.log("valueOf"); return {}},
        toString : () => {console.log("toString"); return {}}
    }

    o * 2
    // valueOf
    // toString
    // TypeError

我们定义了一个对象o, o有valueOf和toString两个方法,这两个方法都返回一个对象,然;后我们进行o*2这个运算的时候,你会看见先执行了valueOf,接下来是toString,最后抛出了-个TypeError,这就说明了这个拆箱转换失败了。

到String的拆箱转换会优先调用toString。我们把刚才的运算从o*2换成String(o),那么你会看到调用顺序就变了。

    var o = {
       valueOf : () => {console.log("valueOf"); return {}},
       toString : () => {console.log("toString"); return {}}
   }

  String(o)
   // toString
   // valueOf
   // TypeError

在ES6之后,还允许对象通过显示指定 @@toprimitive Symbol 来覆盖原有的行文
    var o = {
        valueOf : () => {console.log("valueOf"); return {}},
        toString : () => {console.log("toString"); return {}}
    }

    o[Symbol.toPrimitive] = () => {console.log("toPrimitive"); return "hello"}


    console.log(o + "")
    // toPrimitive
    // hello

猜你喜欢

转载自blog.csdn.net/Welkin_qing/article/details/88546321