标签归档:JavaScript

Java工程师可能不知道的那些FE潜规则

写了一个多月JavaScript,感觉如今可不比几年前只有IE6的年代,而且过去只是用JS写个Ajax或者是简单的表单验证,可如今写一个稍微复杂点的小应用,要兼容所有浏览器,才发现真是太难了,难怪FE是一个独立的工种,有别于我们这些Java工程师了。

1 首先是最简单的select标签,就有诸多不兼容:

A、 cloneNode方法,对于非IE浏览器没有问题,对于IE浏览器,我遇到的问题包括:
1)option selected的会clone不过去,然后会将第一个option作为selected值
2)事件clone也会有问题

B、Readonly:对于IE6,可以通过以下方法将select设为readonly:
obj.onbeforeactive=function(){return false}
obj.onfocus=function(){obj.blur();
obj.onmouseover=function(){obj.setCapture();}
obj.onmouseout=function(){obj.releaseCapture();}
对于其他浏览器,我采用的是元素替代法,动态创建一个input标签,把值赋给它,然后将select隐藏。

C、select的z-index对于IE6无效,网上有很多关于这个讨论,JQuery采用一个iframe搞定

D、动态添加option的方法不同,这个网上一搜一大堆

E、对于onclick和onchange的响应不同,在FF下可以在onclick select时动态读取option值然后构建option,然后选中一个值后执行onchange事件,但是IE下不能这样做。

2 css对offsetWidth之类的理解不同 – http://newleague.iteye.com/blog/765535

3 对于vertical-align baseline的理解不同 – http://w3help.org/zh-cn/causes/RD1016

4 设置背景色
element.style.backgroundColor
在firefox下想改变颜色,必须先设为null,再设为其他颜色才行,即先取消原来的颜色。
在IE下,想取消颜色,必须设为”才行,而换其他颜色,无需先去掉之前的颜色。

5 不同浏览器去padding的理解不同

6 不同浏览器对强制换行和强制不换行的理解不同 – http://www.cftea.com/c/2009/01/QPDZU40MNW8FYYG3.asp
最恶心的是对于IE6,如果是<td><span>我是蚊子</span></td>,那么在td上写了word-break:keep-all依然无效,必须在span上也写。

7 获得head节点的方式不同
在Firefox下可以用window.head,而所有浏览器都兼容的方式是document.getElementsByTagName(‘head’)[0]

8 往head上添加css code的方法不同,也就是动态添加<style>标签。
IE下可以用var style=document.createStyleSheet();style.cssText=cssCode;而有文章说,在Windows上的IE,用createStyleSheet返回的是styleElement的styleSheet,而在Mac上返回的是styleElement自己。在其他浏览器下需要document.createElement(‘style’); 然后还有区分是否具有styleSheet属性。

9 对于onchange事件,firefox浏览器可以注册在table,div等组件上,然后通过冒泡,拦截input,select等发出的事件,而IE不行,必须绑定到相应的组件上

10 将input设为readonly=true,其依然会响应keypress,keyup,keydown,onblur事件

11 IE和非IE对于停止冒泡和取消默认行为的方法不同

12 大家都知道IE和非IE在动态添加事件时使用的方法不同,IE是attachEvent,其他是addListener,然后参数也不同。更重要的是如果一个控件绑定了多个function,他们绑定和执行的顺序是不同的,IE是跟绑定顺序相反,其他是跟绑定顺序相同

13 获得当前事件不同,一个是window.event一个是直接接受event

14 FF下执行offset系列非常慢,但是IE下比较快,而IE的改变CSS的执行非常慢。
Firefox6比Firefox3.6速度快的多,相差好几百倍(针对一个400行*50列的表格的JS处理)

15 如果大量动态改变css,那么使用document.createDocumentFragment,然后将需要修改样式的Dom获取出来appendChild到这个临时的fragment上,修改完css后再append回去即可,这样性能能差好几百倍。

16 浏览器加载网页时,顺序读取html,遇到外部js链接会读进来,然后按顺序执行,边解释边执行,而对于外部css,图片等则是启动另外的线程连接服务器去获取。
IE对于CSS引入有限制,我没试过,但有篇文章讨论:http://blog.csdn.net/ydshang/article/details/4158211

17 表格定位某一行,可以通过改变scrollTop来实现,当然如果出现了滚动条的话

18 IE的Dom用完要记得释放,可以在unload方法中,否则会出现内存泄露

19 unload方法在各个浏览器里各不相同,我之前的文章里有介绍。http://sslaowan.iteye.com/blog/1128209

20 我知道了为什么FE最喜欢的浏览器是FF,最讨厌的是IE6,恨不得IE6去死,其他IE也不怎么样。但是Chrome,Opera也各有各的bug

21 Ajax当使用同步模式时,如果访问的链接是错误的,那么FF会在控制台报错,而IE会直接弹出个对话框,然后就崩溃了。

22 FF支持document.getElementsByClass等方法,IE不支持,可以自己写一个。

23 trim方法在IE和FF下不同,需要自己写一个,可以用正则表达式

24 动态设置元素的css class在IE和非IE浏览器下也不同

25 有时本地字体库会影响你的字体,大小等显示,但是有时甚至会影响你的布局

26 字符串也可以使用><等符号比较大小,但是是字符串比较,不要被骗了

27 JS中this问题非常让人困惑

28 判断浏览器可以有很多方法,主流是两大类,agent判断法和特性法,后者好像更推荐

29 JS是面向对象语言,对象.属性=值 只影响当前对象,而对象.prototype.属性则影响整个类。非IE浏览器可以覆盖DOM对象的类方法,但是IE不行。

30 getComputedStyle,获得外部添加的css,FF支持,IE不支持,具体看这篇文章 http://www.jb51.net/article/16128.htm

31 IE和Chrome支持outerHTML方法,其他浏览器没有。相关讨论:http://walsh.iteye.com/blog/261966http://stackoverflow.com/questions/1700870/how-do-i-do-outerhtml-in-firefox

32 还有一个特悲剧的,IE下会把document.[formname.]控件Id当成那个控件,如果把一个控件比如input的id设为了submit,那么form.submit()就会报错。

33 如果利用全角空格进行布局时,Firefox支持,而IE会去除只剩一个,但是是在某些情况下的,具体看这篇文章:http://w3help.org/zh-cn/causes/BT1025

34 透明度:
filter:alpha(opacity=0); /* IE */
-moz-opacity:0.3; /* Moz + FF */
opacity: 0.3;

至于用不用var的区别,undefined和null的区别,Ajax构建的不同方式,这些一般的Java程序员都了解了。

很多Java程序员也会使用JS框架,比如JQuery,Extjs和Dojo,她们都帮我们屏蔽了很多兼容性问题。Dojo提供了Java一样的面向对象机制。

抛砖引玉,你还遇到过什么陷阱,那些FE都知道,而我们Java工程师不知道?

原文:http://sslaowan.iteye.com/blog/1156214

动态级联下拉框

今天需要遇到添加商品的某一类型的时候,原来的类型只有一个下拉框,虽然下拉框有层次缩进,但太长了,很不好使用,于是,想有这么一个处理:

1)首先生成根类的下拉框
2)选择某一个父类的时候,随后生成一个其子类的下拉框,如果有子类的子类(即孙子),不列出它们。
3)以此类推

完成以上功能的话,非JavaScript莫属了。

这里有一个难点,如果处理好只能生成当前父类的子类(如果存在的话),而且不能重复生成子类的下拉框(select)。变化到同级中的另一个父类的时候,之前父类的子类(还有由点击子类生成的其子类(即孙子类)的下拉框,再类推以下几级),都要消失,而是生成当前指向的父类的子类。

说得乱七八糟的吧。其实,也简单,我们要处理的是,当前某层类位置以后东东,我们需要一个东西记录这些东东,以便选择同层中另一个类时候的删除它们,插进该类的子类。

以上也只是自己想得处理这问题的一个方法,当前能力还非常有待提高,所以很期待高人指点。下边贴出代码,JavaScript好些东西是用于致学(即是用的时候,查看了下参考手册,很多东西没了解,用的比较生硬),还有,以下只支持IE,非常郁闷!其它浏览器不支持Event对象的srcElement属性哦!

类别的XML文档category.xml

HTML DOM:

处理xml并生成category的select菜单的JavaScript:xml_category.js

/**
 * @param   xml_name     xml文档的路径
 * @param   target_id      html的DOM的ID
 */
function xml_category(xml_name, target_id)
{
  var self = this;             // 私有变量,指向对象本身,在JS“类”的封装和访问机制,理解还有待加强。
  
   this.xmlDoc       = loadXMLDoc(xml_name)        // loadXMLDoc函数见/?p=302
   this.targetID     = document.getElementByID(target_id);
   this.catTree      = new Array();                       // 记录select的情况

   /**
    *  产生select下拉框
    */
   this.createSelect = function(parent_id)
   {
      var xmlDoc = self.xmlDoc;
      var cat, cat_num;

      // 以下为了都兼容各浏览器,不使用用与IE的XML DOM的selectNodes() 方法用一个 XPath(//cat[pid=*])[*表传来对应的parent_id] 查询选择节点.其它浏览器可参考Document.evaluate()。
      cat = xmlDoc.getElementByTagName("cat");
      cat_num = parseInt(cat.length);
     
      if (cat_num > 0) // 类别存在
      {
         var slt;
         var is_set = false;                                            // 为标志该层是否有类别,即有option选项
         slt = document.createElement("SELECT")              // 创建一个select元素节点
         slt.onchange = self.createChildSelect;                 // onchange事件

         // 每次都是整树遍历啊! !--
         for (var i=0, j=0; i < cat_num; i++)
         {
            // 同层的类别
            if (parseInt(cat[i].getAttribute("pid")) === parseInt(parent_id))
            {
               // option选项
               slt.optons[j] = new Option(cat[i].childNodes[0].nodeValue, cat[i].getAttribute("id"));
               is_set = true;
               j++; 
            }
         }

         // 有选项值
         if (is_set)
         {
             self.catTree.push(slt);          // 记录这个select下拉框
             self.targetID.appendChild(slt);
         }
         else
         {
            slt = null;
         }

      }
   }

   /** 
    * 生成子类
    */
  this.createChildSelect = function()
  {
       var slt_len = self.catTree.length;
       
       if (slt_len > 0)
       {
            var s = 0;                     // 标记当前触发option在catTree的位置(如果存在的情况)

            // 很不顺眼的遍历
            for (var i=0; i < slt_len; i++)
            { 
                // srcElement是IE的属性。 !--
                if (self.catTree[i] == event.srcElement)
                {
                    s = i ; break;
                }
            }

            // 删除该层位置以后所有的东东
            for (var i= slt_len - 1; i > s; i--)
            {
                 self.targetID.removeChild(self.catTree[i]);
                 seft.catTree.pop();
             }

            self.createSelect(event.srcElement.value);
       }

  }

}

下来在之前HTML文档位于id=”cat”写下如下JS,便可实现了:

var xml = xml_category("xml_category.xml", "cat")
xml.createSelect(0);

以上处理,还非常欠缺灵活性,就是初始化这个select下拉框的时候,如果输入的parent_id不是根类的ID,而是子类,或以下几层的子类的ID的时候,不能生成相应的select下拉框。

到此,写下这点记录,希望高人指点,也继续更进~

Chrome浏览器加载XML文档

Chrome浏览器不支持load方法加载XML文档。网上找了下,需要如下解决:

function loadXMLDoc(xml_name)
{
  var xmlDoc;

  try
  {
    xmlDoc = new ActiveXObject("Microsoft.XMLDOM");   // Support IE
  }
  catch(e)
  {
    try
    {
     // Support Firefox, Mozilla, Opera, etc
      xmlDoc = document.implementation.createDocument("", "", null) ;// 创建一个空的 XML 文档对象。
    }
    catch(e)
    {
       alert(e.message);
    }
  }

  // 加载XML文档
  try
  {
    xmlDoc.async = false;   // 关闭异步加载
    xmlDoc.load(xml_name);
  }
  catch(e)
  {
     // alert(e.message)   如果浏览器是Chrome,则会catch这个异常:Object # (a Document) has no method "load",所以,以下实现支持chrome加载XML文档(只是粗略的写下)

    var xhr = new XMLHttpRequest();
    xhr.open("GET", xml_name, false);
    xhr.send(null);
    
    xmlDoc = xhr.responseXML.documentElement;
  }

   return xmlDoc;
}

BTW,各浏览器加载XML字符串也不同。

IE使用 loadXML() 方法来解析 XML 字符串:

xmlDoc.loadXML(xml_str);

FireFox等使用DOMParser 对象解析XML字符串:

var parseXml = new DOMParser();
var doc = parseXml.parseFromString(xml_str,"text/xml");

JavaScript面向对象基础

传统的Web开发模式中,JavaScript一直被当作过程化的语言使用,比如表单验证之类。其实,JavaScript提供了完善的机制来实现面向对象的开发思想,这在Ajax技术上,表现得淋漓尽致。

总结下JavaScript面向对象的基础:(结合了《Web 2.0 开发技术详解》这本书,希望对大家有帮助)

 

1 定义类

用定义函数的方式(即用“function”关键字定义):

function class01 () { //……. }

此处class01是一个函数,也是一个类。作为函数,可以理解为类的构造函数(初始化)。在JavaScript中,函数和类就是一个概念。

 

BTW:函数声明的方法:

(1)function fun1(…)  {…}

(2)var fun1 = function(…) {…};     // 注意无名函数在调用之前,必需先定义

(3)var fun1 = function fun2(…) {…};

(4)var fun1 = new Function(p1, p2, …, pN, body);

 

2 定义对象

1)使用new操作符。– 适用内部类(如Date类,Array类,String类,RegExp,Math,Error,Object)和用户自定义类。

如:

function class01() { // 类成员的定义 }

var obj = new class01();  // 即所有函数都可以用new来操作。

 

e.g:

var obj = new Object();

obj.name = "ljlwill";               // 定义name属性

obj.hello = function(…) {….}    // 定义obj的hello方法

 

有个小技巧,以下大家发现没有

var namespace = new Object();

var namespace.class1 = function() {…..}

var obj1 = new namespace.class1();

>> 对象名起到一个命名空间的作用。先用new出一个namespace对象,把namespace对象的方法定义为类的形式,再new出一个新的obj1

 

2)使用{}大括号创建无类型对象

传统的面向对象语言中,每个对象都会对应到一个类,而JavaScript中的对象,其实就是属性(方法)的一个集合,无严格意义的类的概念。故可用{}的语法创建一个对象。

如:

var user = { name: "ljwill",        // 定义了name属性,初始化为“ljlwill

hobby: ['basketball', 'football', 'ping-pong'],  // 定义了爱好,类型为数组

// 定义hello方法

hello: function() { alert("Hello, " + this.name); },

…… }

可看到形式,其中属性名和其定义之间用:(冒号)隔开,以,(逗号)结尾。很是紧凑,清晰,减少代码体积。

 

3 对象的属性和方法的引用

1) 对象名.属性名(方法名) — 即用.符号引用

2) 对象名["属性(方法)名"]  — 即用[]符号引用

如:

var arr = new Array(); // 为数组添加一个元素a

arr["push"]("a"); arr.push("a");

// 计算数组长度

var len = arr["length"];

var len = arr.length;

用方括号[]的引用对象属性(方法),类似于数组,体现了一个JavaScript对象就是一组属性(方法)的集合这个性质。适用于不确定具体要引用哪个属性(方法)的场合。 在document的集合对象中,也有类似[]的用法,如果引用某一个“myForm"的表单对象:document.forms["myForm"],当然,也可以写成document.forms.myForm。(注意,这里的forms对象是一个内部对象)

 

4 对象的属性和方法的操作

如何为一个对象添加,修改或删除其属性和方法呢?

在一些语言中,要修改一个已生成的对象的属性或方法,必须在对应的类修改,重新实例化,再重新编译。JavaScript却可以灵活的动态添加,修改或删除其属性和方法。

如:先创建一个空对象user:var user = new Object();

1)添加属性。

user.name = "jinlie";   // 为其添加name属性

user.age   = 25;       // 为其添加age属性

user["name"] = "jinle"   // 使用[]

user["age"]   =  "25"

// 可否发现[]方式有个而外特点, 可以使用非标识符字符串作为属性名称,如数字开头或出现,user["007 name"] = "jinlie",如果user.007 name就错误了。

// 这一属性可以很容易实现一个简单的哈希表。在此不详述。 输出试试:alert(user.name);

 

2)添加方法

user.getName = function() { 

     // 可添加参数 return this.name;

} // alert(user.getName());

 

3)修改属性

即用新的属性替换旧的属性

user.name = "hello"; //  测试:alert(user.getName());

 

4)删除属性和方法

将其置为undefined即可。

user.name = undefined;

user.getName = undefined;

 

到此,我们可以感受JavaScript的强大的灵活性。

 

5 prototype原型对象

在JavaScript中,每个函数(function)其实也是一个对象,他们对应的类是“Function”,而每个函数对象都具有一个子对象prototype。所以,prototype表示了该函数的原型,也即是表示一个类的成员的集合。 prototype是一个对象,也可以对其方法进行动态修改:

如:

function class01 { // ….. }

// 对类的prototype对象增加方法

hello class01.prototype.method = function() { alert("Hello Word!"); }

var obj = new class01();

obj.hello();   // 调用hello()方法

 

看个有趣的例子:

// 为所有的Function函数对象添加method1方法

Function.prototype.method1 = function() {

     alert("This is a function.");

}

function func1() { alert("this is func1"); }

func1.method1();                        // 执行了alert("This is a function")

func1.method1.method1();      // 执行了alert("This is a function")

func1.method1.method1()是个递归定义,因为method1本身也是一个函数,便具有了函数对象的属性和方法,所以它调用了method1这个函数对象的method1方法。结论:所有对Function类型的方法扩充都具有这样的递归性质。

 

6 类成员的访问机制

1)公有成员,私有成员

在JavaScript中,并没有特殊的机制(比如用像一些语言中有关键字private,public等)来访问其成员,但可以通过变量的作用域性质来实现这些功能。– 在一函数内部定义的变量称为局部变量,它不可以被函数外边的程序所使用,却可以被其内部定义的嵌套函数所使用。

有思路了吧?如:

function class1() {

var name = 'ljlwill';      // 私有属性

 

// 私有方法

function privateMethod(…) {

   ….

}

 

// 公有方法

this.pubMethod = function(…) {

    ….

}

}

 

// 建以对象obj

var obj = new class1();

obj.pubMethod();

注意,prototype定义的类成员却不可以访问其构造体中定义的局部变量(私有成员)

 

2)静态成员

它可以通过“class.staticMemberName(类名.静态成员名)”访问。

实现:

function class1() {

     ….

}

calss1.staticName = "ljlwil";

class1.staticMethod = function(…) { …. }

 

如果想给所有的对象添加静态成员(比如静态方法):

Function.prototype.staticMethod = function(…) { … }

 

7 类的继承

继承是OOP中很重要的IDEA。很抱歉,JavaScript没有专门的机制(extends)来实现,但还是可以使用以下方式实现继续

1)拷贝一个类的prototype到另外一个类来实现继承。

如:

function class1() {

   …

}

function class2() {

  ..

}

class2.prototype = class1.prototype;    // 这样class2有了其公有类成员的属性或方法

 

下边的语句是成立的:

var obj = new  class2();

obj instanceof class1;     // intanceof 操作符判断一个对象是否是某一类的实例。

obj instanceof class2;

注意:在JavaScript中,除了基本数据类型(数字,字符串,布尔等),所有的赋值或函数传参都是引用传递,所以class2的prototype引用了class2的prototype,如果改变class2的prototype,class2的prototype也随之改变。所以,注意要不允许类成员的重新定义,用prototype实现继承,还是蛮好用的。

 

2)使用发射机制实现正确继承

发射机制指的是程序在运行时能够获取自身的信息。

如:

// 为Object类添加静态方法extend

Object.extend = function(destination, source) {

for(p in source) {

destination[p] = source[p];

}

return destination;

}

 

// 通过Object类为所有对象添加extend方法

Object.prototype.extend = function(object) {

return Object.extend.apply(this, [this, object]);

}

 

// 定义class1

function class1() {…)

 

class1.prototype = {

method1: function(…) {…},

method2: function(…) {….}

}

 

// 定义class2

function class2() {….}

 

// 继承

class2.prototype = (new class1()).extend({         // 不要把new class1()换成class1,这样和prototype实现继承一样的错误哦!

// 重新定义method1

method1: function() {

    ….

}

}

);

 

// 定义对象

var obj1 = new class1();

var obj2 = new class2();

// 试试吧

obj1.method1();

obj1.method2();

obj2.method1();

obj2.method2();

 

8 抽象类

JavaScript的抽象类也是没任何机制支持的。但却还是可以实现。

传统的面向对象语言中,抽象类的概念是,具有虚函数的类。而虚函数指的是,只做了一个声明而未实现的方法。在JavaScript中,虚函数可以看作该类中没有定义的方法,但已经通过this指针使用了(在其他语言中,虚函数必须先声明)。

如:

Object.extend = function(destination, source) {

for(p in source) {

destination[p] = source[p];

}

return destination;

}

 

// 定义一个抽象基类base,无构造函数
function base() {…}

base.prototype = {

initialize: function() {

this.oninit();     // 调用了一个虚方法

}

 

// 如果要在基类中添加虚方法的一个定义,也可以的:

// oninit: function() {}   // 虚方法是一个空方法,由派生类实现

}

 

function class1() {….}

class1.prototype = (new base()).extend({

// 实现抽象类中的oninit虚方法

oninit: function() {

// 实现

}

});

 

好,今天就谈到这,JavaScript一个非常灵活的设计语言。学好基础,看看那些类的设计模式,和一些出名的框架,不禁要;)一下!