探究面向对象的JavaScript高级语言特性javascript
JavaScript是由Netscape公司工程师Brendan Eich研发的脚本语言,通过推广和流行,兼容ECMA-262标准,至今用于描述HTML网页行为。(前端验证,检测,响应,触发,控制等动态行为)前端
本文涉及到的概念有JavaScript概述,对象类型系统,原型链,做用域链以及上下文this,闭包,命名空间以及面向对象的高级语言特性应用。java
JavaScript知识点庞杂且我的能力和学习时间有限,但愿获得有心者更多的鼓励与启发。web
从面向对象角度理解,JS确实一切皆对象。可是JS是函数式编程,从面向过程角度能够理解为一切皆函数,这多是JavaScript魅力所在。本文叫作面向JavaScript,偏向从面向对象角度理解。编程
如何理解“一切皆对象”。通常来讲咱们会从对象构造器和原型链解释,本文后文中有详细概述,这里不做初步探讨。浏览器
这里咱们从JS最特殊的内置对象GLOBAL开始。闭包
介绍app |
GLOBAL是ECMAScript5规范中两个内置对象其中之一,表示全局对象。ide |
||
做用函数式编程 |
任何不属于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中。
当咱们须要新建对象时,首先对象原型将会依据这些预约义对象被构建。
举个小栗砸:(这里有一些构造器和原型链的应用,对后面章节的梳理有帮助。)
1 var window.namespaceA = [];//声明命名空间 2 namespaceA.A = {…};//声明函数,JS引擎加载时将A.prototype丢到堆内存 3 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;
、
该图总结并参考了ECMAScript5规范,若有错误请指出。
部分对象请参考W3C教程:http://www.w3school.com.cn/jsref/index.asp
这一篇章进入对象的讲解,讲述在当前JS执行环境executable code(ECStack)中,如何建立一个原型实例对象。分配内存,造成做用域链与原型链。改变执行控制权并返回对象。
总结:this new a object by prototype chain in ECStack, then this got return and leave ECStack.
JS引擎执行过程是JS对象的生命周期的交替的过程,JS引擎解析执行。当执行子函数时,会将引擎操做的控制权让给子函数。子函数自己就是一个函数上下文。
window.ECStack = [];//模拟执行环境,实际的执行环境包含window全局上下文
备注:与执行上下文相关的做用域和参数对象在new小节讲解。
执行环境ECStack内存时序结构图如图所示。
ECMAScript5规范丰富了用户自定义Object对象,提供了Object对象的属性特性。
参考W3C教程:http://www.w3school.com.cn/js/pro_js_referencetypes.asp
备注: JSON对象拓展,使用JSON. Stringify(/*Object*/)和JSON.parse(/*String*/)进行对象序列化,有时依赖引用对象的toJSON()方法。
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找属性就是找原型链最近的,找不着就找他爹。
this是当前执行环境上下文的一个属性。JavaScript是单线程的,意味着JS内核执行JS代码切换上下文,this就会改变。若JS执行到代码片断A,说明包含片断A的上一级内容B正在被加载/实例化,那么B就是this。
ECStack = { VO : {…}, this : thisValue};
初始状态
当浏览器加载请求页面时,卸载原始事件,解析并渲染HTML,驱动当前事件并绑定当前窗口对象。每一个窗口都有本身的全局对象,当页面被加载后,window对象实例化并绑定this。
1 function main(){ 2 window = new Window(); 3 this.call(window);//扩展this环境对象,其实只有函数对象才有call方法 4 |
无状态
这一块并无过多的代码演示。我的有两点理解。
1 function Foo () {} 2 Foo.prototype.foo = "bar"; 3 Foo.prototype.logFoo = function () { 4 eval("console.log(this.foo)"); //logs "bar" 5 } 6 var foo = new Foo (); 7 foo.logFoo();//foo调用logFoo方法,只有foo原型有logFoo方法,因此Foo.prototype为this
备注:判断a是不是A的自由属性,使用hasOwnProperty方法。
备注:可使用with,apply,call,bind方法链接this环境上下文,实现属性继承,这里不做过多说明。
new关键词实例化一个对象。对象实例化过程,对this的不断赋值,构造参数对象arguments,并维护实例对象的constructor属性。
举个栗砸。使用构造函数自定义对象:
分解步骤以下:
1 A.prototype.constructor= A(){…}; 2 A() = new Function(); 3 Function() = new Object();
1 Var a = new A(); 2 A.call(a, _args);//_args.id =1;
1 arguments = A.prototype.slice.call(_args);;//传入参数 2 arguments.callee == this;//参数对象调用者为当前函数体
备注:arguments.length表示实际传参数,arguments.callee.length表示指望传参数
1 a.id = arguments[1];//值为1 2 a.name = arguments[2];//值为undefined 3 a.constructor = A.prototype.constructor = A(){…};//继承
1 return a;
新建对象方法 |
描述 |
备注 |
使用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(){}}; |
构造函数模式构造属性 原型模式构造方法 |
当构造函数无特殊声明return返回值时,返回实例化对象。
函数闭包是一种技术,尤为被Lambdas语言普遍应用。Java中可使用匿名函数的方式模拟闭包。
闭包 = 闭包函数 + 一组自由变量与名称绑定存储区映射,实现函数外部访问函数内部变量的技术。
原理:若是一个函数包含内部函数,那么它们均可以看到其中声明的变量;这些变量被称为‘自由’变量。然而,这些变量能够被内部函数捕获,从高阶函数中return实现‘越狱’,以供之后使用。惟一须要注意的是,捕获函数必须在外部函数内定义。函数内没有任何局部声明以前(既不是被传入,也不是局部声明)使用的变量就是被捕获的变量。
备注:Javascript中20%以上的博客是关于闭包概念的,这里再也不赘述。
弊端:应用时需分析函数变量,检查闭包是否会互相产生干扰,检查闭包的实例是否相同。
利端:闭包函数实例化时只执行一次,将不须要暴露在外层环境的变量封装在内部,减小了外部变量。
在ECMAScript中,只支持实现继承而不支持签名继承(接口继承)实现继承基本是经过原型链继承。
继承方式 |
示例 |
原型链继承 |
Sub.prototype = new Super(); |
构造函数继承 |
function Sub(){ Super.call(this);} |
组合继承 |
|
原型式继承 |
Object.create(prototype) |
以上咱们接触了全局变量和this的概念,前端JS开发中,不规范的命名规则会致使JS全局变量混乱甚至冲突。
下面这段代码示例规范变量命名空间
1 var GLOBAL = {}; 2 GLOBAL.namespace = function(str){ 3 var arr = str.split('.'); 4 var start = 0; 5 if(arr[0] == 'GLOBAL'){ start = 1; } 6 for(var i = start; i < arr.length; i++){ 7 GLOBAL[arr[i]] = GLOBAL[arr[i]] || {}; 8 GLOBAL = o[arr[i]]; 9 } 10 };
假设a变量完成A功能中A1子功能。b变量完成A功能中A2子功能。c变量完成B功能。那么
1 GLOBAL.namespace('A.A1'); A.A1.a = …; 2 GLOBAL.namespace('A.A2'); A.A2.b = …; 3 GLOBAL.namespace('B'); B.c = …;
同时,尽可能在匿名函数中声明变量而非全局环境中,并为代码添加更多注释。
火狐开发者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/