任何编程语言都不可缺乏的组成部分——“类型”
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
, -Infinity
或 NaN则返回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_VALUE
和 Number.MIN_VALUE
。
另外在 ECMAScript 6 中,你也能够经过 Number.isSafeInteger()
方法还有 Number.MAX_SAFE_INTEGER
和 Number.MIN_SAFE_INTEGER
来检查值是否在双精度浮点数的取值范围内。 超出这个范围,JavaScript 中的数字再也不安全了,也就是只有 second mathematical interger 能够在 JavaScript 数字类型中正确表现。
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()
.布尔表示一个逻辑实体,能够有两个值:true
和 false
。
根据具体须要,JavaScript 按照以下规则将变量转换成布尔类型:
false
、0
、空字符串(""
)、NaN
、null
和 undefined
被转换为 false
true
小提示:在作双等==逻辑时,2==true 是会返回false的,由于boolean类型被转换为数字1,而后再作比较,详情移步初学者不容错过的双等(==)小知识
一个没有被赋值的变量会有个默认值 undefined。
undefined是一个不能被配置(non-configurable),不能被重写(non-writable)的属性。
返回
值,就会返回一个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。
它是 JavaScript 基本类型 之一
值 null
是一个字面量,特指对象的值未设置。把 null
做为还没有建立的对象,也许更好理解。
注意:
typeof null == "object"
null
常在返回类型应是一个对象但没有关联的值的地方使用。
// foo 不存在,它历来没有被定义过或者是初始化过:
foo;
"ReferenceError: foo is not defined"
// foo 如今已是知存在的,可是它没有类型或者是值:
var foo = null;
foo;
null
复制代码
null
与 undefined
的不一样点: 当检测 null
或 undefined
时,注意相等(==)与全等(===)两个操做符的区别 ,前者会执行类型转换:
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()
” 的返回数组里。Object.getOwnPropertySymbols()
” 返回的数组。myPrivateMethod
的值能够访问到对象属性。typeof
运算符能帮助你识别 symbol 类型
typeof Symbol() === 'symbol'
typeof Symbol('foo') === 'symbol'
复制代码
会根据给定的键 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;
}
复制代码
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/…