资讯专栏INFORMATION COLUMN

你不知道的JavaScript中卷 第一、二章

levy9527 / 1495人阅读

摘要:表达式没有返回值,因此返回结果是。并不改变表达式的结果,只要让表达式不返回值按惯例我们用来获得这主要源自语言,当然使用或其他表达式也是可以的。不是数字的数字如果数学运算的操作数不是数字类型,就无法返回一个有效的数字,这种情况下返回值为。

这里的内容是读书笔记,仅供自己学习所用,有欠缺的地方欢迎留言提示。

第一部分 类型和语法

第1章 类型
ECMAScript语言类型包括Undefined、Null、Boolean、String、Number和Object。
类型:对语言引擎和开发人员来说,类型是值得内部特征,它定义了值得行为,以使其区别于其他值。
喜欢强类型(又称静态类型)语言得人也许回认为“类型”一词用在这里不妥。

1.1 类型
强制类型转换是JavaScript开发人员最头疼得问题之一。

1.2 内置类型
JavaScript有七种内置内容(后面跟着typeof的类型值):

空置 null     "object"

未定义 undefined     "undefined"

布尔值 boolean     "boolean"

数字 number     "number"

字符串 string    "string"

对象 object     "object"

符号 symbol (ES6中新增)    "symbol"

除了对象值类,其他统称为“基本类型”,可以用typeof运算符来查看值得类型,它返回得是类型的字符串值,有意思的是,这七种类型和它们的字符串值并不一一对应。最特殊的就是null,但这个bug在JavaScript中存在了将近二十年,也许永远也不会修复了,因为这牵涉到了太多的Web系统,“修复”它会产生更多的bug,令许多系统无法正常工作。
所以需要使用符合条件来检测null值得类型:

let a = null;
(!a && typeof a === "object"); // true

function和数组实际上都是object的一个“子类型”,所以用typeof获取类型值时,返回都是"object"。
tip:判断是否为null,用(!a && typeof a === "object")来判断。

1.3 值和类型
JavaScript中的变量是没有类型的,只有值才有。变量可以随时持有任何类型的值。
换个角度来理解就是,JavaScript不做“类型强制”;也就是说,语言引擎不要求变量总是持有与其初始值同类型的值。
在对变量执行typeof操作时,得到的结果并不是该变量的类型,而是该变量持有的值的类型。

typeof typeof 42; // "string"

typeof 42首先返回字符串"number",然后typeof "number" 返回"string"。

1.3.1 undefined和undeclard
变量在未持有值的时候为undefined。此时typeof返回"undefined":
undeclared(未声明),undefined与undeclared是完全不一样的。
已在作用域中声明但还没有赋值的变量,是undefined的;相反,还没有在作用域中声明过的变量,是undeclared的。

let a;
a; // undefiend
b; // ReferenceError: b is not defined

浏览器对这类情况的处理很让人抓狂,"b is not defined"容易让人误以为是"b is undefired"。这里再明确一次,"undefined"和"is not defined"是两码事。此时如果浏览器报错成"b is not find"或者"b is not declared"会更明确。
更让人抓狂的是typeof处理undeclared变量的方式,如下:

let a;
typeof a; // "undefined"
typeof b; // "undefined"  而且还没有报错

对于undeclared(或者not defined)变量,typeof照样返回"undefined"。请注意虽然b是一个undeclared变量,但typeof b 并没有报错。这是因为typeof有一个特殊的安全防范机制。
与undeclared变量不同,访问不存在的对象属性(设置是在全局对象window上)不会产生ReferenceError错误。

1.4 小结
JavaScript有七种内置类型:null、undefined、boolean、number、string、object和symbol,可以使用typeof来查看。
变量没有类型,但它们持有的值有类型。类型定义了值的行为特征。
很多开发人员将undefined和undeclared混为一谈,但在JavaScript中它们是两码事。undefined是值的一种。undeclared则表示变量还没有被声明过。
遗憾的是,JavaScript却将它们混为一谈,在我们试图访问"undeclared"变量时这样报错:ReferenceError: a is not defined,并且typeof对undefined和undeclared变量都返回"undefined"。
然而,通过typeof的安全防范机制(阻止报错)来检查undeclared变量,有时是个不错的方法。

第2章 值
数组(array)、字符串(string)和数字(number)是一个程序最基本的组成部分,但在JavaScript中,它们可谓让人喜忧参半。

2.1 数组
和其他强类型语言不同,在JavaScript中,数组可以容纳任何类型的值,可以是字符串、数字、对象(object),甚至是其他数组(多维数组就是通过这种方式来实现的)。
对数组生命后即可向其中加入值,不需要预先设定大小。
需要注意的是,使用delete运算符可以将单元从数组中删除,但是请注意,单元删除后,数组的length属性并不会发生变化。
数组通过数字进行索引,但有趣的是它们也是对象,所以也可以包含字符串键值和属性(但这些并不计算在数组长度内)。

类数组
有时需要将数组(一组通过数字索引的值)转换为真正的数组,这一般通过数组工具函数(如indexOf(..)、contat(..)、forEach(..)等)来实现。

2.2 字符串
字符串经常被当成字符数组。字符串的内部实现究竟有没有数组炳皓说,但JavaScript中的字符串和字符数组并不是一个回事,最多只是看上去相似而已。
字符串和数组的确很相似,它们都是类数组,都有length属性以及indexOf(..)(从ES5开始数组支持此方法)和concat(..)方法。

let a = "foo";
let b = ["f", "o", "o"];
a.length; // 3
b.length; // 3
a.indexOf("o"); // 1
b.indexOf("o"); // 1
let c = a.concat("bar"); // "foobar"
let d = b.concat(["b", "a", "r"]); // ["f", "o", "o", "b", "a", "r"]

a === c; // false
b === d; // false 对象、数组存的是地址

a[1] = "O";
b[1] = "O";
a; // "foo"
b; // ["f", "o", "o"]

JavaScript中字符串是不可变的,而数组是可变的。并且a[1]在JavaScript中并非总是合法语法,在老版本的IE中就不被允许(现在可以了)。正确的方法应该是a.charAt(1)。
字符串不可变是指字符串的成员函数不会改变其原始值,而是创建并返回一个新的字符串。而数组的成员函数在其原始值上进行操作。

c = a.toUpperCase();
a === c; // false
a; // "foo"
c; // "FOO"

许多数组函数用来处理字符串很方便。虽然字符串没有这些函数,但可以通过“借用”数组的非变更方法来处理字符串。

a.join; // undefined
a.map; // undefined
let c = Array.prototype.join.call(a, "-");
let d = Array.prototype.map.call(a, function(v) {
    return v.toUpperCase() + ".";
}).join("");
c; // "f-o-o"
d; // "F.O.O."

另一个不同点在于字符串反转。数组有一个字符串没有的可变更成员函数reverse():

a.reverse(); // undefined
b.reverse(); // ["o", "o", "f"]

// 可惜我们无法"借用"数组的可变更成员函数,因为字符串是不可变的:
Array.prototype.reverse.call(a); // 返回值仍然是字符串"foo"的一个封装对象

tip:通过Array.ptototype.xxxx.call(str)的方式可以让字符串使用数组函数,但是reverse()不适用。
一个变通(破解)的方法是先将字符串转换为数组,待处理完后再将结果换回字符串:

let c = a
    // 将a的值转换为字符串数组
    .split("")
    // 将数组中的字符进行倒转
    .reverse()
    // 将数组中的字符拼接回字符串
    .join("");
c; // "oof"
// 这种方法简单粗暴,但对简单的字符串却完全适用。
// 对于包含复杂字符(Unicode,如星号、多字节字符等)的字符串并不适用。

tip:对于大多数字符串反转,可以用str.split().reverse().join("")的方法。

2.3 数字
JavaScript只有一种数值类型:number(数字),包括“整数”和带小数的十进制数。此处“整数”之所以加引号是因为和其他语言不同,JavaScript没有真正意义上的整数,这也是它一直依赖为人诟病的地方。
JavaSctipt中的“整数”就是没有小数的十进制数。所以42.0即等同于“整数”42。

2.3.1 数字的语法
JavaScript中的数字常量一般用十进制表示。例如:

let a = 42;
let b = 42.3;

// 数字前面的0可以省略
let a = 0.42;
let b = .42;

// 小数点后小数部分最后面的0也可以省略
let a = 42.0;
let b = 42.;

特别大和特别小的数字默认用指数格式显示,与toExponential()函数的输出结果相同。例如:

let a = 5E10;
a; // 50000000000
a.toExponential(); // "5e+10" 这个是字符串

a == a.toExponential(); // true
a === a.toExponential(); // false

由于数字值可以使用Number对象进行封装,因此数字值可以调用Number.prototype中的方法。例如,toFixed(..)方法可以指定小数部分的显示位数。
toPrecision(..)方法用来指定有效数位的显示位数。

let a = 42.59;
a.toFixed(0); // "43"
a.toFixed(1); // "43.6"
a.toPrecision(1); // "4e+1"

a.toFixed(2); // "42.6"
a.toPrecision(2); // "42.59"

a.toFixed(3); // "42.590"
a.toPrecision(3); // "42.6"

a.toPrecision(4); // "42.59"
a.toPrecision(5); // "42.590"

2.3.2 较小的数值
二进制浮点数最大的问题(不仅JavaScript,所有遵循IEEE754规范的语言都是如此),是回出现如下情况:

0.1 + 0.2 === 0.3; // false

从数学角度来说,上面的条件判断应该为true,可结果为什么是false呢?
简单来说,二进制浮点数中的0.1和0.2并不是十分精确,它们相加的结果并非刚好等于0.3,而是一个比较接近的数字0.3000000000000004,所以条件判断结果为false。
问题是,如果一些数字无法做到完全精确,是否意味着数字类型毫无用处呢?答案当然是否定的。
在处理带有小数的数字时需要特别注意。很多(也许是绝大多数)程序只需要处理整数,对打不超过百万或者万亿,此时使用JavaScript的数字类型是绝对安全的。
那么应该怎样来判断0.1+0.2和0.3是否相等呢?
最常见的方法是设置一个误差范围值,通常称为“机器精度”,对JavaScript的数字来说,这个值通常是2^-52。从ES6开始,该值定义在Number.EPSILON中,我们可以直接拿来用,也可以在ES6之前的版本写polyfill:

function numbersCloseEnoughToEqual(n1, n2) {
    return Math.abs(n1 - n2) < Number.EPSILON;
}
let a = 0.1 + 0.2;
let b = 0.3;
numbersCloseEnoughToEqual(a, b); // true

tip: 小数位运算,因浮点存储的原因会造成误差,可以用小于Number.EPSILON来判断是否正确。

2.3.3 整数的安全范围
数字的呈现方式决定了“整数”的安全值范围远远小于Number.MAX_VALUE。
能够被“安全”呈现的最大整数是2^53 - 1,即9007199254740991,在ES6中被定义为Number.MAX_SAFE_INTEGER。最小整数时-9007199254740991,在ES6中被定义为Number.MIN_SAFE_INTEGER。

2.3.4 整数检测
要检测一个值是否是整数,可以使用ES6中的Number.isInteger(..)方法。

Number.isInteger(42); // true
Number.isInteger(42.0); // true
Number.isInteGer(42.3); // false

检测一个值是否是安全的整数,可以使用ES6中的Number.isSafeInteger(..)方法:

Number.isSafeInteger(Math.pow(2, 53)); // false
Number.isSafeInteger(Math.pow(2, 53) - 1); // true

2.3.5 32位有符号整数
虽然整数最大能够达到53位,但是有些数字操作(如整位操作)只适用于32位数字,所以这些操作中数字的安全范围就要小很多,变成从Math.pow(-2, 31)到Math.pow(2, 31) - 1。
a | 0可以将变量a中的数值转换为32位有符号整数,因为整位运算符|只适用于32位整数(它只关心32位以内的值,其他的数位将被忽略)。因此与0进行操作即可截取a中的32位数位。

2.4 特殊数值
JavaScript数据类型中有几个特殊的值需要开发人员特别注意和小心使用。

2.4.1 不是值的值
undefined类型只有一个值,即undefined。null类型也只有一个值,即null。它们的名称既是类型也是值。undefined和null常被用来表示“空的”或是“不是值”的值。二至之间有一些细微的差别。例如:

null 值空值(empty value)

undefined 指没有值(missing value)

或者

undefined 指从未赋值

null 指曾赋过值,但是目前没有值

null是一个特殊关键字,不是标识符,我们不能将其当作变量来使用和赋值。然而undefined却是一个标识符,可以被当作变量来使用和赋值。

2.4.2 undefined
在非严格模式下,我们可以位全局标识符undefined赋值(这样的设计实在是欠考虑!):

function foo() {
    undefined = 2; // 非常糟糕的做法!
}
function foo() {
    "use strict";
    undefined = 2; // TypeError!
}

void运算符
undefined是一个内置标识符(除非被重新定义),它的值为undefined,通过void运算符既可得到该值。
表达式void没有返回值,因此返回结果是undefined。void并不改变表达式的结果,只要让表达式不返回值:

let a = 42;
console.log(void a, a); // undefined 42

按惯例我们用void 0 来获得undefined(这主要源自C语言,当然使用void true或其他void 表达式也是可以的)。void 0,void 1和undefined之间并没有实质上的区别。

2.4.3 特殊的数字
数字类型中有几个特殊的值。

不是数字的数字

如果数学运算的操作数不是数字类型,就无法返回一个有效的数字,这种情况下返回值为NaN。
NaN意指“不是一个数字”(not a number),这个名字容易引起误会。将它理解为“无效数值”或者“坏数值”可能更准确些。

let a = 2 / "foo"; // NaN
typeof a === "number"; // true

tip:typeof并不能完全判断是否为数字类型,还包括NaN。
换句话说,“不是数字的数字”仍然是数字类型。
NaN是一个“警戒值”(有特殊用途的常规值),用于指出数字类型中的错误情况,即“执行数学运算没有成功,这是失败后返回的结果。”
也许有人认为如果要检查变量的值是否为NaN,可以直接和NaN进行比较,就像比较null和undefeind那样,实则不然。

null === null; // true
undefined = undefined; // true
let a = 2 / "foo";
a == NaN; //false

NaN是一个特殊值,他和自身不相等,是唯一一个非自反(自反,reflexive,即x === x不成立)的值。而NaN != NaN竟然为true。
tip: NaN是唯一一个非自反的值。
既然我们无法对NaN进行比较(结果永远为false),那应该怎样来判断它呢?可以使用内建的全局工具函数isNaN(..)来判断一个值是否是NaN。

let a = 2 / "foo";
isNaN(a); // true

isNaN(..)有一个严重的缺陷,它的检查方式过于死板,就是“检查参数是否不是NaN,也不是数字”。这样做的结果并不太准确。

let a = 2 / "foo";
let b = "foo";
window.isNaN(a); // true
window.isNaN(b); // true  ????? "foo"不是一个数字,但是它也不是NaN
// 从ES6开始,使用工具函数Number.isNaN(..)
Number.isNaN(a); // true
Number.isNaN(b); // false 

tip:判断是否为NaN,用Number.isNaN(..)来判断;也可以用非自反来判断。

2.无穷数
熟悉传统编译型语言(如C)的开发人员可能都遇到过编译错误(compiler error)或者运行时错误(runtime exception),例如“除以0”:

let a = 1 / 0;

然而在JavaScript中上例的结果为Infinity(即Number.POSITIVE_INFINITY)。
如果除法运算中的一个操作数为负数。则结果为-Infinity(即Number.NEGATIVE_INFINITY)。

let a = 1 / 0; // Infinity
let b = -1 / 0; // -Infinity
Infinity === Infinity; // true

3.零值
JavsScript有一个常规的0(也叫做+0)和一个-0。
加法和减法运算不会得到负零。
tip: JSON.stringify(-0)返回"0",而JSON.parse("-0")返回-0。

-0 === 0 ; // true emmm,有待深究

2.4.4 特殊等式
NaN和-0在相等比较时的表现有些特别。由于NaN和自身不相等,所以必须使用ES6中的Number.isNaN(..),而-0等于0(对于===也是如此),因此我们必须使用isNegZero(..)这样的工具函数。
ES6中新加入了一个工具方法Object.is(..)来判断两个值是否绝对相等,可以用来处理上述所有的特殊情况:

let a = 2/ "foo";
let b = -3 *0;
Object.is(a, NaN); // true
Object.is(b, -0); // true
Object.is(b, 0); // false

tip: 能使用==和===时就尽量不要使用Object.is(..),因为前者效率更高、更为通用,后者主要用来处理那些特殊的相等比较。

2.5 值和引用
在许多编程语言中,赋值和参数传递可以通过值复制(value-copy)或者引用复制(reference-copy)来完成,这取决于我们使用什么语法。
JavaScript引用指向的是值。如果一个值有10个引用,这些引用指向的都是同一个值,它们相互之间没有应用/指向关系。
JavaScript对值和引用的赋值/传递在语法上没有区别,完全根据值的类型来决定。

let a = 2;
let b = 2; // b是a的值的一个副本
b++;
a; // 2
b; // 3

let c = [1, 2, 3];
let d = c; // d是[1, 2, 3]的一个引用
d.push(4);
c; // [1, 2, 3, 4]
d; // [1, 2, 3, 4]

简单值(即标量基本类型值)总是通过值复制的方式来赋值/传递,包括null、undefined、字符串、数字、布尔和ES6中的symbol。
复合值——对象(包括数组和封装对象)和函数,则总是通过引用复制的方式来赋值/传递。
由于引用指向的是值本身而非变量,所以一个引用无法更改另一个引用的指向。

let a = [1, 2, 3];
let b = a;
b = [4, 5, 6]; // 因为赋值,b改变了自己的引用,并不会改变a的引用,所以b变了,而a没有。
a; // [1, 2, 3]
b; // [4, 5, 6]

2.6 小结
JavaScript中的数组是通过数字索引的一组任意类型的值。字符串和数组类似,但是它们的行为特征不同,在将字符作为数组来处理时需要特别小心。JavaScript中的数字包括“整数”和“浮点型”。
基本类型中定义了几个特殊的值。
null类型只有一个值null,undefined类型也只有一个值undefined。所有变量在赋值之前默认值都是undefined。void运算符返回undefined。
数字类型有几个特殊值,包括NaN(意指“not a number”,更准确地说是“invalid number”)、+Infinity、--Infinity和-0。
简单标量基本类型值(字符串和数字等)通过值复制来赋值/传递,而复合值(对象等)通过引用复制来赋值/传递。
JavaScript中的引用和其它语言中的引用/指针不同,它们不能指向别的变量/引用,只能指向值。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/110012.html

相关文章

  • 精读《你不知道javascript中卷)》

    摘要:强制类型转换本章介绍了的数据类型之间的转换即强制类型转换包括显式和隐式。强制类型转换常常为人诟病但实际上很多时候它们是非常有用的。隐式强制类型转换则没有那么明显是其他操作的副作用。在处理强制类型转换的时候要十分小心尤其是隐式强制类型转换。 前言 《你不知道的 javascript》是一个前端学习必读的系列,让不求甚解的JavaScript开发者迎难而上,深入语言内部,弄清楚JavaSc...

    李世赞 评论0 收藏0
  • 思维导图—你不知道JavaScript中卷

    xmind地址 预览 showImg(https://segmentfault.com/img/bVbfhBm?w=1525&h=3460);

    CastlePeaK 评论0 收藏0
  • 重读你不知道JS (上) 第一二章

    摘要:词法作用域定义在词法阶段的作用域由你在写代码时将变量和块作用域写在哪来决定的,因此当词法分析器处理代码时会保持作用域不变。欺骗词法作用域在词法分析器处理过后依然可以修改作用域。 你不知道的JS(上卷)笔记 你不知道的 JavaScript JavaScript 既是一门充满吸引力、简单易用的语言,又是一门具有许多复杂微妙技术的语言,即使是经验丰富的 JavaScript 开发者,如果没...

    baihe 评论0 收藏0
  • 掌握 Javascript 类型转换:隐式转换救救孩子

    摘要:看下面的代码和会对操作数执行条件判断,如果操作数不是布尔值,会先执行类型转换后再执行条件判断。大家记住这个规则布尔值如果与其他类型进行抽象比较,会先用将布尔值转换为数字再比较。 在上一篇中我们聊过了 JS 类型转换的规则和我发现的一些常见书籍中关于类型转换的一些小错误,当碰到显示类型转换的时候大家可以按照这些规则去拆解出答案。但 JS 中存在一些很隐晦的隐式类型转换,这一篇就来谈下我对...

    weapon 评论0 收藏0
  • 掌握 Javascript 类型转换:从规则开始

    摘要:首先,为了掌握好类型转换,我们要理解一个重要的抽象操作为什么说这是个抽象操作呢因为这是内部才会使用的操作,我们不会显示调用到。基本规则中的类型转换总是返回基本类型值,如字符串数字和布尔值,不会返回对象和函数。 Javascript 里的类型转换是一个你永远绕不开的话题,不管你是在面试中还是工作写代码,总会碰到这类问题和各种的坑,所以不学好这个那是不行滴。关于类型转换我也看过不少的书和各...

    mikyou 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<