编写高质量代码:Web前端开发修炼之道(四)

这一节是继上一节高质量的Javascript

7)编程实用技巧

1:弹性

从 一个标签区和内容区的实例(就是点击不同的标签菜单显示不同的内容块)来说明不需要每个tabmenu都设置onclick事件,为了让程序更有弹性,可 以将所有的点击时间封装成一个函数,变化的标签作为参数传入实现点击不同的标签显示对应的内容块,这样标签的数量可自适应,可增可减,而js代码可以不用 变动,只需要修改html的标签就可以。-------------这点我想做过项目的伙伴们都能深深的体会到。

琐碎知识点:

js 一个非常经典的问题:在遍历数组时对DOM监听事件(如上例中的菜单单击事件tabMenus[i].onclick),索引值始终等于遍历结束后的值。 解决这一问题可以用闭包,或者给每个DOM节点添加Index属性用来标示区别。---------我遍历一般用jquery的each函数,挺好用的, 没用过Dom,但是还是一个值得注意的地方。

2:可复用性

因为一个html页面的id只能出现一次,所以,如果你的程序需要被多次复用,就一定不能用id来标识,不能用id作为js获得DOM节点的挂钩,更好的应该是使用class.  在做项目过程中,我们可以通过class取得一组Dom标签,但由于id的唯一性,只适合取某个固定的标签。

组件需要指定根节点,以保持每个组件之间的独立性。--------具体的可能要看书上的代码示例来体会。(就是尽管有相同的class来标识同类结构(一个组件),但是不同的组件与组件之间还需要指定一个父节点来区别对待)。

3:通过参数实现定制

如果每个tab标签需要不同的样式,这个时候就只能根据每个tabMenu的唯一标示作为参数传到处理函数,在函数中根据不同的标识来选择不同的样式

如果一个函数内某个因素很不稳定,我们可以将它从函数内部分离出来,以参数的形式传入,从而将不稳定的因素和函数解耦。---与第一点弹性有相似处

4:this关键字的指向

在js全局域中  this指向window.

A:JavaScript伪协议和内联事件对于this的指向不同:

  伪协议:a标签的href="javascript:alert(this==window)"     结果:弹出true       伪协议中this指向window

  内联事件:onclick="alert(this.tagName)"                          结果:弹出a          内联事件的this指向当前的Dom元素 

B:setTimeout和setInterval也会改变this的指向,在这两个函数中  this指向window

C:DomNode.on***事件也会改变this的指向,this会指向当前调用时间的Dom结点。

使用匿名函数可将B,C中this的指向改变。

例:

编写高质量代码:Web前端开发修炼之道(四)
 1 <script type="text/javascript">
2 var name = "global-name";
3 var btn = document.getElementById("btn");
4 var test = {
5 name: "test-name",
6 say: function () {
7 alert(this.name);
8 }
9 }
10 test.say(); //输出test-name
11 setTimeout(function () { test.say() }, 1000); //输出test-name 匿名函数会改变指向,谁调用就指向谁
12 /*
13 setTimeout和setInterval两个函数的调用方式,第一个参数应该是函数指针,或字符串
14 */
15 setTimeout('test.say()', 1000);         //作为字符串,还是由test调用,输出test-name
16 setTimeout(test.say, 1000);         //作为函数指针,输出global-name setTimeout的直接调用指向方式,this指向window
17
18 setInterval(function () { test.say() }, 1000); //输出test-name
19 setInterval('test.say()', 1000); //输出test-name ,由test主调
20 setInterval(test.say, 1000); //输出global-name
21 setTimeout(function () { alert(this == window) }, 1000); //输出true, this为全局window对象
22
23 $(function () {
24 var btn2 = document.getElementById("btn");
25 btn2.onclick = function () { test.say() }; //输出test-name
26 btn2.onclick = function () { alert(this == btn2) }; //输出true, this为DOM元素对象
27 });
28 </script>
29 <input type="button" name="btn-name" id="btn" value="test-btn" />
编写高质量代码:Web前端开发修炼之道(四)

总结:1:如果setTimeout和setInterval调用的处理函数,该处理函数是“直接调用的函数”,那么this指向window

   2:DomNode.on***关联的处理函数,该处理函数是“直接调用的函数”,this指向Dom结点

   3:如果将上面的处理函数用匿名函数封装起来,那么,处理函数的调用方式:由直接调用变为间接调用;这样就不会受到外面调用函数的影响,this该指向谁就指向谁

自己的话:一般来说,this----该动作是谁调用的,那么this就指向调用者,只有碰到setTimeout、setInterval、btn.on***等会改变指向的函数,才不会遵守基本规则,但是也可以通过匿名函数的间接调用来解决,这样又变回了一般状态。


另外还可以通过call和apply函数来改变处理函数的this指向,test.say.call(btn); test.say.apply(btn);  this 直接指定的。

5:预留回调接口

我所理解的就是,在函数中预留出一个参数handler,该参数的实参是一个函数。 判断回调函数是否有用,if(handler){ handler(参数);//调用回调函数;}

添加回调的接口可以参加代码的可扩展性。

6:编程中的DRY规则

DRY----don't repeat yourself,强调在程序中不要将相同的代码重复编写多次,更好的做法是只写一次,然后多次引用。提高重用率,减少代码量

7:用hash传参

比较: 普通传参方式:参数量大,而且参数的顺序很重要,

    hash传参方式:hash是一个key-value的集合,可包含任意类型的数据,用hash对象传参,可以提高函数调用的灵活性,没有顺序的控制

如: test函数有6个参数,调用时有些为空,有些不为空,普通传参: test(null,null,null,null,null,"hello"); 用hash传参: test( {str:"hello"} ) 显然第二种简单直观.

8)面向对象编程

1:面向对象

将数据和处理函数定义到了一个对象的内部,作为这个对象的属性和行为存在,在对象的内部,属性和行为通过this关键字来访问,在对象的外部,用对象的属性和对象的行为来调用。   额~,抽象的概念,理解起来就是别扭。

它的思维过程是定义一个对象,对象有自己的属性和行为。属性和行为都从属于对象,于是有对象内,和对象外的概念。

整个程序可以由一堆对象组成,对象和对象之间可能会有通讯,为了能相互访问,于是就有了私有和公有的概念。

2:js的面向对象

A:类的概念 

 一般的类定义,都要class关键字,js中的类没有class关键字,它是用函数来充当类的函数在js中既可以用作普通函数,也可以当类来使用,当充当类时,又担负着构造函数的作用。

fuction test(){//code...}

  调用方式: 函数充当普通函数,直接使用()进行调用。 如:test()

        函数充当类时,使用new来实例化。如:var c=new test();

B:原型(prototype)

原型在js中是一个很重要的概念,因为JS是基于原型的语言,通过new实例化出来的对象,其属性和行为来自于两部分:1:构造函数(既函数本身),2:原型

原型的概念及由来:我们只需要知道“在声明一个类的时候,同时就生成了一个对应的类的原型。”------------我理解为同样的生命周期。

通过test.prototype就可以指向这个原型, 而原型也可以通过它的constructor属性指向test类,具体指的是test类的构造函数。

只有当函数作为类使用,new出来一个对象时,原型才具有它存在的价值(个人观点)

原型是个hash对象,也可以分开定义

如:test.prototype={                    分开定义:

  name:"***",                       test.prototype.name="***";

  type:"***",                         test.prototype.type="***";

  say:function(){.....}                   test.prototype.say=function(){...};

}

C :优先级

当构造函数和原型中都定义了同名的属性和行为,则构造函数中的属性和行为优先级要高,它会覆盖原型中的属性和行为。

this关键字无论是出现在构造函数还是原型中它指代的都是实例对象。能改变this指向的函数就另当别论。

D:公有和私有

js中没有public,protect,private等关键字,js中的公有还是私有是通过作用域来实现的。

用this.***定义的属性都是公有的(原因:在构造和原型中的this 都是同一个实例对象,在原型中可以访问得到),而用var ***定义的属性都是私有的(在原型中访问不到)。方法的公有私有也一样用this区别开来。

习惯: 定义类时,一般我们会把属性(变量)放在构造函数里------方便构造函数接收参数,而行为(方法)放在原型里。

原因:因为在内存中一个类的原型只有一个,写在原型中的行为可以被所有实例共享,实例化时不会在实例的内存中复制一份;而写在类中的行为,会每个实例都会复制一份。--------------为了减少内存消耗。

自己的话:实例化时,原型中的属性和行为是引用,构造函数中的属性和行为是复制。

3:继承

琐碎知识点:在js中,fuction作为普通函数存在时,直接使用()进行调用,函数内的this是指向window;

          function作为类存在时,通过new实例化,类里面的this指向实例对象。

A:构造函数中属性和行为的继承

为了实现构造函数中属性和行为的继承,可以通过call/apply方法来实现。

如:

编写高质量代码:Web前端开发修炼之道(四)

当实例化B的对象时就能访问到构造函数里的属性和方法。

编写高质量代码:Web前端开发修炼之道(四)

B:原型中属性和行为的继承

琐碎知识点:js中的传值和传址。

在js中,赋值语句会用传值和传址两种不同的方式进行复制,如果是数值型,布尔型,字符型等基本数据类型,将复制一份数据进行赋值。---传值

如果是"数值,hash对象等复杂类型"(数值,hash对象可包含简单类型数据),在进行赋值时会直接使用内存地址赋值。-----传址

原型中的属性和行为的继承,我们可以直接将A的原型赋值给B的原型,如B.prototype=A.prototype

编写高质量代码:Web前端开发修炼之道(四)

这样B的实例及B.prototype 是可以访问A类原型中的say方法,但是prototype本质上是一个hash对象,它的赋值是传址的方式,当我们在B的原型中在添加一个AddFunctionForB()方法时,由于传址方式,在A的原型中也会添加B的AddFunctionForB()方法.

如:

编写高质量代码:Web前端开发修炼之道(四)

为了解决这个问题,我们用另一种方法实现prototype的传值-new somefunction() ----new出基类的对象,然后重新定向B类的构造。

在上面的代码中我们定义了A类及A类的原型、B类;我们只需要将B类的原型中指向A类的实例对象(这时,B类会继承A类的构造及原型中的属性和方法),这样就可以访问A类原型中的方法。但由于这种赋值会使得B.prototype.constructor指向了A类的构造,所以我们要将它纠正,重新指向B类。

编写高质量代码:Web前端开发修炼之道(四)

这样就解决了上面的问题,既能从原型继承方法,又不会引起不必要的混乱。

9)prototype和内置类

从第8点知道了prototype和类的关系,现在看看prototype和js自带的内置类的关系。

Js的内置类包括Array,String,Function等 如Array提供length属性,push.pop方法,String提供length属性,replace.split方法 ,Fuction提供Call.apply方法.

一般内置类我们一般不用New实例化,习惯更简单的方式,如 var a="sssss";  而不是 var a=new String("sssss");

只要是类就会有原型,So 我们可以对内置类的原型进行修改,以重写或扩展它的功能。

如:常见的有 Array.prototype.each=function(){....}        Array.prototype.map=function(){.....}

里面可能会涉及到this指针的指向,我们只需要记住,在类的构造函数和原型中的this ,都是指向该类实例化的对象。

注意:内置类的方法可以重写,比如toString()方法,但是 他的属性不能重写,如length;

 

10)标签的自定义属性

在html语言中的标签一般有自己的属性,如 id,class href等,我们有时候会用到自定义的属性
为了从兼容性来考虑,用JS来读取属性时,笔者建议对于常规属性,统一使用node.***的方式读取,对于自定义属性,统一使用node.getAttribute("***")读取。

自定义属性一个非常有用的技巧:----将普通字符串转换为hash对象或数组

字符串的反序列化-----------通过eval函数来实现。

如在a标签里自定义一个属性<a id="a" userinfo="name:'alice' , age:22 , pwd:'123456' " ></a>

取到a的userinfo属性值 var info = document.getElementbyId("a").getAttribute("userinfo");

            alert(typeof info) //string 类型      访问info.name  info.age都访问不到

            info=eval("("+info+")"); 转化为对象类型; 访问info.name  输出alice ;  访问 info.age   输出22;

11) 标签的内联事件和event对象

在IE下,event是window对象的一个属性,是全局作用域下的; 在FF中,event对象是作为事件的参数存在。

在标签的内联事件中,FF下,使用arguments[0]可以访问到event对象。不是内联事件,可以给处理函数传参来访问  funtion(e){...}

关于event对象前面的章节中也有讲过,这里就不再描述了。

12)一些规则

基本在前面的内容中均已描述过,包括css命名规则、注释规则、html规范、css规范、js规范。

到这全书就结束了。将学习笔记记录下来希望对看到该文的朋友有一定的帮助。O(∩_∩)O~ 

转载请注明出处

原文地址:http://www.cnblogs.com/Joans/archive/2012/09/14/2685110.html

上一篇:编写高质量代码:Web前端开发修炼之道(一)


下一篇:编写高质量代码:Web前端开发修炼之道(二)