JavaScript高级程序设计(第3版)学习笔记7 js函数(上)

变量类型

  在说函数之前,先来说说变量类型。

1、变量:变量在本质上就是命名的内存空间。

2、变量的数据类型:就是指变量可以存储的值的数据类型,比如Number类型、Boolean类型、Object类型等,在ECMAScript中,变量的数据类型是动态的,可以在运行时改变变量的数据类型。

3、变量类型:是指变量本身的类型,在ECMAScript中,变量类型就只有两种:值类型和引用类型。当变量的数据类型是简单数据类型时,变量类型就是值类型,当变量的数据类型是对象类型时,变量类型就是引用类型。在不引起歧义的情况下,也可以称变量的数据类型为变量类型。

  那么,值类型和引用类型有什么区别呢?最主要的一个,就是当变量类型为值类型时,变量存储的就是变量值本身,而当变量类型为引用类型时,变量存储的并不是变量值,而只是一个指向变量值的指针,访问引用类型的变量值时,首先是取到这个指针,然后是根据这个指针去获取变量值。如果将一个引用类型的变量值赋给另一个变量,最终结果是这两个变量同时指向了一个变量值,修改其中一个会同时修改到另一个:


代码如下:

var a = {
name:'linjisong',
age:29
};
var b = a;//将引用类型的变量a赋给变量b,a、b同时指向了a开始指向的那个对象
b.name = 'oulinhai';//修改b指向的对象,也就是修改了a指向的对象
console.info(a.name);//oulinhai
b = {//将变量重新赋值,但是b原来指向的对象没有变更,也就是a指向的对象没有变化
name:'hujinxing',
age:23
};
console.info(a.name);//oulinhai

好了,关于变量类型先说到这,如果再继续到内存存储数据结构的话,就怕沉得下去浮不上来。

函数

  如果说对象是房间,那么函数就是有魔幻效应的房间了。函数首先是对象,然后这个函数对象还具有很多魔幻功能……

1、函数

(1)函数是对象

  函数也是一种对象,而用于创建函数对象实例的函数就是内置的Function()函数(创建对象实例需要函数,而函数又是一种对象实例,是不是让你有了先有鸡还是先有蛋的困惑?别钻牛角尖了,只要鸡能生蛋,蛋能孵鸡就行了,谁先谁后还是留给哲学家吧),但是函数这种对象,又和一般的对象有着极大的不同,以至于对函数对象实例使用typeof时返回的不是object而是function了。

(2)函数名是指向函数对象的引用类型变量


代码如下:

function fn(p){
console.info(p);
}
console.info(fn);//fn(p),可以将fn作为一般变量来访问
var b = fn;
b('function');//function,可以对b使用函数调用,说明b指向的对象(也就是原来fn指向的对象)是一个函数

注:关于函数名,在ES5的严格模式下,已经不允许使用eval和arguments了,当然,参数名也不能用这两个了(我想除非你是专业黑客,否则也不会使用这些作为标识符来使用吧)。

2、函数创建

(1)作为一种对象,函数也有和普通对象类似的创建方式,使用new调用构造函数Function(),它可以接受任意数量的参数,最后一个参数作为函数体,而前面的所有参数都作为函数的形式参数,前面的形式参数还可以使用逗号隔开作为一个参数传入,一般形式为:


代码如下:

var fn = new Function(p1, p2, ..., pn, body);
//或者
var fn = Function(p1, p2, ..., pn, body);
//或者
var fn = new Function("p1, p2, ..., pn", q1, q2, ..., qn, body);
//或者
var fn = Function("p1, p2, ..., pn", q1, q2, ..., qn, body);

例如:


代码如下:

var add = new Function('a','b','return a + b;');
console.info(add(2,1));//3
var subtract = Function('a','b','return a - b;');
console.info(subtract(2,1));//1
var sum = new Function('a,b','c','return a + b + c;');
console.info(sum(1,2,3));//6

这种方式创建函数,会解析两次代码,一次正常解析,一次解析函数体,效率会影响,但是比较适合函数体需要动态编译的情况。

(2)由于函数对象本身的特殊性,我们还可以使用关键字function来创建函数:


代码如下:

function add(a, b){
return a + b;
}
console.info(add(2,1));//3
var subtract = function(a, b){
return a - b;
};
console.info(subtract(2,1));//1

从上可以看到,使用function关键字创建函数也有两种方式:函数声明和函数表达式。这两种方式都能实现我们想要的效果,那他们之间有什么区别呢?这就是我们下面要讲的。

3、函数声明和函数表达式

(1)从形式上区分,在ECMA-262的规范中,可以看到:


代码如下:

函数声明: function Identifier (参数列表(可选)){函数体}
函数表达式:function Identifier(可选)(参数列表(可选)){函数体}

除了函数表达式的标识符(函数名)是可选的之外没有任何区别,但我们也可以从中得知:没有函数名的一定是函数表达式。当然,有函数名的,我们就只能从上下文来判断了。

(2)从上下文区分,这个说起来简单,就是:只允许表达式出现的上下文中的一定是函数表达式,只允许声明出现的上下文的一定是函数声明。举一些例子:


代码如下:

function fn(){};//函数声明
//function fn(){}(); // 异常,函数声明不能直接调用
var fn = function fn(){};//函数表达式
(function fn(){});//函数表达式,在分组操作符内
+function fn(){console.info(1);}();//1,函数表达式,出现在操作符+之后,因此可以直接调用,这里,也可以使用其它的操作符,比如new
new function fn(){console.info(2);}();//2,函数表达式,new操作符之后
(function(){
function fn(){};//函数声明
});

(3)区别:我们为什么要花这么大力气来区分函数声明和函数表达式呢?自然就是因为它们的不同点了,他们之间最大的不同,就是声明会提升,关于声明提升,在前面基础语法的那一篇文章中,曾经对全局作用域中的声明提升做过讨论,我们把那里的结论复习一下:

A、引擎在解析时,首先会解析函数声明,然后解析变量声明(解析时不会覆盖类型),最后再执行代码;

B、解析函数声明时,会同时解析类型(函数),但不会执行,解析变量声明时,只解析变量,不会初始化。

在那里也举了一些例子来演示(回忆一下),不过没有同名称的声明例子,这里补充一下:


代码如下:

console.info(typeof fn);//function,声明提升,以函数为准
var fn = '';
function fn(){
}
console.info(typeof fn);//string,由于已经执行了代码,这里fn的类型变为string
try{
fn();//已经是string类型,不能调用了,抛出类型异常
}catch(e){
console.info(e);//TypeError
}
fn = function(){console.info('fn');};//如果想调用fn,只能再使用函数表达式赋值给fn
fn();//fn,可以调用

console.info(typeof gn);//function
function gn(){
}
var gn = '';
console.info(typeof gn);//string

可以看出:不管变量声明是在前还是在后,在声明提升时都是以函数声明优先,但是在声明提升之后,由于要执行变量初始化,而函数声明不再有初始化(函数类型在提升时已经解析),因此后面输出时就成为String类型了。

上面第3行定义了一个函数,然后第7行马上调用,结果竟然不行!你该明白保持全局命名空间清洁的重要性了吧,要不然,你可能会遇到“我在代码中明明定义了一个函数却不能调用”这种鬼事情,反过来,如果你想确保你定义的函数可用,最好就是使用函数表达式来定义,当然,这样做你需要冒着破坏别人代码的风险。

还有一个问题,这里我们怎么确定变量类型是在初始化时候而不是在变量声明提升时候改变的呢?看下面的代码:


代码如下:

console.info(typeof fn);//function
function fn(){
}
var fn;
console.info(typeof fn);//function

可以看到,声明提升后类型为function,并且由于没有初始化代码,最后的类型没有改变。

  关于函数声明和函数表达式,还有一点需要注意的,看下面的代码:


代码如下:

if(true){
function fn(){
return 1;
}
}else{
function fn(){
return 2;
}
}
console.info(fn());// 在Firefox输出1,在Opera输出2,在Opera中声明提升,后面的声明会覆盖前面的同级别声明

if(true){
gn = function(){
return 1;
};
}else{
gn = function(){
return 2;
};
}
console.info(gn());// 1,所有浏览器输出都是1

在ECMAScript规范中,命名函数表达式的标识符属于内部作用域,而函数声明的标识符属于定义作用域。


代码如下:

var sum = function fn(){
var total = 0,
l = arguments.length;
for(; l; l--)
{
total += arguments[l-1];
}
console.info(typeof fn);
return total;
}
console.info(sum(1,2,3,4));//function,10
console.info(fn(1,2,3,4));//ReferenceError

上面是一个命名函数表达式在FireFox中的运行结果,在函数作用域内可以访问这个名称,但是在全局作用域中访问出现引用异常。不过命名函数表达式在IE9之前的IE浏览器中会被同时作为函数声明和函数表达式来解析,并且会创建两个对象,好在IE9已经修正。

  除了全局作用域,还有一种函数作用域,在函数作用域中,参与到声明提升竞争的还有函数的参数。首先要明确的是,函数作用域在函数定义时不存在的,只有在函数实际调用才有函数作用域。


代码如下:

// 参数与内部变量,参数优先
function fn(inner){
console.info(inner);// param
console.info(other);// undefined
var inner = 'inner';
var other = 'other';
console.info(inner);// inner
console.info(other);// other
}
fn('param');

// 参数与内部函数,内部函数优先
function gn(inner){
console.info(inner);// inner()函数
console.info(inner());// undefined
function inner(){
return other;
}
var other = 'other';
console.info(inner);// inner()函数
console.info(inner());// other
}
gn('param');

通过上面的输出结果,我们得出优先级:内部函数声明 > 函数参数 > 内部变量声明。

  这里面的一个过程是:首先内部函数声明提升,并将函数名的类型设置为函数类型,然后解析函数参数,将传入的实际参数值赋给形式参数,最后再内部变量声明提升,只提升声明,不初始化,如果有重名,同优先级的后面覆盖前面的,不同优先级的不覆盖(已经解析了优先级高的,就不再解析优先级低的)。
  说明一下,这只是我根据输出结果的推断,至于后台实现,也有可能步骤完全相反,并且每一步都覆盖前一步的结果,甚至是从中间开始,然后做一个优先级标志确定是否需要覆盖,当然,从效率上来看,应该是我推断的过程会更好。另外,全局作用域其实就是函数作用域的一个简化版,没有函数参数。

  这里就不再举综合的例子了,建议将这篇文章和前面的基础语法那一篇一起阅读,可能效果会更好。关于优先级与覆盖,也引出下面要说的一个问题。

4、函数重载

  函数是对象,函数名是指向函数对象的引用类型变量,这使得我们不可能像一般面向对象语言中那样实现重载:


代码如下:

function fn(a){
return a;
}
function fn(a,b){
return a + b;
}

console.info(fn(1)); // NaN
console.info(fn(1,2));// 3

不要奇怪第8行为什么输出NaN,因为函数名只是一个变量而已,两次函数声明会依次解析,这个变量最终指向的函数就是第二个函数,而第8行只传入1个参数,在函数内部b就自动赋值为undefined,然后与1相加,结果就是NaN。换成函数表达式,也许就好理解多了,只是赋值了两次而已,自然后面的赋值会覆盖前面的:


代码如下:

var fn = function (a){ return a; }
fn = function (a,b){ return a + b;}

那么,在ECMAScript中,怎么实现重载呢?回想一下简单数据类型包装对象(Boolean、Number、String),既可以作为构造函数创建对象,也可以作为转换函数转换数据类型,这是一个典型的重载。这个重载其实在前一篇文章中我们曾经讨论过:

(1)根据函数的作用来重载,这种方式的一般格式为:


代码如下:

function fn(){
if(this instanceof fn)
{
// 功能1
}else
{
// 功能2
}
}

这种方式虽然可行,但是很明显作用也是有限的,比如就只能重载两次,并且只能重载包含构造函数的这种情形。当然,你可以结合apply()或者call()甚至ES5中新增的bind()来动态绑定函数内部的this值来扩展重载,但这已经有了根据函数内部属性重载的意思了。
(2)根据函数内部属性来重载


代码如下:

function fn(){
var length = arguments.length;
if(0 == length)//将字面量放到左边是从Java中带过来的习惯,因为如果将比较操作符写成了赋值操作符(0=length)的话,编译器会提示我错误。如果你不习惯这种方式,请原谅我
   {
return 0;
}else if(1 == length)
{
return +arguments[0];
}else{
return (+arguments[0])+(+arguments[1]);
}
}

console.info(fn());//0
console.info(fn(1));//1
console.info(fn(true));//1
console.info(fn(1,2));//3
console.info(fn('1','2'));//3

这里就是利用函数内部属性arguments来实现重载的。当然,在内部重载的方式可以多种多样,你还可以结合typeof、instanceof等操作符来实现你想要的功能。至于内部属性arguments具体是什么?这就是下面要讲的。

5、函数内部属性arguments

  简单一点说,函数内部属性,就是只能在函数体内访问的属性,由于函数体只有在函数被调用的时候才会去执行,因此函数内部属性也只有在函数调用时才会去解析,每次调用都会有相应的解析,因此具有动态特性。这种属性有:this和arguments,这里先看arguments,在下一篇文章中再说this。

(1)在函数定义中的参数列表称为形式参数,而在函数调用时候实际传入的参数称为实际参数。一般的类C语言,要求在函数调用时实际参数要和形式参数一致,但是在ECMAScript中,这两者之间没有任何限制,你可以在定义的时候有2个形式参数,在调用的时候传入2个实际参数,但你也可以传入3个实际参数,还可以只传入1个实际参数,甚至你什么参数都不传也可以。这种特性,正是利用函数内部属性来实现重载的基础。

(2)形式参数甚至可以取相同的名称,只是在实际传入时会取后面的值作为形式参数的值(这种情况下可以使用arguments来访问前面的实际参数):


代码如下:

function gn(a,a){
console.info(a);
console.info(arguments[0]);
console.info(arguments[1]);
}
gn(1,2);//2,1,2
gn(1);//undefined,1,undefined

这其实也可以用本文前面关于声明提升的结论来解释:同优先级的后面的覆盖前面的,并且函数参数解析时同时解析值。当然,这样一来,安全性就很成问题了,因此在ES5的严格模式下,重名的形式参数被禁止了。

(3)实际参数的值由形式参数来接受,但如果实际参数和形式参数不一致怎么办呢?答案就是使用arguments来存储,事实上,即便实际参数和形式参数一致,也存在arguments对象,并且保持着和已经接受了实际参数的形式参数之间的同步。将这句话细化一下来理解:

•arguments是一个类数组对象,可以像访问数组元素那样通过方括号和索引来访问arguments元素,如arguments[0]、arugments[1]。
•arguments是一个类数组对象,除了继承自Object的属性和方法(有些方法被重写了)外,还有自己本身的一些属性,如length、callee、caller,这里length表示实际参数的个数(形式参数的个数?那就是函数属性length了),callee表示当前函数对象,而caller只是为了和函数属性caller区分而定义的,其值为undefined。
•arguments是一个类数组对象,但并不是真正的数组对象,不能直接对arguments调用数组对象的方法,如果要调用,可以先使用Array.prototype.slice.call(arguments)先转换为数组对象。
•arguments保存着函数被调用时传入的实际参数,第0个元素保存第一个实际参数,第1个元素保存第二个实际参数,依次类推。
•arguments保存实际参数值,而形式参数也保存实际参数值,这两者之间有一个同步关系,修改一个,另一个也会随之修改。
•arguments和形式参数之间的同步,只有当形式参数实际接收了实际参数时才存在,对于没有接收实际参数的形式参数,不存在这种同步关系。
•arguments对象虽然很强大,但是从性能上来说也存有一定的损耗,所以如果不是必要,就不要使用,建议还是优先使用形式参数。


代码如下:

fn(0,-1);
function fn(para1,para2,para3,para4){
console.info(fn.length);//4,形式参数个数
console.info(arguments.length);//2,实际参数个数
console.info(arguments.callee === fn);//true,callee对象指向fn本身
console.info(arguments.caller);//undefined
console.info(arguments.constructor);//Object(),而不是Array()
try{
arguments.sort();//类数组毕竟不是数组,不能直接调用数组方法,抛出异常
}catch(e){
console.info(e);//TypeError
}
var arr = Array.prototype.slice.call(arguments);//先转换为数组
console.info(arr.sort());//[-1,0],已经排好序了

console.info(para1);//0
arguments[0] = 1;
console.info(para1);//1,修改arguments[0],会同步修改形式参数para1

console.info(arguments[1]);//-1
para2 = 2;
console.info(arguments[1]);//2,修改形式参数para2,会同步修改arguments[1]

console.info(para3);//undefined,未传入实际参数的形式参数为undefined
arguments[2] = 3;
console.info(arguments[2]);//3
console.info(para3);//undefined,未接受实际参数的形式参数没有同步关系

console.info(arguments[3]);//undefined,未传入实际参数,值为undefined
para4 = 4;
console.info(para4);//4
console.info(arguments[3]);//undefined,为传入实际参数,不会同步
}

经过测试,arguments和形式参数之间的同步是双向的,但是《JavaScript高级程序设计(第3版)》中第66页说是单向的:修改形式参数不会改变arguments。这可能是原书另一个Bug,也可能是FireFox对规范做了扩展。不过,这也让我们知道,即便经典如此,也还是存有Bug的可能,一切当以实际运行为准。

•结合arguments及其属性callee,可以实现在函数内部调用自身时与函数名解耦,这样即便函数赋给了另一个变量,而函数名(别忘了,也是一个变量)另外被赋值,也能够保证运行正确。典型的例子有求阶乘函数、斐波那契数列等。


代码如下:

//求阶乘
function factorial(num){
if(num <= 1)
{
return 1;
}else{
return num * factorial(num - 1);
}
}
var fn = factorial;
factorial = null;
try{
fn(2);//由于函数内部递归调用了factorial,而factorial已经赋值为null了,所以抛出异常
}catch(e){
console.info(e);//TypeError
}

//斐波那契数列
function fibonacci(num){
if(1 == num || 2 == num){
return 1;
}else{
return arguments.callee(num - 1) + arguments.callee(num - 2);
}
}
var gn = fibonacci;
fibonacci = null;
console.info(gn(9));//34,使用arguments.callee,实现了函数对象和函数名的解耦,可以正常执行

递归的算法非常简洁,但因为要维护运行栈,效率不是很好。关于递归的优化,也有很多非常酣畅漓淋的算法,这里就不深入了。

  需要注意的是,arguments.callee在ES5的严格模式下已经被禁止使用了,这时候可以使用命名的函数表达式来实现同样的效果:


代码如下:

//斐波那契数列
var fibonacci = (function f(num){
return num <= 2 ? 1 : (f(num - 1) + f(num - 2));
});
var gn = fibonacci;
fibonacci = null;
console.info(gn(9));//34,使用命名函数表达式实现了函数对象和函数名的解耦,可以正常执行

(0)

相关推荐

  • JavaScript高级程序设计(第3版)学习笔记 概述

    在JavaScript面世之初,没有人会想到它会被应用的如此广泛,也远比一般人想象中的要复杂强大的多,在我自己学习的过程中,曾经有过多次震撼,只是常常没有过多久,很多美轮美奂的用法就又模糊起来,希望通过对JavaScript高级程序设计(第3版)的专题学习笔记,能够较为系统的将基础知识梳理一次,也能够将自己平常学习与工作过程中遇到的一些美妙用法记录下来,便于自己再次学习,当然,也希望可以给有需要的朋友们一些力所能及的帮助. 相关术语 先简要说一下和JavaScript相关的一些背景术语,就不详细

  • JavaScript高级程序设计(第3版)学习笔记2 js基础语法

    这一篇复习一下ECMAScript规范中的基础语法,英文好的朋友可以直接阅读官方文档.JavaScript本质上也是一种类C语言,熟悉C语言的朋友,可以非常轻松的阅读这篇文章,甚至都可以跳过,不过建议你最好还是看一看,在介绍的同时,我可能会引用一些自认为不易理解且比较流行的用法. 基础语法 1.标识符:所谓标识符,实际上就是指一个满足一定规范,能够被引擎识别的名字,可以用来表示常量.变量.函数名.函数参数.对象.对象属性等所有可命名对象的名称. (1)区分大小写. (2)以字母.下划线(_)或美

  • JavaScript高级程序设计(第3版)学习笔记6 初识js对象

    在房子里面可以放你想放的任意事物--如果你有足够的美学造诣,你甚至可以弄一个房中房试试--当然,为了方便管理,我们会给房子里存放的所有事物都会取上一个不重复的名字,比如医药房间里的各种药品名称.在ECMAScript中,你可以在对象中存放任意你想放的数据,同样,我们需要给存放的数据取一个名字--也就是对象的属性名,再存放各种数据.再看看ECMA-262中对象的定义:无序属性的集合,其属性可以包含简单数据类型值.对象或者函数. 进入对象,我开始有些激动了,说实话,让我想起做这系列学习笔记的最初原因

  • JavaScript高级程序设计(第3版)学习笔记10 再访js对象

    1.对象再认识 (1)对象属性和特性 什么是属性(Property),什么是特性(Attribute),这有什么区别?我不想也不会从语义学上去区分,对于这系列文章来说,属性就是组成对象的一个部分,广义上也包括对象的方法,而特性则是指被描述主体所具有的特征,换句话说,属性是我们可以通过编码来访问的具体存在,而特性则主要是为了便于理解概念的抽象存在,当然,特性也可以通过相应的属性来具体外化.这一小节所讲的对象属性的特性就是对对象属性特征的一个描述,主要来自于ECMA-262规范的第5版,该规范使用两

  • JavaScript高级程序设计(第3版)学习笔记3 js简单数据类型

    ECMAScript是一种动态类型的语言,构建于5种简单数据类型(Undefined.Null.Boolean.Number.String)和一种复杂数据类型(Object)的基础之上.这篇文章就来复习一下简单数据类型,我会尽量从编程实践的角度来描述,下面代码运行环境为FireFox 14.0.1. 简单数据类型 简单数据类型 取值 Undefined undefined(只有一个值) Null null(只有一个值) Boolean true|false(只有两个值) Number 数值 St

  • JavaScript高级程序设计(第3版)学习笔记9 js函数(下)

    再接着看函数--具有魔幻色彩的对象. 9.作为值的函数 在一般的编程语言中,如果要将函数作为值来使用,需要使用类似函数指针或者代理的方式来实现,但是在ECMAScript中,函数是一种对象,拥有一般对象具有的所有特征,除了函数可以有自己的属性和方法外,还可以做为一个引用类型的值去使用,实际上我们前面的例子中已经有过将函数作为一个对象属性的值,又比如函数也可以作为另一个函数的参数或者返回值,异步处理中的回调函数就是一个典型的用法. 复制代码 代码如下: var name = 'linjisong'

  • JavaScript高级程序设计(第3版)学习笔记13 ECMAScript5新特性

    接下来应该是BOM和HTML5了,但是鉴于ECMAScript5相对于ECMAScript3的新变化比较多,而且这些变化也非常的有意思,因此在这篇文章中再将我认为的有意思的变化(并非全部变化)集中整理一下,但这里只是列举,不具体展开. 一.语法变化 1.关键字和保留字 在ES3中,使用关键字做标识符会导致"Identifier Expected "错误,而使用保留字做标识符可能会也可能不会导致相同的错误,具体取决于特定的引擎.在ES5中,关键字和保留字虽然不能作为标识符 使用,但可以作

  • JavaScript高级程序设计(第3版)学习笔记5 js语句

    砖瓦和水泥都有了,接下来该是砌墙了,在ECMAScript中,语句就是我们需要砌的墙了.语句也和操作符一样,对于有C背景的人来说再自然不过了,下面采用类似的形式整理一下语句的相关知识,重点突出一些ECMAScript中比较特别和个人认为比较有意思的地方,同样,没有强调的但比较基础的语句并非不重要,而是我认为你已经熟悉. 语句一览 语句 语法 简要描述 简单语句 ; 语句以分号(;)结束,在不引起歧义的情况下也可以省略分号. 语句块 {} 使用大括号({})将一组语句放一起组成一个语句块,在ECM

  • JavaScript高级程序设计(第3版)学习笔记11 内建js对象

    内建对象就好比是JDK中的类库,开发者可以直接拿来使用,这极大的方便了常见的编程任务.这篇文章就来浏览一下主要的内建对象,当然,我们并不是第一次接触内建对象,前面已经接触到的就有Object.Function.Boolean.Number.String,对于已经介绍过的,这里再总结复习一下,没有介绍过的,根据相关性来对比的总结,RegExp对象及正则表达式在下一篇中再单独介绍. 1.内建全局单例对象 (1)内建全局单例对象:在整个执行环境中只有一个对象实例,这些对象没有内部属性[[Constru

  • JavaScript高级程序设计(第3版)学习笔记8 js函数(中)

    6.执行环境和作用域 (1)执行环境(execution context):所有的JavaScript代码都运行在一个执行环境中,当控制权转移至JavaScript的可执行代码时,就进入了一个执行环境.活动的执行环境从逻辑上形成了一个栈,全局执行环境永远是这个栈的栈底元素,栈顶元素就是当前正在运行的执行环境.每一个函数都有自己的执行环境,当执行流进入一个函数时,会将这个函数的执行环境压入栈顶,函数执行完之后再将这个执行环境弹出,控制权返回给之前的执行环境. (2)变量对象(variable ob

  • JavaScript高级程序设计(第3版)学习笔记4 js运算符和操作符

    在ECMAScript中,有非常丰富的运算符和操作符,在这篇文章中将按通常的分类来稍微整理一下,不过在整理之前,先说明一下: 1.虽然标题是运算符和操作符,然而在我看来并没有多少严格区分的必要,在英文中,貌似也是用一个Operator来表示,所以在下文中我可能会混用.甚至,一些不属于运算符和操作符范畴的,我也整理在这里,只要我觉得必要. 2.对于运算符的优先级,你无需一一牢记--我相信你知道最简单的"先乘除,后加减",至于其它的,如果你不确定,加上括号好了.在ECMAScript中,优

  • JavaScript高级程序设计(第3版)学习笔记12 js正则表达式

    需要指出的是,这里只是总结了正则表达式的常用的且比较简单的语法,而不是全部语法,在我看来,掌握了这些常用语法,已经足够应对日常应用了.正则表达式不只是应用在ECMAScript中,在JAVA..Net.Unix等也有相应应用,这篇文章则是以ECMAScript中的正则表达式为基础总结的. 一.正则表达式基础 1.普通字符:字母.数字.下划线.汉字以及所有没有特殊意义的字符,如ABC123.在匹配时,匹配与之相同的字符. 2.特殊字符:(需要时,使用反斜杠"\"进行转义) 字符 含义 字

随机推荐