本文深入解析JavaScript的核心概念,涵盖函数与全局作用域、词法作用域规则、闭包的定义与用途、对象的多种创建方式及this指针的动态绑定,最后详细阐述原型链机制,包括Object与Function的关系及原型属性指向,帮助开发者理解JS对象模型。
本文通过具体实例,详细阐述JavaScript中作用域、闭包、对象以及原型链的核心概念与使用方法,旨在为读者提供清晰的参考指南。
1 JavaScript变量作用域
1.1 函数作用域
JavaScript不存在块级作用域,即作用域不由{}界定,而是完全由函数决定。因此,if /for等语句中的花括号并不构成独立的作用域。
如前所述,在函数内部定义的局部变量仅对该函数可见,这被称为函数作用域。
嵌套作用域的变量搜索遵循特定规则:当在函数中引用一个变量时,JavaScript会先搜索当前函数的作用域,若未找到,则逐级向上层作用域搜索,直至全局作用域。
var value = 'global';
var f1 = function(){
console.log(v1); //global
};
f1();
var f2 = function(){
var v1 ='local';
console.log(v1); //local
};
f2();
词法作用域规则指出,函数的嵌套关系是在定义时确定的,而非调用时确定。这意味着嵌套关系由词法分析阶段决定,与运行时状态无关。
var v1 = 'global';
var f1 = function(){
console.log(v1);
}
f1(); //global
var f2 = function(){
var v1 = 'local';
f1();
};
f2(); //global
结合上述两条规则,函数内无论何处定义的局部变量,在进入函数时均已存在但未初始化(值为undefined),直到执行到赋值语句时才被初始化。因此,访问未初始化的变量将返回undefined。
var v1 = 'global';
var f = function(){
console.log(v1); //undefined
var v1 = 'local';
};
1.2 全局作用域
全局作用域中的变量是全局对象的属性,无论身处哪个函数均可直接访问,无需通过全局对象引用。不过,显式使用全局对象有助于提升搜索效率。
符合以下条件的变量属于全局作用域:
- 在最外层定义的变量
- 全局对象的属性
- 在任何地方隐式定义的变量。
2 闭包
2.1 闭包的定义
function f1(){
//context define
function f2(){
//func define
};
return f2;
};
f2(); //获得f1中的context
在C/C++等非函数式编程语言中,虽然可以定义并返回函数指针,但外层函数执行结束后,内层函数的上下文信息会被销毁。而在闭包中,不仅保存了返回的函数,还保留了其上下文信息(由词法作用域支持)。生成闭包后,上下文信息会被单独创建,从而能够生成多个相互独立的闭包实例。
2.2 闭包的用途
闭包主要有两个用途:一是便于实现嵌套的回调函数,二是隐藏对象内部细节。
前者可通过NodeJS的编程风格得到印证;后者则利用函数内部局部变量对外不可见的特性,通过提供访问函数来读取或修改这些变量,从而实现面向对象封装的目的。
3 对象
在基于类型的语言中,对象由类实例化生成;而在JavaScript这种基于原型的系统中,对象是通过原型复制生成的。
3.1 对象的创建与访问
JavaScript中的Object本质上是一个由属性组成的关联数组,每个属性包含名称和值。可以使用new Object()或{}来创建对象。对于简单对象,推荐使用对象初始化器,即通过{}字面值创建。属性名可以是带引号或不带引号的字符串,这对JavaScript而言没有区别。访问对象属性时,既可以使用句点符号,也可以使用关联数组语法['name']。后者的优势在于,当属性名未知时,可以使用变量作为关联数组的索引。
3.2 构造函数
还可以通过自定义构造函数来生成对象,从而实例化更多对象。构造函数本质上也是函数,通常约定使用大写字母开头的函数名。在构造函数内部可以定义成员变量和成员函数。
3.3 上下文对象
在JavaScript中,上下文对象即this指针,代表被调用函数所处的环境。其作用是在函数内部引用调用它的对象本身。this的引入对之前介绍的静态作用域产生了影响,引入了动态特性。
从示例可以看出,通过不同的变量引用同一个函数,会导致调用上下文的不同。
传递与绑定上下文
JavaScript的函数可以通过Call和Apply动态绑定到特定的上下文。
若希望永久绑定上下文,可以使用bind函数。需要注意的是,对同一函数多次调用bind是无效的。
var person = {
name:'noname',
getName:function(){console.log(this.name); }
};
var bill = {name:'Bill'};
person.getName(); //noname
bill.getName = person.getName;
bill.getName(); //Bill
name = 'JavaScript';
func = person.getName;
func(); //JavaScript
3.4 原型
在创建对象时,一般成员应在构造函数内定义,而成员函数应定义在其原型上。
接下来我们将重点介绍原型链。
JavaScript中有两个特殊的对象:Object与Function,它们都是构造函数,用于生成对象。
Object.prototype是所有对象的祖先,Function.prototype是所有函数的原型,包括构造函数。
可以将JavaScript的对象分为三类:用户创建对象、构造函数对象、原型对象。
所有对象都包含一个__proto__属性,指向该对象的原型。
构造函数对象拥有prototype属性,指向其原型对象。通过该构造函数创建的新对象,其__proto__属性将指向构造函数的prototype属性。
原型对象包含一个constructor属性,指向对应的构造函数。
function Foo() {}
var obj = new Object();
var foo = new Foo();

参考文献:《Node.JS开发指南》
更多关于JavaScript相关内容感兴趣的读者可查看本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》
希望本文所述对大家JavaScript程序设计有所帮助。