手记

[干货]ECMAScript高效入门:看这一篇就够了!

任何语言的核心都是这门语言最基本的工作原理。本篇博客将以短小精悍的内容讲述ECMAScript的基础语法,操作符,数据类型,内置功能等基本概念。

语法

ECMAScript中的变量,函数名,操作符都是区分大小写的。

数据类型

javascript的基本数据类型:null, undefine, number, string, boolean

javascript的复杂数据类型:object(array, function属于object类型)

变量的赋值

在将一个值赋给变量时,解析器必须确定这个值是基本类型值还是引用类型值。
基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值。

引用类型的值是保存在内存中的对象。在操作对象时,实际上是在操作对象的引用而不是实际的对象。

Number

在ECMAScript中对于数值类型只有Number一种类型存储,不区分整数和浮点数。

对于浮点数值,由于保存浮点数之需要的内存空间是保存整数数值的两倍,因此ECMAScript会不失时机地将浮点数值转化为整数值。
如果小数点后面没有任何数字,那么这个数值就可以作为整数值来保存。同样,如果浮点数值本身表示的就是一个整数,那么该值也会被转换为整数。

var floatNum = 1.0
console.log(floatNum) // 1

对于那些极大或极小的数值,可以用e表示法(俗称科学计数法)表示浮点数值:e前面的数值乘以10的指数次幂。

浮点数值在算术计算时的精度很低。例如,0.1加0.2的结果不是0.3,而是0.30000000000000004。因此千万不要通过浮点数的计算作为if判断条件,否则会出错。

let a = 0.1, b = 0.2
if (a + b === 0.3){ // 不要做这样的测试!
    alert("You got 0.3");
}

对于数值范围的判断

ECMAScript 能够表示的最小数值保存在Number.MIN_VALUE中,能够表示的最大数值保存在Number.MAX_VALUE中。
可以通过isFinite()函数来判断数值位于最大和最小数值之间会返回true。

NaN

NaN:Not a Number,是一个特殊的数值。表示一个本来要返回数值的操作数未返回数值的情况。
NaN 本身有两个非同寻常的特点。

  • 首先,任何涉及NaN 的操作(例如NaN/10)都会返回NaN,这
    个特点在多步计算中有可能导致问题。
  • 其次,NaN 与任何值都不相等,包括NaN 本身。例如,下面的代码会返回false
alert(NaN == NaN); //false
alert(isNaN(NaN)); //true
alert(isNaN(10)); //false(10 是一个数值)
alert(isNaN("10")); //false(可以被转换成数值10)
alert(isNaN("blue")); //true(不能转换成数值)
alert(isNaN(true)); //false(可以被转换成数值1)

数值的二进制存储

对于有符号的整数,32 位中的前31 位用于表示整数的值。第32 位用于表示数值的符号:0 表示正
数,1 表示负数。这个表示符号的位叫做符号位,符号位的值决定了其他位数值的格式。

  • 正数以纯二进制格式存储,31 位中的每一位都表示2 的幂
  • 负数:以二进制补码形式存储。
    1.求这个数值绝对值的二进制码
    2.求二进制反码,即将0 替换为1,将1 替换为0
    3.得到的二进制反码加1。

基本包装类型

为了便于操作基本类型值,ECMAScript 提供了3 个特殊的引用类型:Boolean、Number 和String。对基本包装类型的实例调用typeof 会返回"object"。

引用类型与基本包装类型的主要区别就是对象的生存期。使用new 操作符创建的引用类型的实例,在执行流离开当前作用域之前都一直保存在内存中。而自动创建的基本包装类型的对象,则只存在于一行代码的执行瞬间,然后立即被销毁。这意味着我们不能在运行时为基本类型值添加属性和方法。

Number类型

var s1 = "some text";
s1.color = "red";
alert(s1.color); //undefined

var value = "25";
var number = Number(value); //转型函数
alert(typeof number); //"number"
var obj = new Number(value); //构造函数
alert(typeof obj); //"object"

基本包装类型Number提供toFixed()方法,会按照指定的小数位返回数值的字符串表示。

var num = 10.005;
alert(num.toFixed(2)); //"10.01"

能够自动舍入的特性,使得toFixed()方法很适合处理货币值。

String类型

1.字符方法
提供访问字符串中特定字符的方法是:charAt()和charCodeAt()。
2.字符串操作方法
concat(),用于将一或多个字符串拼接起来,返回拼接得到的新字符串。
3.基于子字符串创建新字符串的方法:slice()、substr()和substring()。

var stringValue = "hello world";
alert(stringValue.slice(3)); //"lo world"
alert(stringValue.substring(3)); //"lo world"
alert(stringValue.substr(3)); //"lo world"
alert(stringValue.slice(3, 7)); //"lo w"
alert(stringValue.substring(3,7)); //"lo w"
alert(stringValue.substr(3, 7)); //"lo worl"

4.字符串位置方法:indexOf()和lastIndexOf()
5.trim()方法:创建一个字符串的副本,删除前置及后缀的所有空格,然后返回结果。
6.大小写转换方法:toUpperCase(),toLowerCase()

var stringValue = "hello world";
alert(stringValue.toLocaleUpperCase()); //"HELLO WORLD"
alert(stringValue.toUpperCase()); //"HELLO WORLD"
alert(stringValue.toLocaleLowerCase()); //"hello world"
alert(stringValue.toLowerCase()); //"hello world"

一般来说,在不知道自己的代码将在哪种语言环境中运行的情况下,还是使用针对地区的方法更稳妥一些。
7.字符串的模式匹配方法:match(),search()和split()
match()方法只接受一个参数,要么是一个正则表达式,要么是一个RegExp 对象。

var text = "cat, bat, sat, fat";
var pattern = /.at/;
//与pattern.exec(text)相同
var matches = text.match(pattern);
alert(matches.index); //0
alert(matches[0]); //"cat"
alert(pattern.lastIndex); //0

search()方法,返回字符串中第一个匹配项的索引;如果没有找到匹配项,则返回-1。
split()方法,基于指定的分隔符将一个字符串分割成多个子字符串,并将结果放在一个数组中。分隔符可以是字符串,也可以是一个RegExp 对象(这个方法不会将字符串看成正则表达式)。split()方法可以接受可选的第二个参数,用于指定数组的大小,以便确保返回的数组不会超过既定大小。
8.替换子字符串方法:replace()

var text = "cat, bat, sat, fat";
var result = text.replace("at", "ond");
alert(result); //"cond, bat, sat, fat"
result = text.replace(/at/g, "ond");
aler t(result); //"cond, bond, sond, fond"

// 高级用法
function htmlEscape(text){
	return text.replace(/[<>"&]/g, function(match, pos, originalText){
		switch(match){
			case "<":
				return "&lt;";
			case ">":
				return "&gt;";
			case "&":
				return "&amp;";
			case "\"":
				return """;
		}
	});
}

引用类型

Array(从数据类型看,属于Object类型)

通过Array.isArray(testVaribaleName)方法可以确认某个值到底是不是数组。

栈方法

栈数据结构的访问规则是LIFO(后进先出)。
push()方法可以接受任意数量的参数,把他们逐个添加到数组的末尾,并返回修改后的数组长度。
pop()方法则从数组末尾移除最后一项,减少数组length值,然后返回移除的项。

队列方法

队列数据结构的访问规则是FIFO(先进先出)。
shift()能够移除数组中的第一个项并返回该项,同时将数组的长度减少1
结合使用shift()和push()方法,可以像使用队列一样使用数组。
unshift()方法能够在数组的前端添加任意个项并返回新数组的长度。结合使用pop()方法,可以从相反的方向模拟队列,在数组前端添加项,从数组末端移除项。

排序方法

直接用来重排序的方法:reverse()和sort()

操作方法

  • concat()方法可以基于当前数组中的所有项创建一个新数组。具体来说,这个方法会先创建当前数组一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组。
  • slice()方法能够基于当前数组中的一或多个项创建一个新数组。slice()方法可以接受一或两个参数,即要返回项的起始和结束位置。在只有一个参数的情况下,slice()方法返回从该参数指定位置开始到当前数组末尾的所有项。注意,slice()方法不会影响原始数组。
var colors = ["red", "green", "blue", "yellow", "purple"];
var colors2 = colors.slice(1);
var colors3 = colors.slice(1,4);
alert(colors2); //green,blue,yellow,purple
alert(colors3); //green,blue,yellow
  • splice()的主要用途是向数组的中部插入项。

 删除:可以删除任意数量的项,只需指定2 个参数:要删除的第一项的位置和要删除的项数。

splice(0,2) //会删除数组中的前两项。

 插入:可以向指定位置插入任意数量的项,只需提供3 个参数:起始位置、0(要删除的项数)和要插入的项。如果要插入多个项,可以再传入第四、第五,以至任意多个项。例如,

splice(2,0,"red","green") //会从当前数组的位置2 开始插入字符串"red"和"green"。

 替换:可以向指定位置插入任意数量的项,且同时删除任意数量的项,只需指定3 个参数:起始位置、要删除的项数和要插入的任意数量的项。插入的项数不必与删除的项数相等。

splice (2,1,"red","green") //会删除当前数组位置2 的项,然后再从位置2 开始插入字符串
"red""green"

splice()方法始终都会返回一个数组,该数组中包含从原始数组中删除的项(如果没有删除任何项,则返回一个空数组)。

位置查找方法

indexOf()方法从数组的开头(位置0)开始向后查找,lastIndexOf()方法则从数组的末尾开始向前查找。

迭代方法

  • every():对数组中的每一项运行给定函数,如果该函数对每一项都返回true,则返回true。
  • filter():对数组中的每一项运行给定函数,返回该函数会返回true 的项组成的数组。
  • forEach():对数组中的每一项运行给定函数。这个方法没有返回值。
  • map():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
  • some():对数组中的每一项运行给定函数,如果该函数对任一项返回true,则返回true。

以上方法都不会修改数组中的包含的值。

归并方法

reduce()和reduceRight()。这两个方法都会迭代数组的所有项,然后构建一个最终返回的值。其中,reduce()方法从数组的第一项开始,逐个遍历到最后。而reduceRight()则从数组的最后一项开始,向前遍历到第一项。

var values = [1,2,3,4,5];
var sum = values.reduce(function(prev, cur, index, array){
	return prev + cur;
});
alert(sum); //15

Date类型

Date类型使用自UTC(Coordinated Universal Time,国际协调时间)1970 年1 月1 日午夜(零时)开始经过的毫秒数来保存日期。
Data.now()方法,返回表示调用这个方法时的日期和时间的毫秒数。

日期格式化方法

  • toDateString()——以特定于实现的格式显示星期几、月、日和年;
  • toTimeString()——以特定于实现的格式显示时、分、秒和时区;
  • toLocaleDateString()——以特定于地区的格式显示星期几、月、日和年;
  • toLocaleTimeString()——以特定于实现的格式显示时、分、秒;
  • toUTCString()——以特定于实现的格式完整的UTC 日期。

日期/时间组件方法

(不需要了解的同学请自行跳过)

getTime() 返回表示日期的毫秒数;与valueOf()方法返回的值相同
setTime(毫秒) 以毫秒数设置日期,会改变整个日期
getFullYear() 取得4位数的年份(如2007而非仅07)
getUTCFullYear() 返回UTC日期的4位数年份
setFullYear(年) 设置日期的年份。传入的年份值必须是4位数字(如2007而非仅07)
setUTCFullYear(年) 设置UTC日期的年份。传入的年份值必须是4位数字(如2007而非仅07)
getMonth() 返回日期中的月份,其中0表示一月,11表示十二月
getUTCMonth() 返回UTC日期中的月份,其中0表示一月,11表示十二月
setMonth(月) 设置日期的月份。传入的月份值必须大于0,超过11则增加年份
setUTCMonth(月) 设置UTC日期的月份。传入的月份值必须大于0,超过11则增加年份
getDate() 返回日期月份中的天数(1到31)
getUTCDate() 返回UTC日期月份中的天数(1到31)
setDate(日) 设置日期月份中的天数。如果传入的值超过了该月中应有的天数,则增加月份
setUTCDate(日) 设置UTC日期月份中的天数。如果传入的值超过了该月中应有的天数,则增加月份
getDay() 返回日期中星期的星期几(其中0表示星期日,6表示星期六)
getUTCDay() 返回UTC日期中星期的星期几(其中0表示星期日,6表示星期六)
getHours() 返回日期中的小时数(0到23)
getUTCHours() 返回UTC日期中的小时数(0到23)
setHours(时) 设置日期中的小时数。传入的值超过了23则增加月份中的天数
setUTCHours(时) 设置UTC日期中的小时数。传入的值超过了23则增加月份中的天数
getMinutes() 返回日期中的分钟数(0到59)
getUTCMinutes() 返回UTC日期中的分钟数(0到59)
setMinutes(分) 设置日期中的分钟数。传入的值超过59则增加小时数
setUTCMinutes(分) 设置UTC日期中的分钟数。传入的值超过59则增加小时数
getSeconds() 返回日期中的秒数(0到59)
getUTCSeconds() 返回UTC日期中的秒数(0到59)
setSeconds(秒) 设置日期中的秒数。传入的值超过了59会增加分钟数
setUTCSeconds(秒) 设置UTC日期中的秒数。传入的值超过了59会增加分钟数
getMilliseconds() 返回日期中的毫秒数
getUTCMilliseconds() 返回UTC日期中的毫秒数
setMilliseconds(毫秒) 设置日期中的毫秒数

RegExp 类型

ECMAScript 通过RegExp 类型来支持正则表达式。

var expression = / pattern / flags ;

pattern:
可以是任何简单或复杂的正则表达式,可以包含字符类、限定符、分组、向前查找以及反向引用。
flags:

  • g:表示全局(global)模式,即模式将被应用于所有字符串,而非在发现第一个匹配项时立即停止;
  • i:表示不区分大小写(case-insensitive)模式,即在确定匹配项时忽略模式与字符串的大小写;
  • m:表示多行(multiline)模式,即在到达一行文本末尾时还会继续查找下一行中是否存在与模式匹配的项。
/*
* 匹配第一个"bat"或"cat",不区分大小写
*/
var pattern1 = /[bc]at/i;
/*
* 与pattern1 相同,只不过是使用构造函数创建的
*/
var pattern2 = new RegExp("[bc]at", "i");

RegExp的主要正则验证方法

1.exec()方法专门为捕获组而设计的。exec()接受一个参数,即要应用模式的字符串,然后返回包含第一个匹配项信息的数组;或者在没有匹配项的情况下返回null。

var text = "mom and dad and baby";
var pattern = /mom( and dad( and baby)?)?/gi;
var matches = pattern.exec(text);
alert(matches.index); // 0
alert(matches.input); // "mom and dad and baby"
alert(matches[0]); // "mom and dad and baby"
alert(matches[1]); // " and dad and baby"
aler t(matches[2]); // " and baby"

2.test()方法,它接受一个字符串参数。在模式与该参数匹配的情况下返回true;否则,返回false。

var text = "000-00-0000";
var pattern = /\d{3}-\d{2}-\d{4}/;
if (pattern.test(text)){
	alert("The pattern was matched.");
}

这种用法经常出现在验证用户输入的情况下,因为我们只想知道输入是不是有效,至于它为什么无效就无关紧要了。

Function类型

在JS中,函数实际是对象。每个函数都是Function类型的实例,而且都与其他引用类型一样具有属性和方法。函数通常是使用函数声明语法定义的,也可以使用函数表达式定义函数。

// 函数表达式的定义方法
var sum = function(num1, num2){
	return num1 + num2;
};

由于函数名仅仅是指向函数的指针,因此函数名与包含对象指针的其他变量没有什么不同。换句话说,一个函数可能会有多个名字。

函数声明与函数表达式区别(JS面试易考!!!)

解析器在向执行环境中加载数据时,对函数声明和函数表达式并非一视同仁。解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。

在代码开始执行之前,解析器就已经通过一个名为函数声明提升(function declaration hoisting)的过程,读取并将函数声明添加到执行环境中。而函数表达式,函数位于一个初始化语句中,在执行到函数所在的语句之前,函数表达式变量中不会保存有对函数的引用。

作为值的函数(JS理解重难点!!!)

因为ECMAScript 中的函数名本身就是变量,所以函数也可以作为值来使用。也就是说,不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回。

function callSomeFunction(someFunction, someArgument){
	return someFunction(someArgument);
}

函数内部属性

在函数内部,有两个特殊的对象:arguments 和this。arguments:包含传入函数的所有参数。callee 的属性,该属性是一个指针,指向拥有这个arguments 对象的函数。

function factorial(num){
	if (num <=1) {
		return 1;
	} else {
		return num * arguments.callee(num-1)
	}
}

函数内部的特殊对象:this。this引用的是函数赖以执行的环境对象。
caller:保存这调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为null

每个函数都包含两个非继承而来的方法:apply()和call()。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内的this对象的值,从而扩充函数赖以运行的作用域。

bind()方法:会创建一个函数的实例,其this 值会被绑定到传给bind()函数的值。

单体内置对象:Global对象和Math对象

Global对象

不属于任何其他对象的属性和方法,最终都是它的属性和方法。事实上,没有全局变量或全局函数;所有在全局作用域中定义的属性和函数,都是Global 对象的属性。诸如isNaN()、isFinite()、parseInt()以及parseFloat(),实际上全都是Global对象的方法。

URI编码方法

Global 对象的encodeURI()和encodeURIComponent()方法可以对URI(Uniform Resource Identifiers,通用资源标识符)进行编码,以便发送给浏览器。
encodeURI()不会对本身属于URI 的特殊字符进行编码,例如冒号、正斜杠、问号和井字号;encodeURIComponent()则会对它发现的任何非标准字符进行编码。

var uri = "http://www.wrox.com/illegal value.htm#start";
//"http://www.wrox.com/illegal%20value.htm#start"
alert(encodeURI(uri));
//"http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.htm%23start"
alert(encodeURIComponent(uri));

一般来说, 我们使用encodeURIComponent() 方法的时候要比使用encodeURI()更多,因为在实践中更常见的是对查询字符串参数而不是对基础URI进行编码。

eval()方法

当解析器发现代码中调用eval()方法时,它会将传入的参数当作实际的ECMAScript 语句来解析,然后把执行结果插入到原位置。通过eval()执行的代码被认为是包含该次调用的执行环境的一部分,因此被执行的代码具有与该执行环境相同的作用域链。这意味着通过eval()执行的代码可以引用在包含环境中定义的变量。

eval("var msg = 'hello world'; ");
alert(msg); //"hello world"

在eval()中创建的任何变量或函数都不会被提升,因为在解析代码的时候,它们被包含在一个字符串中;它们只在eval()执行的时候创建。严格模式下,在外部访问不到eval()中创建的任何变量或函数.

Global对象的属性

  • undefined 特殊值undefined
  • NaN 特殊值NaN
  • Infinity 特殊值Infinity
  • Object 构造函数Object
  • Array 构造函数Array
  • Function 构造函数Function
  • Boolean 构造函数Boolean
  • String 构造函数String
  • Number 构造函数Number
  • Date 构造函数Date
  • RegExp 构造函数RegExp
  • Error 构造函数Error
  • EvalError 构造函数EvalError
  • RangeError 构造函数RangeError
  • ReferenceError 构造函数ReferenceError
  • SyntaxError 构造函数SyntaxError
  • TypeError 构造函数TypeError
  • URIError 构造函数URIError

window对象

ECMAScript 虽然没有指出如何直接访问Global 对象,但Web 浏览器都是将这个全局对象作为window 对象的一部分加以实现的。

JavaScript中的window 对象除了扮演ECMAScript规定的Global 对象的角色外,还承担了很多别的任务。

// 另一种取得Global 对象的方法是使用以下代码:
var global = function(){
	return this;
}();

以上代码创建了一个立即调用的函数表达式,返回this 的值。如前所述,在没有给函数明确指定this 值的情况下(无论是通过将函数添加为对象的方法,还是通过调用call()或apply()),this值等于Global 对象。而像这样通过简单地返回this 来取得Global 对象,在任何执行环境下都是可行的。

Math 对象

常用方法

  • Math.ceil()执行向上舍入,即它总是将数值向上舍入为最接近的整数;
  • Math.floor()执行向下舍入,即它总是将数值向下舍入为最接近的整数;
  • Math.round()执行标准舍入,即它总是将数值四舍五入为最接近的整数(这也是我们在数学课上学到的舍入规则)。
  • Math.random()方法:值 = Math.floor(Math.random() * 可能值的总数 + 第一个可能的值)
  • Math.abs(num) 返回num 的绝对值 Math.asin(x) 返回x 的反正弦值
  • Math.exp(num) 返回Math.E 的num 次幂 Math.atan(x) 返回x 的反正切值
  • Math.log(num) 返回num 的自然对数 Math.atan2(y,x) 返回y/x 的反正切值
  • Math.pow(num,power) 返回num 的power 次幂 Math.cos(x) 返回x 的余弦值
  • Math.sqrt(num) 返回num 的平方根 Math.sin(x) 返回x 的正弦值
  • Math.acos(x) 返回x 的反余弦值 Math.tan(x) 返回x 的正切值

操作符

乘性操作符

乘性操作符:乘法,除法和求模。和其他语言不同,在操作数为非数值的情况下,JS将会自动执行 类型转换。

关系操作符

小于(<)、大于(>)、小于等于(<=)和大于等于(>=)

判断规则:

 如果两个操作数都是数值,则执行数值比较。
 如果两个操作数都是字符串,则比较两个字符串对应的字符编码值。
 如果一个操作数是数值,则将另一个操作数转换为一个数值,然后执行数值比较。
 如果一个操作数是对象,则调用这个对象的valueOf()方法,用得到的结果按照前面的规则执
行比较。如果对象没有valueOf()方法,则调用toString()方法,并用得到的结果根据前面
的规则执行比较。
 如果一个操作数是布尔值,则先将其转换为数值,然后再执行比较。

在比较字符串时,实际比较的是两个字符串中对应位置的每个字符的字符编码值。不同类型的比较操作,将引发自动类型转换。另外,JS还规定了:任何操作数与NaN 进行关系比较,结果都是false。

var result = "Brick" < "alphabet"; //true
var result = "Brick".toLowerCase() < "alphabet".toLowerCase(); //false
var result = "23" < "3"; //true
var result = "23" < 3; //false:字符串"23"会被转换成数值23
var result1 = NaN < 3; //false
var result2 = NaN >= 3; //false

检测类型操作符

typeof

容易被问到的JS自身bug:

typeof null === 'object' //true

instanceof

所有引用类型的值都是Object 的实例。
如果变量是给定引用类型(根据它的原型链来识别)的实例,那么instanceof 操作符就会返回true。

alert(person instanceof Object); // 变量person 是Object 吗?
alert(colors instanceof Array); // 变量colors 是Array 吗?
alert(pattern instanceof RegExp); // 变量pattern 是RegExp 吗?

语句

for语句

for-in 语句是一种精准的迭代语句,可以用来枚举对象的属性。以下是for-in 语句的语法:

for (property in expression) statement

break和continue语句

break 和continue 语句用于在循环中精确地控制代码的执行。其中,break 语句会立即退出循环,
强制继续执行循环后面的语句。而continue 语句虽然也是立即退出循环,但退出循环后会从循环的顶
部继续执行。

switch语句

switch (expression) {
	case value: statement
		break;
	case value: statement
		break;
	case value: statement
		break;
	case value: statement
		break;
	default: statement
}

JS的switch语句不同于其他语言(在很多其他语言中只能使用数值。)
JS中,可以在switch语句中使用任何数据类型,无论是字符串 ,还是对象都没有问题。

switch ("hello world") {
	case "hello" + " world":
		alert("Greeting was found.");
		break;
	case "goodbye":
		alert("Closing was found.");
		break;
	default:
		alert("Unexpected message was found.");
}

每个case 值都还可以返回一个布尔值。这样,每个case 按照顺序被求值,直到找到匹配的值或者
遇到default 语句为止。例子:

var num = 25;

switch (true) {
	case num < 0:
		alert("Less than 0.");
		break;
	case num >= 0 && num <= 10:
		alert("Between 0 and 10.");
		break;
	case num > 10 && num <= 20:
		alert("Between 10 and 20.");
		break;
	default:
		alert("More than 20.");
}

函数

函数的基本语法如下所示:

function functionName(arg0, arg1,...,argN) {
	statements
}

函数返回值

ECMAScript 中的函数在定义时不必指定是否返回值。

JS的函数会在执行完return语句之后停止并立即退出。

另外,return 语句也可以不带有任何返回值。在这种情况下,函数在停止执行后将返回undefined值。这种用法一般用在需要提前停止函数执行而又不需要返回值的情况下

无须指定函数的返回值,因为任何ECMAScript 函数都可以在任何时候返回任何值。
实际上,未指定返回值的函数返回的是一个特殊的undefined 值。

函数的参数

ECMAScript 函数的参数与大多数其他语言中函数的参数有所不同。ECMAScript 函数不介意传递进
来多少个参数,也不在乎传进来参数是什么数据类型。

ECMAScript 中的所有参数传递的都是值,不可能通过引用传递参数。而参数实际上是函数的局部变量。即使变量是按值传递给函数参数,对于复杂数据类型而言,仍然会按引用来访问同一个对象。

在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量(即命名参数,或者用ECMAScript 的概念来说,就是arguments 对象中的一个元素)。

在函数体内可以通过arguments 对象来访问这个参数数组,从而获取传递给函数的每一个参数。

通过访问arguments 对象的length 属性可以获知有多少个参数传递给了函数。

function howManyArgs() {
	alert(arguments.length);
}
howManyArgs("string", 45); //2
howManyArgs(); //0
howManyArgs(12); //1

函数能够接收任意个参数并分别实现适当的功能。

function doAdd() {
	if(arguments.length == 1) {
		alert(arguments[0] + 10);
	} else if (arguments.length == 2) {
		alert(arguments[0] + arguments[1]);
	}
}
doAdd(10); //20
doAdd(30, 20); //50

此外,arguments 对象可以与命名参数一起使用。

function doAdd(num1, num2) {
	if(arguments.length == 1) {
		alert(num1 + 10);
	} else if (arguments.length == 2) {
		alert(arguments[0] + num2); // 这里 num2就是arguments[1]
	}
}

关于arguments 的行为,还有一点比较有意思。那就是它的值永远与对应命名参数的值保持同步。

本例中,修改arguments[1],也就修改了num2,结果它们的值都会变成10。不过,这并不是说读取这两个值会访问相同的内存空间;它们的内存空间是独立的,但它们的值会同步。

※严格模式对如何使用arguments 对象做出了一些限制。首先,像前面例子中那样的赋值会变得无
效。也就是说,即使把arguments[1]设置为10,num2 的值仍然还是undefined。其次,重写
arguments 的值会导致语法错误(代码将不会执行)。

Javascript函数的“重载”

ECMAScript 函数不能像传统意义上那样实现重载。

ECMAScirpt函数没有签名,因为其参数是由包含零或多个值的数组来表示的。而没有函数签名,真正的重载是不可能做到的。
如果在ECMAScript 中定义了两个名字相同的函数,则该名字只属于后定义的函数。

function addSomeNumber(num){
	return num + 100;
}
function addSomeNumber(num) {
	return num + 200;
}
var result = addSomeNumber(100); //300

函数addSomeNumber()被定义了两次。第一个版本给参数加100,而第二个版本给参数加
200。由于后定义的函数覆盖了先定义的函数,返回结果是300.

函数的执行环境及作用域

执行环境(execution context)

执行环境(execution context),每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。解析器在处理数据时会在后台使用它。

全局执行环境是最外围的一个执行环境。
根据ECMAScript 实现所在的宿主环境不同,表示执行环境的对象也不一样。在Web 浏览器中,全局执行环境被认为是window 对象,因此所有全局变量和函数都是作为window 对象的属性和方法创建的。
某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境直到应用程序退出——例如关闭网页或浏览器——时才会被销毁)。

每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。ECMAScript 程序中的执行流正是由这个方便的机制控制着。

作用域链(scope chain)

当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。
作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象(activation object)作为变量对象。

活动对象(activation object)

活动对象在最开始时只包含一个变量,即arguments 对象(这个对象在全局环境中是不存在的)。
作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。
标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级地向后回溯,直至找到标识符为止(如果找不到标识符,通常会导致错误发生)。

垃圾收集(Garbage Collection)

JavaScript 具有自动垃圾收集机制。执行环境会负责管理代码执行过程中使用的内存。

垃圾收集机制的原理其实很简单:找出那些不再继续使用的变量,然后释放其占用的内存。浏览器中的实现,则通常有两个策略。
1.标记清除(在IE、Firefox、Opera、Chrome 和Safari 中使用,不过各个浏览器垃圾收集的时间间隔不同。)
JavaScript 中最常用的垃圾收集方式是标记清除(mark-and-sweep)。

如何标记变量其实并不重要,关键在于采取什么策略。

垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记(当然,可以使用任何标记方式)。然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾收集器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。
2.引用计数 Reference counting(不太常见的垃圾收集策略)
循环引用指对象A 中包含一个指向对象B 的指针,而对象B 中也包含一个指向对象A 的引用。

myObject.element = null;
element.someObject = null;

将变量设置为null 意味着切断变量与它此前引用的值之间的连接。当垃圾收集器下次运行时,就会删除这些值并回收它们占用的内存。

性能问题与内存管理

使用具备垃圾收集机制的语言编写程序,开发人员一般不必操心内存管理的问题。但是,JavaScript在进行内存管理及垃圾收集时面临的问题还是有点与众不同。其中最主要的一个问题,就是分配给Web浏览器的可用内存数量通常要比分配给桌面应用程序的少。这样做的目的主要是出于安全方面的考虑,目的是防止运行JavaScript 的网页耗尽全部系统内存而导致系统崩溃。内存限制问题不仅会影响给变量分配内存,同时还会影响调用栈以及在一个线程中能够同时执行的语句数量。

因此,确保占用最少的内存可以让页面获得更好的性能。而优化内存占用的最佳方式,就是为执行中的代码只保存必要的数据。

一旦数据不再有用,最好通过将其值设置为null 来释放其引用——这个做法叫做解除引用(dereferencing)。这一做法适用于大多数全局变量和全局对象的属性。局部变量会在它们离开执行环境时自动被解除引用。

function createPerson(name){
	var localPerson = new Object();
	localPerson.name = name;
	return localPerson;
}
var globalPerson = createPerson("Nicholas");
// 手工解除globalPerson 的引用
globalPerson = null;

解除一个值的引用并不意味着自动回收该值所占用的内存。解除引用的真正作用是让值脱离执行环境,以便垃圾收集器下次运行时将其回收。


Summary

JavaScript 变量可以用来保存两种类型的值:基本类型值和引用类型值。基本类型的值源自以下5种基本数据类型:Undefined、Null、Boolean、Number 和String。基本类型值和引用类型值具有以下特点:

  •  基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中;
  •  从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本;
  •  引用类型的值是对象,保存在堆内存中;
  •  包含引用类型值的变量实际上包含的并不是对象本身,而是一个指向该对象的指针;
  •  从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同一个对象;
  •  确定一个值是哪种基本类型可以使用typeof 操作符,而确定一个值是哪种引用类型可以使用instanceof 操作符。

所有变量(包括基本类型和引用类型)都存在于一个执行环境(也称为作用域)当中,这个执行环境决定了变量的生命周期,以及哪一部分代码可以访问其中的变量。以下是关于执行环境的几点总结:

  •  执行环境有全局执行环境(也称为全局环境)和函数执行环境之分;
  •  每次进入一个新执行环境,都会创建一个用于搜索变量和函数的作用域链;
  •  函数的局部环境不仅有权访问函数作用域中的变量,而且有权访问其包含(父)环境,乃至全局环境;
  •  全局环境只能访问在全局环境中定义的变量和函数,而不能直接访问局部环境中的任何数据;
  •  变量的执行环境有助于确定应该何时释放内存。

JavaScript 是一门具有自动垃圾收集机制的编程语言,开发者不必关心内存分配和回收问题。可以对JavaScript 的垃圾收集过程进行如下总结:

  •  离开作用域的值将被自动标记为可以回收,因此将在垃圾收集期间被删除。
  •  “标记清除”是目前主流的垃圾收集算法,这种算法的思想是给当前不使用的值加上标记,然后再回收其内存。
  •  另一种垃圾收集算法是“引用计数”,这种算法的思想是跟踪记录所有值被引用的次数。JavaScript
  • 引擎目前都不再使用这种算法;但在IE 中访问非原生JavaScript 对象(如DOM 元素)时,这种算法仍然可能会导致问题。
  •  当代码中存在循环引用现象时,“引用计数”算法就会导致问题。
  •  解除变量的引用不仅有助于消除循环引用现象,而且对垃圾收集也有好处。为了确保有效地回收内存,应该及时解除不再使用的全局对象、全局对象属性以及循环引用变量的引用。
43人推荐
随时随地看视频
慕课网APP

热门评论

现在数据类型多个symbol了

大佬幸苦了,让小弟先赞为敬

强大,为作者点赞。肯定费了不少功夫。

查看全部评论