JS Tutorial

Numbers

JS中的Numbers是双精度64位格式的IEEE754的值。即在JS中没有整数(除了BIgInt)。即一个整数事实上是一个浮点数

实际上整数被当作32位的int,对于某些实现就是这样存储的,直到它需要被当作Number时才会修改存储方式。这在位操作上是需要注意的

JS支持标准的算数运算(加减乘除等)还有Math中的高级操作

可以使用parseInt()函数来将字符串转换为int,这个函数接受进制作为第二个可选参数

使用 0x 开头的数字被当作16进制数转换

可以使用parseFloat()函数来转换浮点数,这个函数总是基于10进制

使用+ 也可以将字符串转换为Number

如果字符串不表示数字,会返回 NaN(和其它数字的运算都返回NaN)。使用isNan()来判断结果是否为NaN

其它特殊值为 Infinity和 -Infinity

可以使用isFinite()来测试这三个特殊值

PS:parseInt parseFloat这两个函数会转换字符串直到遇到不是数字格式的字符。使用 + 转换字符串,如果字符串中有无效字符,则返回的是NaN

Strings

JS中的字符串是Unicode字符序列。准确的说它们是UTF-16码元(code unit)序列。每个字符代表1或2个码元
使用 length属性来获取字符串的长度

可以把string当成对象使用,它包含很多方法 charAt replace toUpperCase等

其它类型

Null表示不是一个值(non-value)。undefined是类型为undefined的值,即未初始化的变量,即一个还没有赋值的变量

JS有布尔类型,true false 都是关键字。任何值都可以转换为布尔值,需要遵循两条规则:

  1. false 0 空字符串’’ NaN null undefined都是false
  2. 其它的值都是true。使用 Boolean()来判断是true或false

Variables

JS中的新变量使用 let const var来声明

let声明block-level的变量。这个变量在声明它的语句块中有效

const声明那些不会改变的值。这个变量在声明它的语句快中有效

var是最常用的。它没有另外两个关键字的限制。这个变量在声明它的函数(function)中有效。如果声明了变量但是没有赋值,那么它的值是undefined

JS中语句块没有域(scope),只有函数有域,所以使用var声明的变量在整个函数中有效,ES2015之后,let和const可以用来声明块级别的变量

Operators

  • / %。赋值 = ,组合操作 += -=,++ — 支持前缀、后缀使用。+也用于字符串拼接。字符串和任何类型的值做加法都返回字符串(空字符串+其它值转换类型)

< > <= >=。这些比较运算符支持字符串和数字。双等==,如果等号两边类型不同,那么会做类型强制转换,为了防止这种状况,使用===,对应有!= !==

JS中也有位操作符,这里不再叙述

Control structures

if-else if () {} else if() {} else {}

while while () {}

do-while do {} while()

for for (var I = 0; I < 5; I++) {}

for-of for (let v of array) {}

for-in for (let property in object) {}

&& || 执行的是短路判断,可以利用这一点(var name = o && o.getName()),还能缓存值(var name = cachedName || (cachedName = getName()))(这里可以看出赋值运算符是要返回值的)

JS支持三目运算 a === b ? A : b

Switch 基于数字或者字符串来进行分支判断 switch (action) {case ‘’: break; default: ;}。如果不添加 break语句,那么会一直向下执行。default分支是可选的。在switch的条件和case中都可以放表达式,它们的计算发生在进行 === 之前

Objects

JS的对象可以简单看作name-value对的集合。JS中除了核心的类型以外全部都是对象。对象中的 name部分是字符串,值可以是任何的JS值

创建空对象的两种方式:var obj = new Object() / var obj = {}。这两种方式语义上相同,第二种叫做对象字面值
语法,更方便

对象中的属性可以链式访问(点操作符或者中括号)

下面的例子创建了一个对象原型,并且在这个原型上创建了实例:

function Person(name, age) {
	this.name = name
	this.age = age
}
var you = new Person(‘You’, 24)

在创建了对象之后,又能通过点操作符或者中括号来访问这个实例的属性(读写)。中括号写法的优点在于,属性名被看作字符串,这样可以在运行时被计算(真正的值),但是这也阻止JS引擎做更多的优化。这种方式也允许给对对象绑定名字为JS保留字的属性。ECMA5规定保留字作为不需要包含在引号中使用了。ES2015之后,对象的key使用变量 {[phoneType]: 12345} 而不是 var userPhone = {}; userPhone[phoneType] = 12345

Arrays

JS中的数组也是特殊的对象。通过中括号语法来访问下标对应数据。数组的字面值 var a = [‘a’, ‘b’, ‘c’] a.length // 3。length并不是数组中元素的数量,它的值总是有效索引最大值+1。访问数组外的索引值得到的是undefined。一般遍历数组的方式是结合length和索引。ES2015介绍了for-of来遍历数组。也可以通过for-in来遍历数组,但是它遍历的是array indices。此外,如果在Array.prototype上添加了新属性,它也可以在这个循环中被遍历到,因此这种遍历在数据上并不被推荐。ECMA5的forEach也可以遍历数组,[‘a’, ‘b’, ‘c’].forEach(function(curr, idx, array) {})

介绍一些数组的常用方法

  • 转成逗号分隔的每个元素的toString方法返回值 toString
  • 转成逗号分隔的每个元素的toLocaleString方法返回值 toLocaleString
  • 返回新数组,包含每个添加进来的元素 concat
  • 将数组转为字符串,通过sep参数规定分隔符 join(sep)
  • 移除并返回最后一个元素 pop
  • 数组尾追加任意数量元素 push
  • 移除并返回第一个元素 shift
  • 在数组前添加无限多个元素 unshift
  • 返回子数组 slice(start[, end])
  • 排序,接收排序函数 sort([cmpfunc])
  • 通过删除一段,将它替换成别的内容来修改数组 splice(start, delcount[, items])
  • 反转数组 reverse

Functions

基本的函数形式

function add(x, y) {
	var total = x+y
	return total
}

JS函数接收0或多个命名参数。函数体中可以有任意多的语句和函数中有效的变量。return语句可以在任意时间返回值,终止函数,如果没有return(或者只有return),函数返回undefined

命名参数只是一个声明,理论上可以不传,这种情况下,参数被设置为undefined,传多余的参数会被忽略

函数中一个叫做arguments的变量保存了所有传入的参数。可以通过Rest parameter syntax来替代这种方式,…variable会包含所有传入函数的参数,例子:

function avg(…args) {
	var sum = 0
	for (for arg of args) {
		sum += arg
	}
	return sum
}

avg (2,3,4,5) // 3.5

这里要注意的是,…variable无论在参数列表的什么地方,它只保存它后面传入的参数,而不包括它前面的

JS允许你通过任意的arguments数组来调用函数,使用函数对象的apply方法avg.apply(null, [2,3,4,5]) // 这种用法可以避免新写一个接收数组参数的函数

JS允许(在任何位置)创建匿名函数,利用这一点有很多技巧(例如隐藏变量)

JS允许函数递归调用

匿名函数如何递归调用:

var charsInBody = (function counter(elem) {
	if (elem.nodeType == 3) { // text node
		return elem.nodeValue.length
	}
	var count = 0
	for (var I = 0, child; child = elem.childNodes[I]; I++) {
		count += counter(child)
	}
	return count
})(document.body); // 这里的名字count只能在函数内部使用,且这个名字在debugger中可见

JS的函数是对象,它们可以被增加和修改属性

Custom objects

JS是基于原型的语言,并没有class语句。然而JS使用function来模拟class:

function makePerson(first, last) {
	return {
		first: first,
		last: last,
		fullName: function() { return this.first + ‘ ’ + this.last }
		fullNameReversed(): function { return this.last + ‘ ’ + this.first }
	}
}

this关键字在函数中引用当前的对象,它的具体含义取决于你调用函数的方式。如果你使用点操作符或者中括号调用,这个调用的对象会被this引用,如果没有用这种方式,this引用着全局对象(即如果将此函数赋值给其它的变量,调用这个变量(此时为函数),那么this依旧引用global object)

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强相关,它创建一个新的空对象,然后调用制定的函数,this此时就是创建出来的新对象,虽然this所在的函数没有返回值,只是修改了this的属性。但是new会将this返回给调用者。这种函数被称作构造函数,一般这种函数以大写字母开头。这种方式依旧不能避免之前的单独调用实例上的函数出现的错误

上面这种改进,每次创建一个Person对象就会新建两个新的函数对象,如何来共享函数呢:

function personFullName() {
  return this.first + ' ' + this.last;
}
function personFullNameReversed() {
  return this.last + ', ' + this.first;
}
function Person(first, last) {
  this.first = first;
  this.last = last;
  this.fullName = personFullName;
  this.fullNameReversed = personFullNameReversed;
}

更好的做法是:

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;
};

Person.prototype是所有Person实例共享的一个对象。它构成了查找链(原型链)的一部分,任何时候要访问Person中不存在的属性,JS会检查Person.prototype来查看是否存在此属性。此时,所有添加到原型中的属性都能通过this访问

JS允许你在任何时候修改原型,即运行时中修改已有对象的内容。对于JS的原生对象也同样适用

原型链的root是Object.prototype,其中方法包括toString。此时在Person的原型上定义toString就会覆盖Object原型中的方法

apply方法的第一个参数就是被当作this的对象

function trivialNew(constructor, ...args) {
  var o = {}; // Create an object
  constructor.apply(o, args);
  return o;
}

这种方式新建的对象没有设置原型链
apply的相似方法call,也允许你设置this,但是接收的是分开的参数而不是数组

JS函数可以在另一个函数内部声明,一个需要注意的细节是JS可以访问父域中的变量。这提供了写更易维护代码的工具。当一个函数需要一些其它部分用不着的函数就可以使用嵌套函数,这样可以保持global域的函数更少,这是一件好事。通过这样的方式也可以减少全局变量的使用,“local globals”

Closures

JS提供的一种强大的抽象机制,也经常引起困惑

function makeAdder(a) {
	return function(b) {
		return a + b
	}
}
var add5 = makeAdder(5)
var add20 = makeAdder(20)
add5(6) // 11
add20(7) // 27

makeAdder()函数创建了一个新函数,每次调用这个新函数,都会将函数的参数和创建这个函数的参数相加并返回

这里发生的事情和嵌套函数一样,只是外面的函数返回了,因此它的局部变量也都不存在了,然而,它们的确存在

任何时候,当JS执行一个函数,一个scope对象被创建来持有这个函数内部的局部变量。它通过任何传入函数的参数来初始化。一个全新的scope对象每次函数执行都会被创建,和全局对象(this,浏览器中的window)不同,这些scope对象不能直接通过代码访问,没有办法来遍历当前scope对象的属性

当makeAdder()被调用,scope对象建立,持有属性a,然后它返回一个新函数。通常JS的gc会在此刻清除创建makeAdder()生成的scope对象,但是返回的函数保留了这个scope对象的引用,最后,这个scope对象不会被gc掉,直到没有这个函数返回的新函数的引用存在

scope对象形成了scope链,和JS对象使用的原型链类似

闭包就是一个函数和创建它生成的scope对象的结合。闭包使你可以保存状态,它们经常被用来代替对象