Javascript本质第一篇:核心概念

时间:2022-06-28 21:19:20

很多人在使用Javascript之前都至少使用过C++、C#或Java,面向对象的编程思想已经根深蒂固,恰好Javascript在语法上借鉴了Java,虽然方便了Javascript的入门,但要深入理解Javascript的时候,长期使用这些编程语言造成的思维定势却给使用Javascript带来误导。作者在学习Javascript的时候曾陷入了这个误区,希望通过这篇文章让新学者避免走这个弯路,迅速正确地掌握Javascript。

1. 要点

在面对Javascript时,要牢记以下几点:

1.1 Javascript不是面向对象的编程语言

如果非要把Javascript归到面向什么的话,Javascript准确的说是面向原型的编程语言,它是从self语言发展而来,除了语法上借鉴了Java,其它方面和Java什么关系都没有,本质上更不同。简单说,Javascript里面没有类,全是对象。在使用Javascript的时候,应该时刻提醒自己:Javascript不是C++、C#或Java。

1.2 Javascript是解释执行的语言

虽然这很显而易见,但如果不时刻牢记这一点,而把Javascript和编译型语言的运行方式混淆的话,也非常不利于理解Javascript。比如,如果你用C++、C#或Java中的局部变量的思想去理解Javascript函数中通过var定义的变量的话,就会有麻烦。

2. Javascript中的对象和类型

Javascript没有传统面向对象编程语言中的类,全部是对象。

Javascript中的对象键值对的集合,键的类型是字符串,值可以是任意对象。创建新对象的方式有:new 函数()、{}语法、Object.create(原型对象)。

函数也是对象,是一种包含可运行的代码的特殊对象,并且代码能够以函数调用的形式被执行。函数对象能通过function关键字定义或通过new关键字使用Function构造函数来创建。

下面的示例创建了一个函数对象foo,并为它设置三个属性:一个数字,一个字符串,一个函数。从示例可以看到函数对象除了能被调用以外,其他行为和普通对象无异。

Javascript本质第一篇:核心概念

当通过“new 函数()”的语法创建对象时,函数被称为构造函数。

在Javascript中,通常将构造函数称为对象的类型。比如obj = new Fun(),那么称obj的类型为Fun。

Javascript有Undefined,  Null,  Boolean,  Number, String, Object和Function这几种基本类型。Javascript运行时会对Undefined,  Null,  Boolean,  Number, and  String这五种基础类型的对象以及Function类型的函数对象会以特定的方式解释和处理。

因为函数是对象,函数除了可以通过function语法定义以外,也可以用创建对象的方式创建:fun = new Function("参数", "函数体"),Function本身是Javascript内建的函数。

基本类型Boolean,  Number, String, Object, Array, Date等都是内建的函数。没有Undefined和Null内建函数,而是有Undefined和Null类型的内建的唯一的对象,分别是undefined和null。

3. 执行上下文

Javascript中每一行代码的执行都是在当前执行上下文中完成。

执行上下文的层级关系是由代码的定义结构(也就是源代码字面结构)决定的,与运行时函数的调用栈结构无关。

执行上下文由一个上下文变量容器和一个this绑定构成。上下文变量容器中包含当前执行上下文中定义的变量。this绑定表示当前执行上下文中this关键字指向的对象。

简单来说,每次函数调用都进入一个新的执行上下文,同一函数递的归调用也会进入新的执行上下文。

每一个执行上下文都包含对创建它的上一级执行上下文的引用,因此所有执行上下文组成一棵树。

在浏览器中,根执行上下文(即不在任何函数中运行的代码)中没有上下文变量容器,this绑定到全局对象window。因此在根执行上下文中定义变量时,使用var、不使用var和使用this.定义的变量都是全局对象window的属性,因为没有上下文变量容器。通过这一规定,Javascript运行过程中的所有执行上下文都能指向全局对象,形成一棵完整的树。

在除了根执行上下文之外的其它执行上下文中,变量定义的规则如下:

var i = 1 变量i位于当前执行上下文的上下文变量容器中 
j = 2 变量j是全局对象的属性
this.k = 3 变量k是当前执行上下文的this对象的属性

在表达式中使用一个变量时,Javascript将首先从当前执行上下文的上下文变量容器中查找该变量,如果不存在,则在上级执行上下文的上下文变量容器中查找,以此类推,直到根执行上下文,在根执行上下文中,将在全局对象中查找是否有同名的属性。示例如下:

Javascript本质第一篇:核心概念

当前执行上下文的this绑定默认继承自上级执行上下文,如果当前运行代码所在的函数是通过obj.currentfun()的方式调用,也就是obj对象的方法,那么this将被绑定到obj。在使用时,this关键字必须明确写出,因为this在逻辑上是与执行上下文有关的,与当前函数所属的对象没有直接因果关系。

垃圾回收机制:如果当前执行上下文及其中的代码已经执行完成,且其所有下级执行上下文已经释放,且没有来自当前执行上下文之外的对当前执行上下文的上下文变量容器中的变量的引用,那么当前执行上下文被释放,当前执行上下文的上下文变量容器中的变量指向的对象和this指向的对象的引用值减一(各引擎具体实现不同,这里描述的是原理)。

这也就是Javascript中常用到的闭包模式的原理。如果从传统面向对象语言的角度去看,闭包模式看上去很诡异;但是在理解了Javascript的执行上下文后,闭包模式就是一种很自然的代码编写模式。

4. 原型

由于Javascript中没有类的概念,因此也没有面向对象中的继承的概念。在完全没有任何继承机制的编程语言中,每个对象的方法都要明确设置一遍。假如Javascript没有继承机制,通过构造函数function A(name) {this.name= name;};创建了100个A的对象, 现在要为每个对象实现一个新的方法,就需要明确的为这100个对象都添加一个方法属性,当每个对象都有很多方法的时候,会带来巨大的性能开销和非常差的编码体验,使得代码编写方式最终会倒退到面向过程的方式。

Javascript由面向原型的编程语言self发展而来,通过原型实现了另一种方式的继承:属性共享。如果为了和面向对象中的继承区分,Javascript中通过原型实现的应该叫做共享。

Javascript中的原型在物理上是一个对象,可以是任意对象;原型这个词也表示一种机制。

每个对象都可以拥有一个原型,同时这个原型本身还可以有自己的原型,形成一个原型链,直到其原型为null的原型。

当获取对象的一个属性值的时候,先在当前对象的键值对集合中查找这属性,如果找到返回对应的值;如果没有,则在原型对象的键值对集合中查找,如果还没找到,则在原型的原型中查找,以此类推。

通过一个示例来演示原型的使用方法:

Javascript本质第一篇:核心概念

上面例子中,PersonPrototype就是原型对象,Person是构造函数,将Person的prototype属性设置为PersonPrototype对象,那么通过new Person创建的所有对象的原型都是PersonPrototype。

通过示例可以看到,修改原型中showMyName方法的定义,p.showMyName()的输出也会跟着发生变化,这个现象和上面原型的定义一致。

如果明确的为对象p设置一个showMyName方法,那么p对象的键值对集合中将也包含showMyName的定义,根据上面规则的描述的规则,p对象中定义的showMyName将被使用,忽略了原型中对showMyName的定义。这个时候,我们可以通过p.__proto__.showMyName来访问原型中的showMyName方法,但是调用p.__proto__.showMyName的时候,this关键字绑定到了原型对象PersonPrototype,由于原型对象中没有name属性的定义,因此输出“undefined!”。

接下来再定义几个Person对象:

Javascript本质第一篇:核心概念

在上述代码中,所有Person对象都共享了原型对象的方法,除非某个Person对象中重新定义了原型中同名的方法。

在对象中定义与原型中同名的属性时,会在该对象中创建一个新的键值对,而不会覆盖原型中同名属性的值。

一般来说原型对象中通常定义方法和常量,不应该定义对象的状态值,因为多数情况下所有的对象共享一个状态值没有实际意义。比如上面的代码中,name属性在逻辑上不应该被定义在原型中,因为每个人都有自己的名字。

方法是最适合定义在原型中的。共享方法给所有的对象,正是原型的主要作用。

5. 结论

本文介绍了Javascript的三个核心概念:对象、执行上下文和原型。这些是Javascript最重要的内容,理解这三点之后,很快就能掌握Javascript。

Javascript代码中各类表达式与Java基本相同。

Javascript包括很多预定义的类型、对象和对象属性,比如全局对象及其属性,JSON类型和RegExp类型等等,这些查阅手册即可。

当然,还有很多细节性的东西,例如:如果不明确设置Person的原型,该原型默认会是一个Person类型的对象;constructor属性的作用。有机会将在后续章节中展开讨论。