如何编写可维护的JavaScript代码?

作者&投稿:历娟 (若有异议请与网页底部的电邮联系)
如何编写可维护的JavaScript代码?~

5�局实亩�髯苁遣槐涞模���奖局示褪且恍┖诵牡幕�「拍睢U饫锏幕�〔皇侵窲avaScript的表达式、数据类型、函数API等基础知识,而是指支撑上面这么一大堆JavaScript名词背后东西的基础。我知道这样会让我这篇文章很难写下去,因为那将包含太多主题,所以本文只打算管中窥豹:本文将先讲一些概念,然后讲一些实践指导原则,最后涉及一些工具的讨论。 在正式开始这篇博客之前,我们需要问自己为什么代码可维护性值得我们关注。相信只要你写过相当量的代码后,都已经发现了这点:Fix Bug比写代码困难得多。花三个小时写的代码,而之后为了Fix其中的一个Bug花两三天时间,这种情况并不少见。再加上Fix Bug的人很可能不是代码原作者,这无疑更雪上加霜。所以代码可维护性是一个非常值得探讨的话题,提高代码可维护性就一定程度上能节省Fix Bug的时间,节省Fix Bug的时间进而就节省了人力成本。No 1. 将代码组织成模块 基本任何一门编程语言都认为模块化能提升代码可维护性。我们知道软件工程的核心在于控制复杂度,而模块化本质上是分离关注点,从而分解复杂度。IIFE模块模式 当我们最开始学习编写JavaSript代码时,基本都会写下面这样的代码:1varmyVar = 10;2varmyFunc = function() {3 // ...4}; 这样的代码本身没有什么问题,但是当这样的代码越来越多时,会给代码维护带来沉重的负担。原因是这样导致myVar和myFunc暴露给全局命名空间,从而污染了全局命名空间。以我个人经验来看,一般当某个页面中的JavaScript代码达到200行左右时就开始要考虑这个问题了,尤其是在企业项目中。那么我们该怎么办呢? 最简单的解决方法是采用IIFE(Immediate Invoked Function Expression,立即执行函数表达式)来解决(注意这里是函数表达式,而不是函数声明,函数声明类似 var myFunc = function() { // ... }),如下:1(function() {2 varmyVar = 10;3 varmyFunc = function() {4 // ...5 };6}) (); 现在myVar和myFunc的作用域范围就被锁定在这个函数表达式内部,而不会污染全局命名空间了。这有点类似”沙盒机制“(也是提供了一个安全的执行上下文)。我们知道JavaScript中没有块级作用域,能产生作用域只能借助函数,正如上面这个例子一样。 但是现在myVar、myFunc只能在函数表达式内部被使用,如果它需要向外提供一些借口或功能(像大部分JavaScript框架或JavaScript库一样),那么该怎么办呢?我们会采用下面的做法:1(function(window, $, undefined) {2 varmyFunc = function() {3 // ...4 }5 6 window.myFunc = myFuc;7}) (window, jQuery); 我们来简单分析下,代码很简单:首先将window对象和jQuery对象作为立即执行函数表达式的参数,$只是传入的jQuery对象的别名;其次我们并未传递第三个参数,但是函数却有一个名为undefined的参数,这是一个小技巧,正因为没有传第三个参数,所以这里第三个参数undefined的值始终是undefined,就保证内部能放心使用undefined,而不用担心其他地方修改undefined的值;最后通过window.myFunc导出要暴露给外部的函数。 比如我们看一个实际JavaScript类库的例子,比如 Validate.js,我们可以看到它是这样导出函数的:1(function(window, document, undefined) {2 varFormValidator = function(formName, fields, callback) {3 // ...4 };5 6 window.FormValidator = FormValidator;7}) (window, document); 是不是与前面说的基本一样?另一个例子是jQuery插件的编写范式中的一种,如下:1(function($) { 2 $.fn.pluginName = function() { 3 // plugin implementation code 4 }; 5})(jQuery); 既然jQuery插件都来了,那再来一个jQuery源码的例子也无妨:1(function( window, undefined ) {2 varjQuery = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced'3 returnnewjQuery.fn.init( selector, context, rootjQuery );4 },5 6 // Expose jQuery to the global object7 window.jQuery = window.$ = jQuery;8})( window ); 上面这样写使得我们调用jQuery函数既可以用$("body"),又可以用jQuery("body")。命名空间(Namespace) 虽然使用IIEF模块模式让我们的代码组织成一个个模块,维护性提升了,但如果代码规模进一步增大,比如达到2000-10000级别,这时前面方法的局限性又体现出来了? 怎么说呢?观察下前面的代码,所有函数都是通过作为window对象属性的方式导出的,这样如果有很多个开发人员同时在开发,那么就显得不太优雅了。尤其是有的模块与模块之间可能存在层级关系,这时候我们需要借助“命名空间”了,命名空间可以用来对函数进行分组。 我们可以这样写:1(function(myApp, $, undefined) {2 // ...3}) (window.myApp = window.myApp || {}, jQuery); 或者这样:1varmyApp = (function(myApp, $, undefined) {2 ...3 returnmyApp;4}) (window.myApp || {}, jQuery); 现在我们不再往立即执行函数表达式传递window对象,而是传递挂载在window对象上的命名空间对象。第二段代码中的 || 是为了避免在多个地方使用myApp变量时重复创建对象。Revealing Module Pattern 这种模块模式的主要作用是区分出私有变量/函数和公共变量/函数,达到将私有变量/函数隐藏在函数内部,而将公有变量/函数暴露给外部的目的。 代码示例如下:01varmyModule = (function(window, $, undefined) {02 var_myPrivateVar1 = "";03 var_myPrivateVar2 = "";04 var_myPrivateFunc = function() {05 return_myPrivateVar1 + _myPrivateVar2;06 };07 08 return{09 getMyVar1: function() { return_myPrivateVar1; },10 setMyVar1: function(val) { _myPrivateVar1 = val; },11 someFunc: _myPrivateFunc 12 };13}) (window, jQuery); myPrivateVar1、myPrivateVar2是私有变量,myPrivateFunc是私有函数。而getMyVar1(public getter)、getMyVar1(public setter)、someFunc是公共函数。是不是有点类似普通的Java Bean? 或者我们可以写成这种形式(换汤不换药):01varmyModule = (function(window, $, undefined) {02 varmy= {};03 04 var_myPrivateVar1 = "";05 var_myPrivateVar2 = "";06 var_myPrivateFunc = function() {07 return_myPrivateVar1 + _myPrivateVar2;08 };09 10 my.getMyVar1 = function() {11 return_myPrivateVar1;12 };13 14 my.setMyVar1 = function(val) {15 _myPrivateVar1 = val;16 };17 18 my.someFunc = _myPrivateFunc;19 20 returnmy;21}) (window, jQuery);模块扩展(Module Augmentation) 有时候我们想为某个已有模块添加额外功能,可以像下面这样:1varMODULE = (function(my) {2 my.anotherMethod = function() {3 // added method...4 };5 6 returnmy;7}(MODULE || {}));Tight Augmentation 上面的例子传入的MODULE可能是undefined,也就是说它之前可以不存在。与之对应Tight Augmentation模式要求传入的MODULE一定存在并且已经被加载进来。1varMODULE = (function(my) {2 varold_moduleMethod = my.moduleMethod;3 4 my.moduleMethod = function() {5 // method override, has access to old through old_moduleMethod...6 };7 8 returnmy;9}(MODULE)); 代码意图很明显:实现了重写原模块的moduleMethod函数,并且可以在重写的函数中调用od_moduleMethod。但这种写法不够灵活,因为它假定了一个先决条件:MODULE模块一定存在并且被加载进来了,且它包含moduleMethod函数。子模块模式 这个模式非常简单,比如我们为现有模块MODULE创建一个子模块如下:1MODULE.sub = (function() {2 varmy = {};3 // ...4 5 returnmy;6}());No 2. 利用OO构造函数模式(Constructor Pattern) JavaScript没有类的概念,所以我们不可以通过类来创建对象,但是可以通过函数来创建对象。比如下面这样:01varPerson = function(firstName, lastName, age) {02 this.firstName = firstName;03 this.lastName = lastName;04 this.age = age;05};06 07Person.prototype.country = "China";08Person.prototype.greet = function() {09 alert("Hello, I am "+ this.firstName + " "+ this.lastName);10}; 这里firstName、lastName、age可以类比为Java类中的实例变量,每个对象有专属于自己的一份。而country可以类比为Java类中的静态变量,greet函数类比为Java类中的静态方法,所有对象共享一份。我们通过下面的代码验证下(在Chrome的控制台输):01varPerson = function(firstName, lastName, age) {02 this.firstName = firstName;03 this.lastName = lastName;04 this.age = age;05};06 07Person.prototype.country = "China";08Person.prototype.greet = function() {09 alert("Hello, I am "+ this.firstName + " "+ this.lastName);10};11 12varp1 = newPerson("Hub", "John", 30);13varp2 = newPerson("Mock", "William", 23);14console.log(p1.fistName == p2.firstName); // false15console.log(p1.country == p2.country); // true16console.log(p1.greet == p2.greet); // true 但是如果你继续测下面的代码,你得不到你可能预期的p2.country也变为UK:1p1.country = "UK";2console.log(p2.country); // China 这与作用域链有关,后面我会详细阐述。继续回到这里。既然类得以通过函数模拟,那么我们如何模拟类的继承呢? 比如我们现在需要一个司机类,让它继承Person,我们可以这样:01varDriver = function(firstName, lastName, age) {02 this.firstName = firstName;03 this.lastName = lastName;04 this.age = age;05};06 07Driver.prototype = newPerson(); // 108Driver.constructor = Driver;09Driver.prototype.drive = function() {10 alert("I'm driving. ");11};12 13varmyDriver = newDriver("Butter", "John", 28);14myDriver.greet();15myDriver.drive(); 代码行1是实现继承的关键,这之后Driver又定义了它扩展的只属于它自己的函数drive,这样它既可以调用从Person继承的greet函数,又可以调用自己的drive函数了。PS:本节未完,周末补全(使用对象字面量管理配置选项)No3. 遵循一些实践指导原则 下面是一些指导编写高可维护性JavaScript代码的实践原则的不完整总结。尽量避免全局变量 JavaScript使用函数来管理作用域。每个全局变量都会成为Global对象的属性。你也许不熟悉Global对象,那我们先来说说Global对象。ECMAScript中的Global对象在某种意义上是作为一个终极的“兜底儿”对象来定义的:即所有不属于任何其他对象的属性和方法最终都是它的属性和方法。所有在全局作用域中定义的变量和函数都是Global对象的属性。像escape()、encodeURIComponent()、undefined都是Global对象的方法或属性。 事实上有一个我们更熟悉的对象指向Global对象,那就是window对象。下面的代码演示了定义全局对象和访问全局对象:1myglobal = "hello"; // antipattern2console.log(myglobal); // "hello"3console.log(window.myglobal); // "hello"4console.log(window["myglobal"]); // "hello"5console.log(this.myglobal); // "hello" 使用全局变量的缺点是:全局变量被应用中所有代码共享,所以很容易导致不同页面出现命名冲突(尤其是包含第三方代码时)全局变量可能与宿主环境的变量冲突1functionsum(x, y) {2 // antipattern: implied global3 result = x + y;4 returnresult;5} result现在就是一个全局变量。要改正也很简单,如下:1functionsum(x, y) {2 varresult = x + y;3 returnresult;4} 另外通过var声明创建的全局变量与未通过var声明隐式创建的全局变量有下面的不同之处:通过var声明创建的全局变量无法被delete而隐式创建的全局变量可以被delete delete操作符运算后返回true或false,标识是否删除成功,如下:01// define three globals02varglobal_var = 1;03global_novar = 2; // antipattern04(function() {05 global_fromfunc = 3; // antipattern06}());07 08// attempt to delete09deleteglobal_var; // false10deleteglobal_novar; // true11deleteglobal_fromfunc; // true12 13// test the deletion14typeofglobal_var; // "number"15typeofglobal_novar; // "undefined"16typeofglobal_fromfunc; // "undefined" 推荐使用Single Var Pattern来避免全局变量如下:1functionfunc() {2 vara = 1,3 b = 2,4 sum = a + b,5 myobject = {},6 i,7 j;8 // function body...9} 上面只用了一个var关键词就让a、b、sum等变量全部成为局部变量了。并且为每个变量都设定了初始值,这可以避免将来可能出现的逻辑错误,并提高可读性(设定初始值意味着能很快看出变量保存的到底是一个数值还是字符串或者是一个对象)。 局部变量相对于全局变量的另一个优势在于性能,在函数内部从函数本地作用域查找一个变量毫无疑问比去查找一个全局变量快。避免变量提升(hoisting)陷阱 你很可能已经看到过很多次下面这段代码,这段代码经常用来考察变量提升的概念:1myName = "global";2functionfunc() {3 console.log(myName); // undefined4 varmyName = "local";5 console.log(myName); // local6}7 8func(); 这段代码输出什么呢?JavaScript的变量提升会让这段代码的效果等价于下面的代码:1myName = "global";2functionfunc() {3 varmyName;4 console.log(myName); // undefined5 myName = "local";6 console.log(myName); // local7}8 9func(); 所以输出为undefined、local就不难理解了。变量提升不是ECMAScript标准,但是却被普遍采用对非数组对象用for-in,而对数组对象用普通for循环 虽然技术上for-in可以对任何对象的属性进行遍历,但是不推荐对数组对象用for-in,理由如下:如果数组对象包含扩展函数,可能导致逻辑错误for-in不能保证输出顺序for-in遍历数组对象性能较差,因为会沿着原型链一直向上查找所指向的原型对象上的属性 jQuery.each()的实现也体现了这条原则:01if( isArray ) {02 for( ; i < length; i++ ) {03 value = callback.call( obj[ i ], i, obj[ i ] );04 if( value === false) {05 break;06 }07 }08} else{09 for( i inobj ) {10 value = callback.call( obj[ i ], i, obj[ i ] );11 if( value === false) {12 break;13 }14 }15} 所以推荐对数组对象用普通的for循环,而对非数组对象用for-in。但是对非数组对象使用for-in时常常需要利用hasOwnProperty()来滤除从原型链继承的属性(而一般不是你想要列出来的),比如下面这个例子:01// the object02varman = {03 hands: 2,04 legs: 2,05 heads: 106};07 08// somewhere else in the code09// a method was added to all objects10if(typeofObject.prototype.clone === "undefined") {11 Object.prototype.clone = function() {};12}13 14for(vari inman) { 15 console.log(i, ": ", man[i]); 16} 输出如下:1hands : 22legs : 23heads : 14clone : function() {} 即多了clone,这个可能是另外一个开发者在Object的原型对象上定义的函数,却影响到了我们现在的代码,所以规范的做法有两点。第一坚决不允许在原生对象的原型对象上扩展函数或者属性 (如果你和你同事都在Array.prototype上定义了一个map函数那将导致混乱)。第二将代码改写为类似下面这种:1for(vari inman) {2 if(man.hasOwnProperty(i)) {3 console.log(i, ": ", man[i]);4 }5} 进一步我们可以改写代码如下:1for(vari inman) {2 if(Object.prototype.hasOwnProperty.call(man, i)) { // filter3 console.log(i, ":", man[i]);4 }5} 这样有啥好处呢?第一点防止man对象重写了hasOwnProperty函数的情况;第二点性能上提升了,主要是原型链查找更快了。 进一步缓存Object.prototype.hasOwnProperty函数,代码变成下面这样:1vari, hasOwn = Object.prototype.hasOwnProperty;2for(i inman) {3 if(hasOwn.call(man, i)) { // filter4 console.log(i, ":", man[i]);5 }6}避免隐式类型转换 隐式类型转换可能导致一些微妙的逻辑错误。我们知道下面的代码返回的是true:10 == false20 == "" 建议做法是始终使用恒等于和恒不等于,即===和!===。

function selectAll(){//全选
var obj = document.listform.elements;//listform是form的name="listform"
for (var i=0;i<obj.length;i++){
if (obj[i].name == "delid"){//delid是checkbook的name="delid",你可以随便起名字
obj[i].checked = true;
}
}
}

function unselectAll(){
var obj = document.listform.elements;//全不选
for (var i=0;i<obj.length;i++){
if (obj[i].name == "delid"){
if (obj[i].checked==true) obj[i].checked = false;
else obj[i].checked = true;
}
}
}

5�局实亩�髯苁遣槐涞模���奖局示褪且恍┖诵牡幕�「拍睢U饫锏幕�〔皇侵窲avaScript的表达式、数据类型、函数API等基础知识,而是指支撑上面这么一大堆JavaScript名词背后东西的基础。我知道这样会让我这篇文章很难写下去,因为那将包含太多主题,所以本文只打算管中窥豹:本文将先讲一些概念,然后讲一些实践指导原则,最后涉及一些工具的讨论。 在正式开始这篇博客之前,我们需要问自己为什么代码可维护性值得我们关注。相信只要你写过相当量的代码后,都已经发现了这点:Fix Bug比写代码困难得多。花三个小时写的代码,而之后为了Fix其中的一个Bug花两三天时间,这种情况并不少见。再加上Fix Bug的人很可能不是代码原作者,这无疑更雪上加霜。所以代码可维护性是一个非常值得探讨的话题,提高代码可维护性就一定程度上能节省Fix Bug的时间,节省Fix Bug的时间进而就节省了人力成本。No 1. 将代码组织成模块 基本任何一门编程语言都认为模块化能提升代码可维护性。我们知道软件工程的核心在于控制复杂度,而模块化本质上是分离关注点,从而分解复杂度。IIFE模块模式 当我们最开始学习编写JavaSript代码时,基本都会写下面这样的代码:1varmyVar = 10;2varmyFunc = function() {3 // ...4}; 这样的代码本身没有什么问题,但是当这样的代码越来越多时,会给代码维护带来沉重的负担。原因是这样导致myVar和myFunc暴露给全局命名空间,从而污染了全局命名空间。以我个人经验来看,一般当某个页面中的JavaScript代码达到200行左右时就开始要考虑这个问题了,尤其是在企业项目中。那么我们该怎么办呢? 最简单的解决方法是采用IIFE(Immediate Invoked Function Expression,立即执行函数表达式)来解决(注意这里是函数表达式,而不是函数声明,函数声明类似 var myFunc = function() { // ... }),如下:1(function() {2 varmyVar = 10;3 varmyFunc = function() {4 // ...5 };6}) (); 现在myVar和myFunc的作用域范围就被锁定在这个函数表达式内部,而不会污染全局命名空间了。这有点类似”沙盒机制“(也是提供了一个安全的执行上下文)。我们知道JavaScript中没有块级作用域,能产生作用域只能借助函数,正如上面这个例子一样。 但是现在myVar、myFunc只能在函数表达式内部被使用,如果它需要向外提供一些借口或功能(像大部分JavaScript框架或JavaScript库一样),那么该怎么办呢?我们会采用下面的做法:1(function(window, $, undefined) {2 varmyFunc = function() {3 // ...4 }5 6 window.myFunc = myFuc;7}) (window, jQuery); 我们来简单分析下,代码很简单:首先将window对象和jQuery对象作为立即执行函数表达式的参数,$只是传入的jQuery对象的别名;其次我们并未传递第三个参数,但是函数却有一个名为undefined的参数,这是一个小技巧,正因为没有传第三个参数,所以这里第三个参数undefined的值始终是undefined,就保证内部能放心使用undefined,而不用担心其他地方修改undefined的值;最后通过window.myFunc导出要暴露给外部的函数。 比如我们看一个实际JavaScript类库的例子,比如 Validate.js,我们可以看到它是这样导出函数的:1(function(window, document, undefined) {2 varFormValidator = function(formName, fields, callback) {3 // ...4 };5 6 window.FormValidator = FormValidator;7}) (window, document); 是不是与前面说的基本一样?另一个例子是jQuery插件的编写范式中的一种,如下:1(function($) { 2 $.fn.pluginName = function() { 3 // plugin implementation code 4 }; 5})(jQuery); 既然jQuery插件都来了,那再来一个jQuery源码的例子也无妨:1(function( window, undefined ) {2 varjQuery = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced'3 returnnewjQuery.fn.init( selector, context, rootjQuery );4 },5 6 // Expose jQuery to the global object7 window.jQuery = window.$ = jQuery;8})( window ); 上面这样写使得我们调用jQuery函数既可以用$("body"),又可以用jQuery("body")。命名空间(Namespace) 虽然使用IIEF模块模式让我们的代码组织成一个个模块,维护性提升了,但如果代码规模进一步增大,比如达到2000-10000级别,这时前面方法的局限性又体现出来了? 怎么说呢?观察下前面的代码,所有函数都是通过作为window对象属性的方式导出的,这样如果有很多个开发人员同时在开发,那么就显得不太优雅了。尤其是有的模块与模块之间可能存在层级关系,这时候我们需要借助“命名空间”了,命名空间可以用来对函数进行分组。 我们可以这样写:1(function(myApp, $, undefined) {2 // ...3}) (window.myApp = window.myApp || {}, jQuery); 或者这样:1varmyApp = (function(myApp, $, undefined) {2 ...3 returnmyApp;4}) (window.myApp || {}, jQuery); 现在我们不再往立即执行函数表达式传递window对象,而是传递挂载在window对象上的命名空间对象。第二段代码中的 || 是为了避免在多个地方使用myApp变量时重复创建对象。Revealing Module Pattern 这种模块模式的主要作用是区分出私有变量/函数和公共变量/函数,达到将私有变量/函数隐藏在函数内部,而将公有变量/函数暴露给外部的目的。 代码示例如下:01varmyModule = (function(window, $, undefined) {02 var_myPrivateVar1 = "";03 var_myPrivateVar2 = "";04 var_myPrivateFunc = function() {05 return_myPrivateVar1 + _myPrivateVar2;06 };07 08 return{09 getMyVar1: function() { return_myPrivateVar1; },10 setMyVar1: function(val) { _myPrivateVar1 = val; },11 someFunc: _myPrivateFunc 12 };13}) (window, jQuery); myPrivateVar1、myPrivateVar2是私有变量,myPrivateFunc是私有函数。而getMyVar1(public getter)、getMyVar1(public setter)、someFunc是公共函数。是不是有点类似普通的Java Bean? 或者我们可以写成这种形式(换汤不换药):01varmyModule = (function(window, $, undefined) {02 varmy= {};03 04 var_myPrivateVar1 = "";05 var_myPrivateVar2 = "";06 var_myPrivateFunc = function() {07 return_myPrivateVar1 + _myPrivateVar2;08 };09 10 my.getMyVar1 = function() {11 return_myPrivateVar1;12 };13 14 my.setMyVar1 = function(val) {15 _myPrivateVar1 = val;16 };17 18 my.someFunc = _myPrivateFunc;19 20 returnmy;21}) (window, jQuery);模块扩展(Module Augmentation) 有时候我们想为某个已有模块添加额外功能,可以像下面这样:1varMODULE = (function(my) {2 my.anotherMethod = function() {3 // added method...4 };5 6 returnmy;7}(MODULE || {}));Tight Augmentation 上面的例子传入的MODULE可能是undefined,也就是说它之前可以不存在。与之对应Tight Augmentation模式要求传入的MODULE一定存在并且已经被加载进来。1varMODULE = (function(my) {2 varold_moduleMethod = my.moduleMethod;3 4 my.moduleMethod = function() {5 // method override, has access to old through old_moduleMethod...6 };7 8 returnmy;9}(MODULE)); 代码意图很明显:实现了重写原模块的moduleMethod函数,并且可以在重写的函数中调用od_moduleMethod。但这种写法不够灵活,因为它假定了一个先决条件:MODULE模块一定存在并且被加载进来了,且它包含moduleMethod函数。子模块模式 这个模式非常简单,比如我们为现有模块MODULE创建一个子模块如下:1MODULE.sub = (function() {2 varmy = {};3 // ...4 5 returnmy;6}());No 2. 利用OO构造函数模式(Constructor Pattern) JavaScript没有类的概念,所以我们不可以通过类来创建对象,但是可以通过函数来创建对象。比如下面这样:01varPerson = function(firstName, lastName, age) {02 this.firstName = firstName;03 this.lastName = lastName;04 this.age = age;05};06 07Person.prototype.country = "China";08Person.prototype.greet = function() {09 alert("Hello, I am "+ this.firstName + " "+ this.lastName);10}; 这里firstName、lastName、age可以类比为Java类中的实例变量,每个对象有专属于自己的一份。而country可以类比为Java类中的静态变量,greet函数类比为Java类中的静态方法,所有对象共享一份。我们通过下面的代码验证下(在Chrome的控制台输):01varPerson = function(firstName, lastName, age) {02 this.firstName = firstName;03 this.lastName = lastName;04 this.age = age;05};06 07Person.prototype.country = "China";08Person.prototype.greet = function() {09 alert("Hello, I am "+ this.firstName + " "+ this.lastName);10};11 12varp1 = newPerson("Hub", "John", 30);13varp2 = newPerson("Mock", "William", 23);14console.log(p1.fistName == p2.firstName); // false15console.log(p1.country == p2.country); // true16console.log(p1.greet == p2.greet); // true 但是如果你继续测下面的代码,你得不到你可能预期的p2.country也变为UK:1p1.country = "UK";2console.log(p2.country); // China 这与作用域链有关,后面我会详细阐述。继续回到这里。既然类得以通过函数模拟,那么我们如何模拟类的继承呢? 比如我们现在需要一个司机类,让它继承Person,我们可以这样:01varDriver = function(firstName, lastName, age) {02 this.firstName = firstName;03 this.lastName = lastName;04 this.age = age;05};06 07Driver.prototype = newPerson(); // 108Driver.constructor = Driver;09Driver.prototype.drive = function() {10 alert("I'm driving. ");11};12 13varmyDriver = newDriver("Butter", "John", 28);14myDriver.greet();15myDriver.drive(); 代码行1是实现继承的关键,这之后Driver又定义了它扩展的只属于它自己的函数drive,这样它既可以调用从Person继承的greet函数,又可以调用自己的drive函数了。PS:本节未完,周末补全(使用对象字面量管理配置选项)No3. 遵循一些实践指导原则 下面是一些指导编写高可维护性JavaScript代码的实践原则的不完整总结。尽量避免全局变量 JavaScript使用函数来管理作用域。每个全局变量都会成为Global对象的属性。你也许不熟悉Global对象,那我们先来说说Global对象。ECMAScript中的Global对象在某种意义上是作为一个终极的“兜底儿”对象来定义的:即所有不属于任何其他对象的属性和方法最终都是它的属性和方法。所有在全局作用域中定义的变量和函数都是Global对象的属性。像escape()、encodeURIComponent()、undefined都是Global对象的方法或属性。 事实上有一个我们更熟悉的对象指向Global对象,那就是window对象。下面的代码演示了定义全局对象和访问全局对象:1myglobal = "hello"; // antipattern2console.log(myglobal); // "hello"3console.log(window.myglobal); // "hello"4console.log(window["myglobal"]); // "hello"5console.log(this.myglobal); // "hello" 使用全局变量的缺点是:全局变量被应用中所有代码共享,所以很容易导致不同页面出现命名冲突(尤其是包含第三方代码时)全局变量可能与宿主环境的变量冲突1functionsum(x, y) {2 // antipattern: implied global3 result = x + y;4 returnresult;5} result现在就是一个全局变量。要改正也很简单,如下:1functionsum(x, y) {2 varresult = x + y;3 returnresult;4} 另外通过var声明创建的全局变量与未通过var声明隐式创建的全局变量有下面的不同之处:通过var声明创建的全局变量无法被delete而隐式创建的全局变量可以被delete delete操作符运算后返回true或false,标识是否删除成功,如下:01// define three globals02varglobal_var = 1;03global_novar = 2; // antipattern04(function() {05 global_fromfunc = 3; // antipattern06}());07 08// attempt to delete09deleteglobal_var; // false10deleteglobal_novar; // true11deleteglobal_fromfunc; // true12 13// test the deletion14typeofglobal_var; // "number"15typeofglobal_novar; // "undefined"16typeofglobal_fromfunc; // "undefined" 推荐使用Single Var Pattern来避免全局变量如下:1functionfunc() {2 vara = 1,3 b = 2,4 sum = a + b,5 myobject = {},6 i,7 j;8 // function body...9} 上面只用了一个var关键词就让a、b、sum等变量全部成为局部变量了。并且为每个变量都设定了初始值,这可以避免将来可能出现的逻辑错误,并提高可读性(设定初始值意味着能很快看出变量保存的到底是一个数值还是字符串或者是一个对象)。 局部变量相对于全局变量的另一个优势在于性能,在函数内部从函数本地作用域查找一个变量毫无疑问比去查找一个全局变量快。避免变量提升(hoisting)陷阱 你很可能已经看到过很多次下面这段代码,这段代码经常用来考察变量提升的概念:1myName = "global";2functionfunc() {3 console.log(myName); // undefined4 varmyName = "local";5 console.log(myName); // local6}7 8func(); 这段代码输出什么呢?JavaScript的变量提升会让这段代码的效果等价于下面的代码:1myName = "global";2functionfunc() {3 varmyName;4 console.log(myName); // undefined5 myName = "local";6 console.log(myName); // local7}8 9func(); 所以输出为undefined、local就不难理解了。变量提升不是ECMAScript标准,但是却被普遍采用对非数组对象用for-in,而对数组对象用普通for循环 虽然技术上for-in可以对任何对象的属性进行遍历,但是不推荐对数组对象用for-in,理由如下:如果数组对象包含扩展函数,可能导致逻辑错误for-in不能保证输出顺序for-in遍历数组对象性能较差,因为会沿着原型链一直向上查找所指向的原型对象上的属性 jQuery.each()的实现也体现了这条原则:01if( isArray ) {02 for( ; i < length; i++ ) {03 value = callback.call( obj[ i ], i, obj[ i ] );04 if( value === false) {05 break;06 }07 }08} else{09 for( i inobj ) {10 value = callback.call( obj[ i ], i, obj[ i ] );11 if( value === false) {12 break;13 }14 }15} 所以推荐对数组对象用普通的for循环,而对非数组对象用for-in。但是对非数组对象使用for-in时常常需要利用hasOwnProperty()来滤除从原型链继承的属性(而一般不是你想要列出来的),比如下面这个例子:01// the object02varman = {03 hands: 2,04 legs: 2,05 heads: 106};07 08// somewhere else in the code09// a method was added to all objects10if(typeofObject.prototype.clone === "undefined") {11 Object.prototype.clone = function() {};12}13 14for(vari inman) { 15 console.log(i, ": ", man[i]); 16} 输出如下:1hands : 22legs : 23heads : 14clone : function() {} 即多了clone,这个可能是另外一个开发者在Object的原型对象上定义的函数,却影响到了我们现在的代码,所以规范的做法有两点。第一坚决不允许在原生对象的原型对象上扩展函数或者属性 (如果你和你同事都在Array.prototype上定义了一个map函数那将导致混乱)。第二将代码改写为类似下面这种:1for(vari inman) {2 if(man.hasOwnProperty(i)) {3 console.log(i, ": ", man[i]);4 }5} 进一步我们可以改写代码如下:1for(vari inman) {2 if(Object.prototype.hasOwnProperty.call(man, i)) { // filter3 console.log(i, ":", man[i]);4 }5} 这样有啥好处呢?第一点防止man对象重写了hasOwnProperty函数的情况;第二点性能上提升了,主要是原型链查找更快了。 进一步缓存Object.prototype.hasOwnProperty函数,代码变成下面这样:1vari, hasOwn = Object.prototype.hasOwnProperty;2for(i inman) {3 if(hasOwn.call(man, i)) { // filter4 console.log(i, ":", man[i]);5 }6}避免隐式类型转换 隐式类型转换可能导致一些微妙的逻辑错误。我们知道下面的代码返回的是true:10 == false20 == "" 建议做法是始终使用恒等于和恒不等于,即===和!===。


JB和JA有什么区别?
汇编语言中JB、JA都是条件转移指令,常用于比较两个无符号数的大小,判断条件是CF、ZF状态的组合。为确定CF、ZF状态,转移前用CMP指令设置标志位(CMP A,B)。其中:指令JA表示 CF=0 且ZF=0 即A>B转移。指令JB表示 CF=1 且ZF=0 即A <B转移。许多汇编程序为程序开发、汇编控制、辅助调试提供...

汇编语言中JB,JA,是什么意思
汇编语言中JB、JA都是条件转移指令,常用于比较两个无符号数的大小,判断条件是CF、ZF状态的组合。为确定CF、ZF状态,转移前用CMP指令设置标志位(CMP A,B)。当计算机的硬件不认识字母符号,这时候就需要一个专门的程序把这些字符变成计算机能够识别的二进制数。因为汇编语言只是将机器语言做了简单编译...

ja是什么意思
Java语言的特点包括跨平台性、面向对象、安全性等。其中,跨平台性是Java最重要的特点之一,Java程序只需编写一次,就可以在任何支持Java的平台上运行。面向对象使得Java语言更容易理解和维护。安全性则体现在Java的垃圾回收机制以及防止内存泄漏等方面。此外,Java丰富的库和API也大大简化了开发过程。综上所...

java软件工程师做什么
1、完成前端或后端架构规划,管控,指导及核心开发;2、根据系统概要完成软件的设计、开发、测试、修改bug等工作;3、负责功能模块详细设计、业务功能实现、单元测试和系统维护;4、负责业务需求的沟通;5、处理上级授权与交办的其它工作等。拓展:什么是Ja Ja是一门面向对象地编程语言,吸收了C++语言的各种...

经理工作岗位职责
2、计算机或软件工程本科或以上学历,熟悉数据库语言(sql server 、orcle)具备存储过程、视图等编写,了解JA开发语言【此为硬性要求,不合适者请勿投简历】; 3、优化网络系统,规划调整设备配置,完成路由器\/交换机\/防火墙的配置施工及布线; 4、熟悉公司计算机软硬件的日常维护及管理,审核全公司电脑及周边设备的采购,小型...

软体开发工程师个人简历模板
1.精通JA程式设计,有JSP、JABEAN、JDBC、SERVLET\/JSP等开发经验;2.熟悉Tomecat、WebLogic等开发平台;3.熟练掌握Struts、Hibernate等框架的使用;4.熟练掌握Oracle,Sql Server,MySql等资料库;5.熟练应用面向物件的设计思想和设计模式,对MVC架构有很深入的了解;详细个人自传 诚实守信,认真细致,有责任心,...

常见的十五种Java开发工具是什么,有什么特点
除了基于JAVA是平台无关的外,脚本的格式是基于XML的,比make脚本来说还要好维护一些。Ant是Apache提供给Java开发人员的构建工具,它可以在Windows OS和Unix OS下运行,它不仅开放源码并且还是一个非常好用的工具。Ant是Apache Jakarta中一个很好用的Java开发工具,Ant配置文件采用XML文档编写,所以Java程序...

编程语言排行榜2023(编程语言排行榜2023年3月)
八、JaScript必不可少 今年JaScript的使用量有所下降,名次比去年有所下滑。但是现在所有软件开发人员都以某种方式使用JaScript。与HTML和CSS一起使用,JaScript对于前端Web开发来说必不可少,以便创建交互式网页,并向用户动态显示内容。 超过90%的网站使用这种语言,它也是初学者开始上手的友好的编程语言之一。所以,如果...

程序员是做什么的?
写代码。需要注意的是程序员有了一定的经验后你就转变为工程师了,这个时候你就不光是简单的完成任务了。另外程序员需要选择一门或者多门语言来编程,不同的语言适合编写不同的程序,目前主流编程语言包括,Ja、JaScript、Python、C++、php以及其他小语种等等,每种编程语言适合开发的程序有所不同。

想做程序员需要学什么(小程序开发一个多少钱啊)
程序员要需要能看懂需求文档,并且能准确辩咐地使用编程语言,根据需求中的要求来编写成程序。企业开发的项目,往往会由该程序的架构师提供一个程序框架,程序员在该框架的规范下进行编程,实现需求的功能,以确保程序的规范、可读,以及可维护性。 3.日常工作写程序 一个软件开发一般流程是产品经理根据用户需求做一个项目...

铜官山区15866312127: 怎样编写高质量的Java代码 -
滕桂山庄: 如何编写高质量代码,从而提升系统性能.想必是很多程序员都非常注意的地方,最近总结了一些要点,特此记录在案. 所谓代码高可读性和高可维护性,其实就是应该有着规范的Java程序设计风格,在开发程序过程中,从近期目标看是应该...

铜官山区15866312127: 请帮忙传一份pdf《编写可维护的javascript 中文 pdf》 -
滕桂山庄: http://pan.baidu.com/s/1i3Gr06D

铜官山区15866312127: 前端开发,如何写出优秀js代码 -
滕桂山庄: 每位前端工程师2113都喜欢易理解、可扩展、易维护的代码,如何写出优秀的JavaScript代码,也是每位前端工程师的功课.如何才能写出优秀的JavaScript代码呢?1.写代码前一定要搞清楚你要解决的问题是什么,你的方案5261是否能够解决...

铜官山区15866312127: 如何编写JAVESCRIPT程序语言
滕桂山庄: 象很多其它编程语言一样,javascript 也是用文本格式编写,由语句 (statements),语句块 (blocks) 和注释 (comments) 构成.语句块 (blocks) 是由一些相互有关联的语句构成的语句集合.在一句语句 (statement) 里,你可以使用变...

铜官山区15866312127: 如何从头开始开发一个JavaScript组件库 -
滕桂山庄: 1.代码注释,一般要注明组件的用途,传参的含义,以及一些关键代码的注释,一切为了可维护性还有以后接手你代码的小伙伴们;2.组件灵活性,参数的扩展性,类似于既支持单参数又支持对象的调用,以及一些内置功能的处理3.组件性,遵循 Keep it simply的原则,这里是要保证调用方的方便性,不至于要花半天来学习怎么用你的组件,类似于部分非关键HTML代码由js来控制生成的,多用css3以及canvas来模拟替换一些图片效果.4.接口的一致性,组件使用方式的统一化,还是为了使用得爽,不至于被乱七八糟的调用方式搞得心情不好.5.组件的模块化与一些底层封装

铜官山区15866312127: 如何使用javascript编写风险评估 -
滕桂山庄: 可维护的代码意味着:可读的一致的可预测的看上去就像是同一个人写的已记录

铜官山区15866312127: java 和javaScript的开发环境 -
滕桂山庄: java是sun公司开发的编程语言,必须安装jdk;javascript则是网页的脚本语言,跟java没有本质上的关系,也不需要什么开发环境,主要看浏览器支不支持.Dreamweaver里可以编写javaScript 另外,不管对什么语言,开发工具是不一定的.理论上说,用记事本可以开发岀任何语言的程序.

铜官山区15866312127: <script src="xxxx.js" type="text/javascript"></script> 打开此js文件后看到很多的function(xxxxxxxxx)
滕桂山庄: <script src="xxxx.js" type="text/javascript">&lt;&#47;script&gt;<br>这个是引入外部js文件,是为了页面代码的整洁和增强代码的可维护性<br>作用就是把这个js文件的内容加载到页面中来,所以无论是直接写在页面中的,还是从外部引用的...

铜官山区15866312127: JAVA,CSS,JAVASCRIPT面试题 -
滕桂山庄: 1,本人测试,var a=();var a=//; 报错两个不行! --------------------------------- 2,JAVA! int char while for do switch void double float unsigned long try abstract super extent bool break case catch class delegate foreach in static void public ...

铜官山区15866312127: Javascript 是什么 -
滕桂山庄: JavaScript 是属于网络的脚本语言!JavaScript 被数百万计的网页用来改进设计、验证表单、检测浏览器、创建cookies,以及更多的应用.JavaScript 是因特网上最流行的脚本语言.JavaScript 很容易使用!你一定会喜欢它的!什么是 ...

本站内容来自于网友发表,不代表本站立场,仅表示其个人看法,不对其真实性、正确性、有效性作任何的担保
相关事宜请发邮件给我们
© 星空见康网