你的位置:首页 > Java教程

[Java教程]JavaScript Oriented[探究面向对象的JavaScript高级语言特性]


JavaScript Oriented

探究面向对象的JavaScript高级语言特性

Prologue . JavaScript Introduce

1.  JS

Abstract

JavaScript是由Netscape公司工程师Brendan Eich研发的脚本语言,经过推广和流行,兼容ECMA-262标准,至今用于描述HTML网页行为。(前端验证,检测,响应,触发,控制等动态行为)

Knowledge Tree

 

2.     About Document

本文涉及到的概念有JavaScript概述,对象类型系统,原型链,作用域链以及上下文this,闭包,命名空间以及面向对象的高级语言特性应用。

JavaScript知识点庞杂且个人能力和学习时间有限,希望得到有心者更多的鼓励与启发。

 

Chapter one . Type Family

1.  That’s all Object things?

从面向对象角度理解,JS确实一切皆对象。但是JS是函数式编程,从面向过程角度可以理解为一切皆函数,这可能是JavaScript魅力所在。本文叫做面向JavaScript,偏向从面向对象角度理解。

        如何理解“一切皆对象”。一般来说我们会从对象构造器和原型链解释,本文后文中有详细概述,这里不作初步探讨。

 

2.     Begin from GLOBAL

这里我们从JS最特殊的内置对象GLOBAL开始。

介绍

GLOBAL是ECMAScript5规范中两个内置对象其中之一,表示全局对象。

作用

任何不属于JavaScript其他对象的属性和方法都属于GLOBAL。

实现

JavaScript对其并没有明确的实现。浏览器将GLOBAL作为宿主对象Window的一部分实现。

JSEngine的起始阶段就是实例化一个Window对象。

这也解释了(this === window) == true;

属性

预定义对象

Object Array Function Boolean String Number Date RegExp Error EvalError RangeError ReferenceError SyntaxError TypeError URIError(作为函数原型的构造器)

全局属性

undefined NaN Infinity …

全局函数

编解码

decodeURI()/decodeURIComponent()/encodeURI()/

encodeURIComponent()/escape()/unescape()

转换

getClass()/Number()/String()/parseFloat()/parseInt()

判断

isFinite()/isNaN()

执行

eval()

用户定义

如var _temp_val = {}; // window._temp_val = {};全局变量混乱问题

Tips:全局属性可以直接使用而不用[window. ] Object,同时除了用户定义以为的属性不能delete

在JS引擎启动并实例化window对象时,JS原生对象和浏览器对象全部作为预定义对象放置在window中。

当我们需要新建对象时,首先对象原型将会依据这些预定义对象被构建。

举个小栗砸:(这里有一些构造器和原型链的应用,对后面章节的梳理有帮助。)

         var window.namespaceA = [];//声明命名空间

         namespaceA.A = {…};//声明函数,JS引擎加载时将A.prototype丢到堆内存

         var a = Object.create(A.prototype,{ …args });

JS引擎行为

Object() = window.Object.prototype.constructor;//Object构造函数

Function() = window.Function.prototype.constructor;//Function构造函数

A() = window.namespaceA.A.prototype.constructor; // A构造函数

         a = new A({ …args});//以A对象为原型实例化引用a

         a.[[prototype]] = A.prototype;

3. We Are Family

        该图总结并参考了ECMAScript5规范,如有错误请指出。

        部分对象请参考W3C教程:http://www.w3school.com.cn/jsref/index.asp

       

Chapter two . User Object

            这一篇章进入对象的讲解,讲述在当前JS执行环境executable code(ECStack)中,如何创建一个原型实例对象。分配内存,形成作用域链与原型链。改变执行控制权并返回对象。

总结:this new a object by prototype chain in ECStack, then this got return and leave ECStack.

1.     Context – ECStack

JS引擎执行过程是JS对象的生命周期的交替的过程,JS引擎解析执行。当执行子函数时,会将引擎操作的控制权让给子函数。子函数本身就是一个函数上下文。

window.ECStack = [];//模拟执行环境,实际的执行环境包含window全局上下文

备注:与执行上下文相关的作用域和参数对象在new小节讲解。

执行环境ECStack内存时序结构图如图所示。

 

2.     WHAT – object

ECMAScript5规范丰富了用户自定义Object对象,提供了Object对象的属性特性。

 

参考W3C教程:http://www.w3school.com.cn/js/pro_js_referencetypes.asp

备注: JSON对象拓展,使用JSON. Stringify(/*Object*/)和JSON.parse(/*String*/)进行对象序列化,有时依赖引用对象的toJSON()方法。

3.     TYPE – prototype chain

JS是元解释型语言,当JS引擎执行JS代码时,会分析语法结构,并将得到的对象结构放置在堆内存中,称为原型。内存中的所有对象都包含一个属性[[prototype]]指针指向堆内存的原型,FireFox,Chrome等浏览器将该属性定义为__proto__可显示调用。

酱:Foo.__proto__ à Foo.prototype

同时原型对象存在原型,形成一条原型链。当JS引擎实例化window对象构造全局上下文this时,堆内存中生成如下原型链:

 

        图中每一个矩形都是一个原型对象。每个原型对象都有自己的构造器函数。

        酱:Foo.prototype.constructor == Foo();//注意这里是函数,只是JS允许写成Foo。

        我将JAVA与JavaScript做类比,虽然这样可能不太妥当。

JavaScript概念

Java概念

预定义对象,如Object

API对象,如java.lang.Object

内核

JVM

内核初始化window时,原型对象Object.prototype加载到堆内存中

JVM启动后,类信息Object.class加载到方法区中。

Object()//Object.prototype. constructor构造函数

Object构造函数

var obj = new Object();

Object obj = new Object();

obj.__proto__ == Object.prototype

obj.getClass() == Object.class;

        Constructor构造函数概念是一个动态概念,意味着运行时内核中的原型对象才包含构造函数。理解预定义对象,原型对象,原型对象构造函数和实例化对象后,需要引申一个概念:如何判定对象类型。

关键字

类型

返回值

说明

typeof

一元运算符

字符串

基本类型undefined string number Boolean

引用类型object 函数类型 function。

不返回null的原因:object JS类型值是存在32 BIT 单元里,32位有1-3位表示TYPE TAG,其它位表示真实值。object的标记位为000。而null标记位为00,最终体现的类型还是object..

instanceof

二元运算符

布尔值

判断除undefined null一个变量是否某个对象的实例,就是看该对象是否在该变量的原型链上。

constructor

函数

构造函数

var temp = obj.constructor.toString();

temp.replace(/^function (\w+)\(\).+$/,'$1');

prototype

对象

原型类型

Object.prototype.toString.call(obj).slice(8,-1).toLowerCase();

这时候我们可以把本节原型链内存结构图补齐啦:

 

当查询对象的属性时,若该对象不存在该属性,则在该对象的原型链中向上查找对象原型是否存在该属性。若存在,则返回该属性,若直到原型为null时仍未找到该属性,返回undefined。

 

javascript找属性就是找最近的,找不着就找他爹。

4.     WHO – this

this是当前执行环境上下文的一个属性。JavaScript是单线程的,意味着JS内核执行JS代码切换上下文,this就会改变。若JS执行到代码片段A,说明包含片段A的上一级内容B正在被加载/实例化,那么B就是this。

ECStack = { VO : {…}, this : thisValue};

初始状态

当浏览器加载请求页面时,卸载原始事件,解析并渲染HTML,驱动当前事件并绑定当前窗口对象。每个窗口都有自己的全局对象,当页面被加载后,window对象实例化并绑定this。

function main(){
                window = new Window();

this.call(window);//扩展this环境对象,其实只有函数对象才有call方法

|

无状态

这一块并没有过多的代码演示。个人有两点理解。

  1. A调用方法a,则a方法内的this为A。
  2. 若A中没有a方法,则a方法内的this为A原型链上包含方法a最近的原型对象B。

function Foo () {}

Foo.prototype.foo = "bar";

Foo.prototype.logFoo = function () { eval("console.log(this.foo)");//logs "bar" }

var foo = new Foo ();

foo.logFoo();//foo调用logFoo方法,只有foo原型有logFoo方法,所以Foo.prototype为this

备注:判断a是否是A的自由属性,使用hasOwnProperty方法。

备注:可以使用with,apply,call,bind方法连接this环境上下文,实现属性继承,这里不作过多说明。

5.    DO – new

Introduce

new关键词实例化一个对象。对象实例化过程,对this的不断赋值,构造参数对象arguments,并维护实例对象的constructor属性。

Step

举个栗砸。使用构造函数自定义对象:

1 function A(/* number*/ id, /*string*/name){ this.id = id; this.name = name;}

2 var a = new A(1);//a.__proto__ =A.prototype

分解步骤如下:

  1. 执行1行时,加载A对象构造函数并放入内存,构造原型链

A.prototype.constructor=A(){…};

A() = new Function();

Function() = newObject();

  1. 执行2行时,使用构造函数实例化a,并将A.prototype作用域给实例a

Var a = new A();

A.call(a, _args);//_args.id=1;

  1. 执行内存中构造函数A.prototype.constructor前,构造参数对象arguments对象

arguments = A.prototype.slice.call(_args);;//传入参数

arguments.callee ==this;//参数对象调用者为当前函数体

备注:arguments.length表示实际传参数,arguments.callee.length表示期望传参数

  1. 执行内存中构造函数A.prototype.constructor时,根据参数赋值,继承原型链中的属性方法。

a.id = arguments[1];//值为1

a.name = arguments[2];//值为undefined

a.constructor = A.prototype.constructor= A(){…};//继承

  1. 返回值

return a;

Ways

新建对象方法

描述

备注

使用JS内存中内置对象的构造方法

var str = new String(“str”);

只能实例化内置对象

使用JSON字面量

var o = {‘name’ :‘sapphire’};

方便快捷的方式

工厂模式

自定义函数中创建对象并返回

抽象方式

构造函数模式

function Foo(_args){…};

var foo = new Foo(_args);

常用方式

方法无法共享

原型模式

function Foo(id){Foo.prototype.id = id};

切断已存在实例与原型对象关系

属性共享

组合模式

function Foo(){…};

Foo.prototype = {fun : function(){}};

构造函数模式构造属性

原型模式构造方法

6.    RESULT – return

当构造函数无特殊声明return返回值时,返回实例化对象。

 

Chapter three . closure

函数闭包是一种技术,尤其被Lambdas语言广泛应用。Java中可以使用匿名函数的方式模拟闭包。

1.    Introduce

闭包 = 闭包函数 + 一组自由变量与名称绑定存储区映射,实现函数外部访问函数内部变量的技术。

原理:如果一个函数包含内部函数,那么它们都可以看到其中声明的变量;这些变量被称为‘自由’变量。然而,这些变量可以被内部函数捕获,从高阶函数中return实现‘越狱’,以供以后使用。唯一需要注意的是,捕获函数必须在外部函数内定义。函数内没有任何局部声明之前(既不是被传入,也不是局部声明)使用的变量就是被捕获的变量。

备注:Javascript中20%以上的博客是关于闭包概念的,这里不再赘述。

弊端:应用时需分析函数变量,检查闭包是否会互相产生干扰,检查闭包的实例是否相同。

利端:闭包函数实例化时只执行一次,将不需要暴露在外层环境的变量封装在内部,减少了外部变量。

Chapter four . inherit

在ECMAScript中,只支持实现继承而不支持签名继承(接口继承)实现继承基本是通过原型链继承。

继承方式

示例

原型链继承

Sub.prototype = new Super();

构造函数继承

function Sub(){ Super.call(this);}

组合继承

 

原型式继承

Object.create(prototype)

 

Chapter five . namespace

以上我们接触了全局变量和this的概念,前端JS开发中,不规范的命名规则会导致JS全局变量混乱甚至冲突。

下面这段代码示例规范变量命名空间

varGLOBAL = {};

GLOBAL.namespace= function(str){

  var arr = str.split('.');

  var start = 0;

  if(arr[0] == 'GLOBAL'){ start = 1; }

  for(var i = start; i < arr.length; i++){

    GLOBAL[arr[i]] = GLOBAL[arr[i]] || {};

    GLOBAL = o[arr[i]];

  }

};

假设a变量完成A功能中A1子功能。b变量完成A功能中A2子功能。c变量完成B功能。那么

GLOBAL.namespace('A.A1);A.A1.a = …;

GLOBAL.namespace('A.A2);A.A2.b = …;

GLOBAL.namespace('A.A1);B.c = …;

同时,

尽量在匿名函数中声明变量而非全局环境中。

为代码添加更多注释。

Chapter six . reference

火狐开发者JS文档

https://developer.mozilla.org/en-US/docs/Web/JavaScript/A_re-introduction_to_JavaScript

IBM开发者社区

http://www.ibm.com/developerworks/cn/web/

有关ECMA-262个人站点

http://dmitrysoshnikov.com/