朝花夕拾,从新介绍javascript类型

任何编程语言都不可缺乏的组成部分——“类型”
ps: 这篇文章大概会浪费你10-15分钟的时间~javascript

动态类型

JavaScript 是一种弱类型或者说动态语言。这意味着你不用提早声明变量的类型,在程序运行过程当中,类型会被自动肯定。这也意味着你可使用同一个变量保存不一样类型的数据:前端

var foo = 42;    // foo is a Number now
foo = "bar"; // foo is a String now
foo = true;  // foo is a Boolean now
复制代码

JavaScript 中的类型包括:java

你可能还常常见到并使用 undefined(未定义)类型和 null(空)类型。此外还有Array(数组)类型,以及分别用于表示日期和正则表达式的 Date(日期)和 RegExp(正则表达式),这三种类型都是特殊的对象。严格意义上说,Function(函数)也是一种特殊的对象。因此准确来讲,JavaScript 中的类型应该包括这些:node

7 种原始类型:正则表达式

Object(对象)编程

原始值( primitive values )数组

除 Object 之外的全部类型都是不可变的(值自己没法被改变),所以称这些类型的值为“原始值”。浏览器

数字

JavaScript 采用“遵循 IEEE 754 标准的双精度 64 位格式”表示数字(-(2^53 -1) 到 2^53 -1)。和其余编程语言(如 C 和 Java)不一样,JavaScript 不区分整数值和浮点数值,全部数字在 JavaScript 中均用浮点数值表示,因此在进行数字运算的时候要特别注意。看看下面的例子:安全

0.1 + 0.2 = 0.30000000000000004
复制代码

注意,除了上述例子以外,还有 0.1+0.7 = 0.7999999999999999数据结构

而除此以外,0.1 加上其余 0.3, 0.4, ..., 0.9都是正常的

在具体实现时,整数值一般被视为32位整型变量,在个别实现(如某些浏览器)中也以32位整型变量的形式进行存储,直到它被用于执行某些32位整型不支持的操做,这是为了便于进行位操做。

数字类型中只有一个整数有两种表示方法: 0 可表示为 -0 和 +0("0" 是 +0 的简写)。 在实践中,这也几乎没有影响。 例如 +0 === -0 为真。 可是,你可能要注意除以0的时候:

42 / +0; // Infinity
42 / -0; // -Infinity
复制代码

JavaScript 支持标准的算术运算符,包括加法、减法、取模(或取余)等等。还有一个内置对象 Math(数学对象),用以处理更多的高级数学函数和常数:

Math.sin(3.5);
var circumference = 2 * Math.PI * r;
复制代码

你可使用内置函数 parseInt() 将字符串转换为整型。该函数的第二个可选参数表示字符串所表示数字的基(进制):

parseInt("123", 10); // 123
parseInt("010", 10); // 10
复制代码

这是由于字符串以数字 0 开头,parseInt()函数会把这样的字符串视做八进制数字;同理,0x开头的字符串则视为十六进制数字。

若是想把一个二进制数字字符串转换成整数值,只要把第二个参数设置为 2 就能够了:

parseInt("11", 2); // 3
复制代码

JavaScript 还有一个相似的内置函数 parseFloat(),用以解析浮点数字符串,与parseInt()不一样的地方是,parseFloat() 只应用于解析十进制数字。

单元运算符 + 也能够把数字字符串转换成数值:

+ "42";   // 42
+ "010";  // 10
+ "0x10"; // 16
复制代码

若是给定的字符串不存在数值形式,函数会返回一个特殊的值 NaN(Not a Number 的缩写):

parseInt("hello", 10); // NaN
复制代码

要当心NaN:若是把 NaN 做为参数进行任何数学运算,结果也会是 NaN

NaN + 5; //NaN
复制代码

可使用内置函数 isNaN() 来判断一个变量是否为 NaN

isNaN(NaN); // true
复制代码

注意: parseInt()parseFloat() 函数会尝试逐个解析字符串中的字符,直到赶上一个没法被解析成数字的字符,而后返回该字符前全部数字字符组成的数字。然而若是使用运算符 "+", 只要字符串中含有没法被解析成数字的字符,该字符串都将被转换成 NaN。可分别使用这两种方法解析“10.2abc”这一字符串,并比较获得的结果,来理解这两种方法的区别。

parseInt('10.2abc'); // 10
parseFloat('10.2abc'); // 10.2
+ "10.2abc"; // NaN
复制代码

JavaScript 还有两个特殊值:Infinity(正无穷)和 -Infinity(负无穷):

1 / 0; //  Infinity
-1 / 0; // -Infinity
复制代码

可使用内置函数 isFinite() 来判断一个变量是不是一个有穷数, 若是类型为Infinity, -InfinityNaN则返回false

isFinite(1/0); // false
isFinite(Infinity); // false
isFinite(-Infinity); // false
isFinite(NaN); // false

isFinite(0); // true
isFinite(2e64); // true

isFinite("0"); // true
// 若是是纯数值类型的检测,则返回 false:
Number.isFinite("0"); // false
复制代码

要检查值是否大于或小于 +/-Infinity,你可使用常量 Number.MAX_VALUENumber.MIN_VALUE

另外在 ECMAScript 6 中,你也能够经过 Number.isSafeInteger() 方法还有 Number.MAX_SAFE_INTEGERNumber.MIN_SAFE_INTEGER 来检查值是否在双精度浮点数的取值范围内。 超出这个范围,JavaScript 中的数字再也不安全了,也就是只有 second mathematical interger 能够在 JavaScript 数字类型中正确表现。

BigInt 类型

BigInt类型是 JavaScript 中的一个基础的数值类型,能够用任意精度表示整数。使用 BigInt,能够安全地存储和操做大整数,甚至能够超过数字的安全整数限制。BigInt是经过在整数末尾附加 n 或调用构造函数来建立的。

> const x = 2n ** 53n;
9007199254740992n
> const y = x + 1n; 
9007199254740993n
复制代码

能够对BigInt使用运算符+、*、-、**%,就像对数字同样。BigInt 严格来讲并不等于一个数字,但它是松散的。

在将BigInt转换为Boolean时,它的行为相似于一个数字:if、||、&&、Boolean 和!。

警告:BigInt不能与数字互换操做。不然,将抛出TypeError

字符串

JavaScript 中的字符串是一串Unicode 字符序列,是一组16位的无符号整数值的“元素”。更准确地说,它们是一串UTF-16编码单元的序列,每个编码单元由一个 16 位二进制数表示。每个Unicode字符由一个或两个编码单元来表示。

字符串中的每一个元素占据了字符串的位置。第一个元素的索引为0,下一个是索引1,依此类推。字符串的长度是它的元素的数量。若是想表示一个单独的字符,只需使用长度为 1 的字符串。

经过访问字符串的 length(编码单元的个数)属性,能够获得它的长度。

"hello".length; // 5
复制代码

String实际上是 JavaScript 对象。你能够像 object 同样使用字符串,字符串也有 methods(方法)能让你操做字符串和获取字符串的信息。

"hello".charAt(0); // "h"
"hello, world".replace("world", "mars"); // "hello, mars"
"hello".toUpperCase(); // "HELLO"
复制代码

注意:JavaScript 字符串是不可更改的。这意味着字符串一旦被建立,就不能被修改。可是,能够基于对原始字符串的操做来建立新的字符串。即JavaScript 中对字符串的操做必定返回了一个新字符串,原始字符串并无被改变

。例如:

  • 获取一个字符串的子串可经过选择个别字母或者使用 String.substr().
  • 两个字符串的链接使用链接操做符 (+) 或者 String.concat().

布尔类型

布尔表示一个逻辑实体,能够有两个值:truefalse

根据具体须要,JavaScript 按照以下规则将变量转换成布尔类型:

  1. false0、空字符串("")、NaNnullundefined 被转换为 false
  2. 全部其余值被转换为 true

小提示:在作双等==逻辑时,2==true 是会返回false的,由于boolean类型被转换为数字1,而后再作比较,详情移步初学者不容错过的双等(==)小知识

Undefined 类型

一个没有被赋值的变量会有个默认值 undefined。undefined是一个不能被配置(non-configurable),不能被重写(non-writable)的属性。

  • 一个没有被赋值的变量的类型是undefined。
  • 一个函数若是没有使用return语句指定返回值,就会返回一个undefined值。

使用undefined和严格相等或不相等操做符来决定一个变量是否拥有值。可是若是你不知道这个值是否声明过,例如试图使用全局变量中的属性,建议使用typeof,它不会在一个变量没有被声明的时候抛出一个错误。

// 这里没有声明y
if(typeof y === 'undefined') {       // 没有错误,执行结果为true
   console.log("y is " + typeof y )  // y is undefined
}

if(y === undefined) {                // ReferenceError: y is not defined

}
复制代码

Null 类型

Null 类型只有一个值: null。它是 JavaScript 基本类型 之一

null 是一个字面量,特指对象的值未设置。把 null 做为还没有建立的对象,也许更好理解。

注意:typeof null == "object"

null 常在返回类型应是一个对象但没有关联的值的地方使用。

// foo 不存在,它历来没有被定义过或者是初始化过:
foo;
"ReferenceError: foo is not defined"

// foo 如今已是知存在的,可是它没有类型或者是值:
var foo = null; 
foo;
null
复制代码

nullundefined 的不一样点:

当检测 nullundefined 时,注意相等(==)与全等(===)两个操做符的区别 ,前者会执行类型转换:

typeof null        // "object" (由于一些之前的缘由而不是'null')
typeof undefined   // "undefined"
null === undefined // false
null  == undefined // true
null === null // true
null == null // true
!null //true
isNaN(1 + null) // false
isNaN(1 + undefined) // true
复制代码

相关知识点: 初学者不容错过的双等(==)小知识

符号类型

符号(Symbols)是ECMAScript 第6版新定义的。符号类型是惟一的而且是不可修改的。

Symbol()函数会返回symbol类型的值,该类型具备静态属性和静态方法。每一个从Symbol()返回的symbol值都是惟一的。一个symbol值能做为对象属性的标识符;这是该数据类型仅有的目的

不支持 var sym = new Symbol(); // TypeError

const symbol1 = Symbol();
const symbol2 = Symbol(42);
const symbol3 = Symbol('foo');

console.log(typeof symbol1);
// expected output: "symbol"

console.log(symbol3.toString());
// expected output: "Symbol(foo)"

console.log(Symbol('foo') === Symbol('foo'));
// expected output: false
复制代码

Symbol函数的参数是可选的,字符串类型。是对symbol的描述,可用于调试但不是访问symbol自己。

再次强调,Symbol类型惟一合理的用法是用变量存储 symbol的值,而后使用存储的值建立对象属性。

var  myPrivateMethod  = Symbol();
this[myPrivateMethod] = function() {...};
复制代码

当一个 symbol 类型的值在属性赋值语句中被用做标识符,

  • 该属性是匿名的;而且是不可枚举的。由于这个属性是不可枚举的,
  • 它不会在循环结构 “for( ... in ...)” 中做为成员出现,也由于这个属性是匿名的,它一样不会出如今 “Object.getOwnPropertyNames()” 的返回数组里。
  • 这个属性能够经过建立时的原始 symbol 值访问到,或者经过遍历 “Object.getOwnPropertySymbols()” 返回的数组。
  • 经过保存在变量 myPrivateMethod的值能够访问到对象属性。
  • 当使用 JSON.stringify() 时,以 symbol 值做为键的属性会被彻底忽略:

typeof运算符能帮助你识别 symbol 类型

typeof Symbol() === 'symbol'
typeof Symbol('foo') === 'symbol'
复制代码

方法:Symbol.for(key)

会根据给定的键 key,来从运行时的 symbol 注册表中找到对应的 symbol,若是找到了,则返回它,不然,新建一个与该键关联的 symbol,并放入全局 symbol 注册表中。

对象

JavaScript 中的对象,Object,能够简单理解成“名称-值”对(而不是键值对,ES 2015 的映射表(Map),比对象更接近键值对))

“名称”部分是一个 JavaScript 字符串,“值”部分能够是任何 JavaScript 的数据类型——包括对象。

有两种简单方法能够建立一个空对象:

var obj = new Object();
var obj = {};
复制代码

这两种方法在语义上是相同的。第二种更方便的方法叫做“对象字面量(object literal)”法。

“对象字面量”也能够用来在对象实例中定义一个对象:

var obj = {
    name: "Carrot",
    "for": "Max",//'for' 是保留字之一,使用'_for'代替
    details: {
        color: "orange",
        size: 12
    }
}
复制代码

对象的属性能够经过链式(chain)表示方法进行访问:

obj.details.color; // orange
obj["details"]["size"]; // 12
复制代码

注意:若'名称'不包含中划线之类的字符,推荐使用第一种object.key来调用

若key未知或须要经过参数传递进来,则使用第二种object[key]来调用,下面会说明缘由

下面的例子建立了一个对象原型,Person,和这个原型的实例,You

function Person(name, age) {
  this.name = name;
  this.age = age;
}

// 定义一个对象
var You = new Person("You", 24); 
// 咱们建立了一个新的 Person,名称是 "You" 
// ("You" 是第一个参数, 24 是第二个参数..)
复制代码

完成建立后,对象属性能够经过以下两种方式进行赋值和访问:

obj.name = "Simon"
var name = obj.name;
复制代码

和:

// bracket notation
obj['name'] = 'Simon';
var name = obj['name'];
// can use a variable to define a key
var user = prompt('what is your key?')
obj[user] = prompt('what is its value?')
复制代码

这两种方法在语义上也是相同的。第二种方法的优势在于属性的名称被看做一个字符串,这就意味着它能够在运行时被计算,缺点在于这样的代码有可能没法在后期被解释器优化。它也能够被用来访问某些以预留关键字做为名称的属性的值:

obj.for = "Simon"; // 语法错误,由于 for 是一个预留关键字
obj["for"] = "Simon"; // 工做正常
复制代码

数组

JavaScript 中的数组是一种特殊的对象。它的工做原理与普通对象相似(以数字为属性名,但只能经过[] 来访问),但数组还有一个特殊的属性——length(长度)属性。这个属性的值一般比数组最大索引大 1。

注意,Array.length 并不老是等于数组中元素的个数,以下所示:

var a = ["dog", "cat", "hen"];
a[100] = "fox";
a.length; // 101
复制代码

若是试图访问一个不存在的数组索引,会获得 undefined

typeof(a[90]); // undefined
复制代码

ES2015 引入了更加简洁的 for...of 循环,能够用它来遍历可迭代对象,例如数组:

for (const currentValue of a) {
  // Do something with currentValue
}
复制代码

不推荐使用 for...in 循环,该方法遍历数组的索引。若是哪一个家伙直接向 Array.prototype 添加了新的属性,使用这样的循环这些属性也一样会被遍历

ECMAScript 5 增长了另外一个遍历数组的方法,forEach()

["dog", "cat", "hen"].forEach(function(currentValue, index, array) {
  // Do something with currentValue or array[index]
});
复制代码

若部分遍历,还能够考虑some(), every()等方法,建议查看 Array 方法的完整文档

函数

最简单的函数就像下面这个这么简单:

function add(x, y) {
    var total = x + y;
    return total;
}
复制代码
  • 一个 JavaScript 函数能够包含 0 个或多个已命名的变量。
  • 函数体中的表达式数量也没有限制。你能够声明函数本身的局部变量。
  • return 语句在返回一个值并结束函数。若是没有使用 return 语句,或者一个没有值的 return 语句,JavaScript 会返回 undefined

你能够传入多于函数自己须要参数个数的参数,只是多余的参数会被忽略。

函数其实是访问了函数体中一个名为 arguments 的内部对象,这个对象就如同一个相似于数组的对象同样,包括了全部被传入的参数。

function add() {
    var sum = 0;
    for (var i = 0, j = arguments.length; i < j; i++) {
        sum += arguments[i];
    }
    return sum;
}

add(2, 3, 4, 5); // 14
复制代码

为了使代码变短一些,咱们可使用剩余参数来替换arguments的使用。

function avg(...args) {
  var sum = 0;
  for (let value of args) {
    sum += value;
  }
  return sum / args.length;
}

avg(2, 3, 4, 5); // 3.5
复制代码

不管“剩余参数操做符”被放置到函数声明的哪里,它都会把除了本身以前的全部参数存储起来。

一般不建议使用该特性,会致使传参意义不明确

JavaScript 容许以递归方式调用函数。递归在处理树形结构(好比浏览器 DOM)时很是有用。

function countChars(elm) {
    if (elm.nodeType == 3) { // TEXT_NODE 文本节点
        return elm.nodeValue.length;
    }
    var count = 0;
    for (var i = 0, child; child = elm.childNodes[i]; i++) {
        count += countChars(child);
    }
    return count;
}
复制代码

你能够命名当即调用的函数表达式(IIFE——Immediately Invoked Function Expression),以下所示:

var charsInBody = (function counter(elm) {
    if (elm.nodeType == 3) { // 文本节点
        return elm.nodeValue.length;
    }
    var count = 0;
    for (var i = 0, child; child = elm.childNodes[i]; i++) {
        count += counter(child);
    }
    return count;
})(document.body);
复制代码

如上所提供的函数表达式的名称的做用域仅仅是该函数自身。这容许引擎去作更多的优化,而且这种实现更可读、友好。

自定义对象

让咱们来定义一我的名对象,这个对象包括人的姓和名两个域(field)。名字的表示有两种方法:“名 姓(First Last)”或“姓, 名(Last, First)”。

function makePerson(first, last) {
    return {
        first: first,
        last: last
    }
}
function personFullName(person) {
    return person.first + ' ' + person.last;
}
function personFullNameReversed(person) {
    return person.last + ', ' + person.first
}
s = makePerson("Simon", "Willison");
personFullName(s); // Simon Willison
personFullNameReversed(s); // Willison, Simon
复制代码

上面的写法虽然能够知足要求,可是看起来很麻烦,由于须要在全局命名空间中写不少函数。既然函数自己就是对象,若是须要使一个函数隶属于一个对象,那么不可贵到:

function makePerson(first, last) {
    return {
        first: first,
        last: last,
        fullName: function() {
            return this.first + ' ' + this.last;
        },
        fullNameReversed: function() {
            return this.last + ', ' + this.first;
        }
    }
}
s = makePerson("Simon", "Willison");
s.fullName(); // Simon Willison
s.fullNameReversed(); // Willison, Simon
复制代码

当使用函数时,函数内this 指代当前的对象,也就是调用了函数的对象。若是在一个对象上使用点或者方括号来访问属性或方法,这个对象就成了 this。若是并无使用“点”运算符调用某个对象,那么 this 将指向全局对象(global object)。这是一个常常出错的地方。例如:

s = makePerson("Simon", "Willison");
var fullName = s.fullName;
fullName(); // undefined undefined
复制代码

下面使用关键字 this 改进已有的 makePerson函数:

function Person(first, last) {
    this.first = first;
    this.last = last;
    this.fullName = function() {
        return this.first + ' ' + this.last;
    }
    this.fullNameReversed = function() {
        return this.last + ', ' + this.first;
    }
}
var s = new Person("Simon", "Willison");
复制代码

new 关键字将生成的 this 对象返回给调用方,而被 new 调用的函数称为构造函数。习惯的作法是将这些函数的首字母大写,这样用 new 调用他们的时候就容易识别了。

下面是一个 new 方法的简单实现:

function trivialNew(constructor, ...args) {
    var o = {}; // 建立一个对象
    constructor.apply(o, args);
    return o;
}
复制代码

这并非 new 的完整实现,由于它没有建立原型(prototype)链。

每次咱们建立一个 Person 对象的时候,咱们都在其中建立了两个新的函数对象,下面使用原型链进行优化。

Person.prototype 是一个能够被Person的全部实例共享的对象。它是一个名叫原型链(prototype chain)的查询链的一部分:当你试图访问一个 Person 没有定义的属性时,解释器会首先检查这个 Person.prototype 来判断是否存在这样一个属性。因此,任何分配给 Person.prototype 的东西对经过 this 对象构造的实例都是可用的。

function Person(first, last) {
    this.first = first;
    this.last = last;
}
Person.prototype.fullName = function() {
    return this.first + ' ' + this.last;
}
Person.prototype.fullNameReversed = function() {
    return this.last + ', ' + this.first;
}
复制代码

JavaScript 容许你在程序中的任什么时候候修改原型(prototype)中的一些东西,也就是说你能够在运行时(runtime)给已存在的对象添加额外的方法

关于原型链更多内容,可参见继承与原型链

最后附上对象及其衍生类型的一些判断方式

判断数组

  • Array.isArray()
  • [] instanceof Array判断是否在Array的原型链上
  • [].constructor === Array经过其构造函数判断
  • Object.prototype.toString.call([])判断值是否为'[object Array]'
const arr = [1, 2, 3];

Array.isArray(arr); // true
arr instanceof Array; // true
arr.constructor === Array; // true
Object.prototype.toString.call(arr); // "[object Array]"
复制代码

判断对象

  • {} instanceof Object判断是否在Object的原型链上
  • {}.constructor === Object经过其构造函数判断
  • Object.prototype.toString.call({}), 值为'[object Object]'
const obj = {};

obj instanceof Object; // true
obj.constructor === Object; // true
Object.prototype.toString.call(obj); // "[object Object]"
复制代码

判断函数

  • func typeof function
  • func instanceof Function判断是否在Function的原型链上
  • func.constructor === Function经过构造函数判断
  • Object.prototype.toString.call(func)值为 "[object Function]"
function func() {}

typeof(func); // function
func instanceof Function; // true
func.constructor === Function; // true
Object.prototype.toString.call(func); // "[object Function]"
复制代码

相关系列: 从零开始的前端筑基之旅(超级精细,持续更新~)

参考文档:

从新介绍javascript:developer.mozilla.org/zh-CN/docs/…

JavaScript 数据类型和数据结构: developer.mozilla.org/zh-CN/docs/…

相关文章
相关标签/搜索