理解JavaScript面向对象(三):作用域链及原型
来源:互联网 发布:java构造器和构造函数 编辑:程序博客网 时间:2024/06/04 20:11
前两篇我们花费了大量的篇幅讲解了JavaScript中面向对象的基本概念,理解js中的对象,以及JavaScript中为我们提供的原生对象Object,这些都是理解JavaScript面向对象的基础。为了能够理解今天我们要一起研究的原型,在介绍原型之前,我们先来了解几个概念:
1)执行环境和作用域链:
执行环境(简称环境)定义了变量,或者函数有权访问的其他数据,并决定了他们各自的行为。每个执行环境都对应的有一个与之关联的对象(称为变量对象)。虽然我们在编程的时候不直接使用它,但是解析器处理数据时会用到它(之后会举例)。执行环境的最外围是全局执行环境,在浏览器中,全局执行环境为window对象。因此,所有全局变量和函数都是作为window对象的属性和方法而创建的。
在ECMAScript中,每个函数都有自己的执行环境,当程序进入一个函数执行时,就把这个函数的执行环境压入到执行环境栈中。当函数执行完成,再把执行环境从栈中弹出,将执行控制权返回给之前的执行环境。ECMAScript用这种机制控制着程序的执行。为了保证对执行环境中的变量和函数有序的访问,ECMAScript当代码在执行环境中执行时,会创建变量对象(与执行环境关联)的作用域链,链的最前端始终是当前执行代码所在的执行环境相关联的变量对象。下一个对象是来自包含环境(包含这个环境的执行环境),再下一个对象是包含环境的包含环境,以此类推直到全局环境变量对象(始终是作用域链的最后一个对象)。系统判断能否访问某个属性(包括函数,因为函数本质也是对象)就是根据这个作用域链逐级查找的。我们看一个例子,加深一下理解:
注意:如果执行环境是函数,就把它的活动对象(函数的实例)作为变量对象,活动对象在开始时只包含一个对象,就是arguments对象。在上面的例子中,changeColor()的作用域链包含两个对象:它自己的变量对象和全局变量对象。函数中之所以能访问color,是因为从它的作用域链中能够找到它。我们看一个复杂一点的例子:
上面的例子中,一共有三个执行环境,全局环境,changeColor局部环境,swapColor执行环境。此时的作用域链如图:
当执行swapColor()时,虽然swapColor的执行环境中有tmpColor,但是它的父环境(作用域链的下一个环境对象)中包含innerColor,父环境的父环境中包含color,所以,它可以访问所有变量。但是,全局环境就只能访问Color了。
所以,作用域链中的环境变量对象之间的联系是线性的,有序的。内部环境可以通过作用域链访问外部环境,而外部环境不能访问内部环境的变量和函数。每个环境都可以向上搜索作用域链,以查询变量或函数名,但是任何环境都不能向下搜索作用域链而进入到另一个执行环境。
2)再谈函数:
之前我们谈过,函数的本质也是对象,函数名仅仅是指向这个对象的指针而已。既然函数也是对象,它自然也就和其他对象一样,同样具有属性和方法。今天我们就一起看一看,函数的属性和方法:
this属性:是一个特殊的对象,它是一个指针,指向的是函数的执行对象。
arguments:是一个类数组对象,因为它不是Array的实例,但是可以通过[ ]的语法访问。这个对象用于保存传递给函数的参数(实际参数)。
length:表示函数希望接收的命名参数的个数(形参个数)。
prototype:对于引用类型而言,prototype是真正保存他们的所有实例的方法的对象(原型对象)。任何函数都具有的属性,由这个函数所创建的对象,默认会连接到这个属性上。
上面这个例子中,构造方法没有任何成员,而在它的原型对象中添加了name属性。在访问一个对象的属性(包括函数)会在当前对象中去查找,如果没有就会到与之关联的构造方法中去查找,再没有就到与构造方法相关联的原型对象中去查找。
理解了这些,我们就一起揭开原型的神秘面纱,大家都知道,JavaScript是基于原型而实现面向对象的,那么原型有什么好处呢?我们看一个传统的构造函数创建对象的例子:
此例中,实例化了两个Person对象p1和p2,各自都有一个sayHello()的函数。此时的内存结构如图:
输出的结果是:
虽然是两个不同对象,但是函数的逻辑功能是相同的。由于我们内存资源是很宝贵的,考虑到数据的共享性,属性名称是各个对象独立的,而函数是实现相同的逻辑的,可以多个对象来共享。因为我们都知道,对象会到与之联系的prototype中去寻找数据,所以,可以考虑将共享的数据放到prototype中,这样,可以保证,无论创造多少对象,这个方法只有一个副本。这可以大大的节省内存资源,而且创建的对象越多,优势就越明显。引入原型后,同样创建两个对象,内存结构如图:
可以看到,每个对象都有一个__proto__的属性,通过调试,我们可以发现对象的__proto__和它对应的构造函数(创建它的构造函数,否则没有关系)的prototype属性其实是同一个对象,这也就是我们之前在将prototype属性时提到的:由这个函数所创建的对象,默认会连接到这个属性上。我们用一个例子去理解一下:
输出结果也的确是true。那么,我们该如何去理解这两个属性呢?因为__proto__是非标准属性,是相对于对象的实例而言的。我们所说的“原型对象”也没有一个统一的规范。我们可以根据具体的访问情况来理解,例如上面的定义中:Person.prototype 是函数Person的原型属性,是对象p1的原型对象。由于__proto__是非标准的属性,而在大部分描述时我们用prototype。再说原型属性时是针对构造函数Person而言的,在说原型对象时是针对对象实例p1而言的。进行如此简单的划分就不至于混乱了。正是因为原型的引入,实现了数据共享的方法,也就奠定了JavaScript中面向对象的基础。- 理解JavaScript面向对象(三):作用域链及原型
- JavaScript(面向对象+原型理解+继承+作用域链和闭包+this使用总结)
- javascript--面向对象(三)原型对象存在的问题及组合组合使用原型和构造函数
- javascript 面向对象(六)原型的特性和作用
- Javascript原型,原型链,面向对象
- JavaScript基础(12.面向对象及原型简介)
- JavaScript面向对象及原型的理解及笔记整理【一】
- 简单理解javascript原型及原型链
- Javascript面向对象及组件详细介绍(五)原型链
- javascript面向对象(七)原型另外的作用--扩充对象的属性和方法
- Javascript 面向对象原型
- JavaScript面向对象-原型
- 理解javascript原型和作用域系列
- 面向对象三、作用域
- [JavaScript面向对象编程指南]-深入理解JavaScript默认的继承方式——原型链
- javascript--面向对象(四)原型对象,构造函数及实例对象的关系图
- JavaScript 面向对象(七)原型链深入
- javascript面向对象(原型、继承)
- linux 根目录下各文件夹的作用
- 计算机视觉界牛人牛事
- eval()的使用和兼容性问题
- struts1.X配置文件详解
- MATLAB符号函数运算与conj
- 理解JavaScript面向对象(三):作用域链及原型
- 知识回顾之什么是Ajax?
- C# .NET万能数据库访问封装类(ACCESS、SQLServer、Oracle)
- netmap源码分析(五)ioctl 注册过程
- Java 方法传参方式: 按值调用
- Hierarchical Configurations
- at org.apache.cxf.jaxws.EndpointImpl.doPublish(EndpointImpl.java:370) 异常
- Java匿名内部类访问外部变量,为何需被标志为final?
- redis快速入门