Can not step twice into the same river

分类 F2E 下的文章

02月 05

HTML META 那些事

作为一个苦逼的前端,移动设备的铺天盖地的涌来,html页面head之间的那些优化,你是不是还仅仅只惦记着关键字神马的呢?酱紫很容易out哦~。真是不整不知道一整吓一跳,本文整理一些常用的meta标签,与诸君共览:

<!DOCTYPE html><!-- 使用 HTML5 doctype,不区分大小写 注意:doctype前千万必要加注释神马的,ie会强制quirk模式 -->
<html lang="zh-cmn-Hans"> <!-- 更加标准的 lang 属性写法 http://zhi.hu/XyIa -->
<head>
    <!-- 声明文档使用的字符编码 -->
    <meta charset='utf-8'>
    <!-- 优先使用 IE 最新版本和 Chrome -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
    <!-- 启用360浏览器的极速模式(webkit) -->
    <meta name="renderer" content="webkit">
    <!-- 页面描述 -->
    <meta name="description" content="不超过150个字符"/>
    <!-- 页面关键词 -->
    <meta name="keywords" content=""/>
    <!-- 网页作者 -->
    <meta name="author" content="name, email@gmail.com"/>
    <!-- 搜索引擎抓取 -->
    <meta name="robots" content="index,follow"/>
    <!-- 为移动设备添加 viewport -->
    <meta name="viewport" content="initial-scale=1, maximum-scale=3, minimum-scale=1, user-scalable=no">
    <!-- `width=device-width` 会导致 iPhone 5 添加到主屏后以 WebApp 全屏模式打开页面时出现黑边 http://bigc.at/ios-webapp-viewport-meta.orz -->
 
    <!-- iOS 设备 begin -->
    <meta name="apple-mobile-web-app-title" content="标题">
    <!-- 添加到主屏后的标题(iOS 6 新增) -->
    <meta name="apple-mobile-web-app-capable" content="yes"/>
    <!-- 是否启用 WebApp 全屏模式,删除苹果默认的工具栏和菜单栏 -->
 
    <meta name="apple-itunes-app" content="app-id=myAppStoreID, affiliate-data=myAffiliateData, app-argument=myURL">
    <!-- 添加智能 App 广告条 Smart App Banner(iOS 6+ Safari) -->
    <meta name="apple-mobile-web-app-status-bar-style" content="black"/>
    <!-- 设置苹果工具栏颜色 -->
    <meta name="format-detection" content="telphone=no, email=no"/>
    <!-- 忽略页面中的数字识别为电话,忽略email识别 -->
    <!-- 启用360浏览器的极速模式(webkit) -->
    <meta name="renderer" content="webkit">
    <!-- 避免IE使用兼容模式 -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <!-- 不让百度转码 -->
    <meta http-equiv="Cache-Control" content="no-siteapp" />
    <!-- 针对手持设备优化,主要是针对一些老的不识别viewport的浏览器,比如黑莓 -->
    <meta name="HandheldFriendly" content="true">
    <!-- 微软的老式浏览器 -->
    <meta name="MobileOptimized" content="320">
    <!-- uc强制竖屏 -->
    <meta name="screen-orientation" content="portrait">
    <!-- QQ强制竖屏 -->
    <meta name="x5-orientation" content="portrait">
    <!-- UC强制全屏 -->
    <meta name="full-screen" content="yes">
    <!-- QQ强制全屏 -->
    <meta name="x5-fullscreen" content="true">
    <!-- UC应用模式 -->
    <meta name="browsermode" content="application">
    <!-- QQ应用模式 -->
    <meta name="x5-page-mode" content="app">
    <!-- windows phone 点击无高光 -->
    <meta name="msapplication-tap-highlight" content="no">
    <!-- iOS 图标 begin -->
    <link rel="apple-touch-icon-precomposed" href="/apple-touch-icon-57x57-precomposed.png"/>
    <!-- iPhone 和 iTouch,默认 57x57 像素,必须有 -->
    <link rel="apple-touch-icon-precomposed" sizes="114x114" href="/apple-touch-icon-114x114-precomposed.png"/>
    <!-- Retina iPhone 和 Retina iTouch,114x114 像素,可以没有,但推荐有 -->
    <link rel="apple-touch-icon-precomposed" sizes="144x144" href="/apple-touch-icon-144x144-precomposed.png"/>
    <!-- Retina iPad,144x144 像素,可以没有,但推荐有 -->
    <!-- iOS 图标 end -->
 
    <!-- iOS 启动画面 begin -->
    <link rel="apple-touch-startup-image" sizes="768x1004" href="/splash-screen-768x1004.png"/>
    <!-- iPad 竖屏 768 x 1004(标准分辨率) -->
    <link rel="apple-touch-startup-image" sizes="1536x2008" href="/splash-screen-1536x2008.png"/>
    <!-- iPad 竖屏 1536x2008(Retina) -->
    <link rel="apple-touch-startup-image" sizes="1024x748" href="/Default-Portrait-1024x748.png"/>
    <!-- iPad 横屏 1024x748(标准分辨率) -->
    <link rel="apple-touch-startup-image" sizes="2048x1496" href="/splash-screen-2048x1496.png"/>
    <!-- iPad 横屏 2048x1496(Retina) -->
 
    <link rel="apple-touch-startup-image" href="/splash-screen-320x480.png"/>
    <!-- iPhone/iPod Touch 竖屏 320x480 (标准分辨率) -->
    <link rel="apple-touch-startup-image" sizes="640x960" href="/splash-screen-640x960.png"/>
    <!-- iPhone/iPod Touch 竖屏 640x960 (Retina) -->
    <link rel="apple-touch-startup-image" sizes="640x1136" href="/splash-screen-640x1136.png"/>
    <!-- iPhone 5/iPod Touch 5 竖屏 640x1136 (Retina) -->
    <!-- iOS 启动画面 end -->
 
    <!-- iOS 设备 end -->
    <meta name="msapplication-TileColor" content="#000"/>
    <!-- Windows 8 磁贴颜色 -->
    <meta name="msapplication-TileImage" content="icon.png"/>
    <!-- Windows 8 磁贴图标 -->
 
    <link rel="alternate" type="application/rss+xml" title="RSS" href="/rss.xml"/>
    <!-- 添加 RSS 订阅 -->
    <link rel="shortcut icon" type="image/ico" href="/favicon.ico"/>
    <!-- 添加 favicon icon -->

    <!-- sns 社交标签 begin -->
    <!-- 参考微博API -->
    <meta property="og:type" content="类型" />
    <meta property="og:url" content="URL地址" />
    <meta property="og:title" content="标题" />
    <meta property="og:image" content="图片" />
    <meta property="og:description" content="描述" />
    <!-- sns 社交标签 end -->
 
    <title>标题</title>
</head>
01月 20

this杂谈

以下内容全是掏箱底的干货(大牛那里复制来的),部分我也没有掌握。

全局this

浏览器宿主的全局环境中,this指的是window对象。

<script type="text/javascript">
    console.log(this === window); //true
</script>

浏览器中在全局环境下,使用var声明变量其实就是赋值给this或window。

<script type="text/javascript">
    var foo = "bar";
    console.log(this.foo); //logs "bar"
    console.log(window.foo); //logs "bar"
</script>

任何情况下,创建变量时没有使用var或者let(ECMAScript 6),也是在操作全局this。

<script type="text/javascript">
    foo = "bar";
    function testThis() {
      foo = "foo";
    }
    console.log(this.foo); //logs "bar"
    testThis();
    console.log(this.foo); //logs "foo"
</script>

Node命令行(REPL)中,this是全局命名空间。可以通过global来访问。

> this
{ ArrayBuffer: [Function: ArrayBuffer],
  Int8Array: { [Function: Int8Array] BYTES_PER_ELEMENT: 1 },
  Uint8Array: { [Function: Uint8Array] BYTES_PER_ELEMENT: 1 },
  ...
> global === this
true

在Node环境里执行的JS脚本中,this其实是个空对象,有别于global。

test.js
console.log(this);
console.log(this === global);

$ node test.js
{}
false

当尝试在Node中执行JS脚本时,脚本中全局作用域中的var并不会将变量赋值给全局this,这与在浏览器中是不一样的。

test.js
var foo = "bar";
console.log(this.foo);

$ node test.js
undefined

…但在命令行里进行求值却会赋值到this身上。

> var foo = "bar";
> this.foo
bar
> global.foo
bar

在Node里执行的脚本中,创建变量时没带var或let关键字,会赋值给全局的global但不是this(译注:上面已经提到this和global不是同一个对象,所以这里就不奇怪了)。

test.js
foo = "bar";
console.log(this.foo);
console.log(global.foo);
$ node test.js
undefined
bar

但在Node命令行里,就会赋值给两者了。

译注:简单来说,Node脚本中global和this是区别对待的,而Node命令行中,两者可等效为同一对象。

函数或方法里的this

除了DOM的事件回调或者提供了执行上下文(后面会提到)的情况,函数正常被调用(不带new)时,里面的this指向的是全局作用域。

<script type="text/javascript">
    foo = "bar";
    function testThis() {
      this.foo = "foo";
    }
    console.log(this.foo); //logs "bar"
    testThis();
    console.log(this.foo); //logs "foo"
</script>

test.js

foo = "bar";
function testThis () {
  this.foo = "foo";
}
console.log(global.foo);
testThis();
console.log(global.foo);
$ node test.js
bar
foo

还有个例外,就是使用了"use strict";。此时this是undefined。

<script type="text/javascript">
    foo = "bar";
    function testThis() {
      "use strict";
      this.foo = "foo";
    }
    console.log(this.foo); //logs "bar"
    testThis();  //Uncaught TypeError: Cannot set property 'foo' of undefined 
</script>

当用调用函数时使用了new关键字,此刻this指代一个新的上下文,不再指向全局this。

<script type="text/javascript">
    foo = "bar";
    function testThis() {
      this.foo = "foo";
    }
    console.log(this.foo); //logs "bar"
    new testThis();
    console.log(this.foo); //logs "bar"
    console.log(new testThis().foo); //logs "foo"
</script>

通常我将这个新的上下文称作实例。

原型中的this

函数创建后其实以一个函数对象的形式存在着。既然是对象,则自动获得了一个叫做prototype的属性,可以自由地对这个属性进行赋值。当配合new关键字来调用一个函数创建实例后,此刻便能直接访问到原型身上的值。

function Thing() {
    console.log(this.foo);
}
Thing.prototype.foo = "bar";
var thing = new Thing(); //logs "bar"
console.log(thing.foo);  //logs "bar"

当通过new的方式创建了多个实例后,他们会共用一个原型。比如,每个实例的this.foo都返回相同的值,直到this.foo被重写。

function Thing() {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
    console.log(this.foo);
}
Thing.prototype.setFoo = function (newFoo) {
    this.foo = newFoo;
}
var thing1 = new Thing();
var thing2 = new Thing();
thing1.logFoo(); //logs "bar"
thing2.logFoo(); //logs "bar"
thing1.setFoo("foo");
thing1.logFoo(); //logs "foo";
thing2.logFoo(); //logs "bar";
thing2.foo = "foobar";
thing1.logFoo(); //logs "foo";
thing2.logFoo(); //logs "foobar";

在实例中,this是个特殊的对象,而this自身其实只是个关键字。你可以把this想象成在实例中获取原型值的一种途径,同时对this赋值又会覆盖原型上的值。完全可以将新增的值从原型中删除从而将原型还原为初始状态。

function Thing() {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
    console.log(this.foo);
}
Thing.prototype.setFoo = function (newFoo) {
    this.foo = newFoo;
}
Thing.prototype.deleteFoo = function () {
    delete this.foo;
}
var thing = new Thing();
thing.setFoo("foo");
thing.logFoo(); //logs "foo";
thing.deleteFoo();
thing.logFoo(); //logs "bar";
thing.foo = "foobar";
thing.logFoo(); //logs "foobar";
delete thing.foo;
thing.logFoo(); //logs "bar";

…或者不通过实例,直接操作函数的原型。

function Thing() {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
    console.log(this.foo, Thing.prototype.foo);
}
var thing = new Thing();
thing.foo = "foo";
thing.logFoo(); //logs "foo bar";

同一函数创建的所有实例均共享一个原型。如果你给原型赋值了一个数组,那么所有实例都能获取到这个数组。除非你在某个实例中对其进行了重写,实事上是进行了覆盖。

function Thing() {
}
Thing.prototype.things = [];
var thing1 = new Thing();
var thing2 = new Thing();
thing1.things.push("foo");
console.log(thing2.things); //logs ["foo"]

通常上面的做法是不正确的(译注:改变thing1的同时也影响了thing2)。如果你想每个实例互不影响,那么请在函数里创建这些值,而不是在原型上。

多个函数可以形成原型链,这样this便会在原型链上逐步往上找直到找到你想引用的值。

function Thing1() {
}
Thing1.prototype.foo = "bar";
function Thing2() {
}
Thing2.prototype = new Thing1();
var thing = new Thing2();
console.log(thing.foo); //logs "bar"

很多人便是利用这个特性在JS中模拟经典的对象继承。
注意原型链底层函数中对this的操作会覆盖上层的值。

function Thing1() {
}
Thing1.prototype.foo = "bar";
function Thing2() {
    this.foo = "foo";
}
Thing2.prototype = new Thing1();
function Thing3() {
}
Thing3.prototype = new Thing2();
var thing = new Thing3();
console.log(thing.foo); //logs "foo"

我习惯将赋值到原型上的函数称作方法。上面某些地方便使用了方法这样的字眼,比如logFoo方法。这些方法中的this同样具有在原型链上查找引用的魔力。通常将最初用来创建实例的函数称作构造函数。
原型链方法中的this是从实例中的this开始住上查找整个原型链的。也就是说,如果原型链中某个地方直接对this进行赋值覆盖了某个变量,那么我们拿到 的是覆盖后的值。

function Thing1() {
}
Thing1.prototype.foo = "bar";
Thing1.prototype.logFoo = function () {
    console.log(this.foo);
}
function Thing2() {
    this.foo = "foo";
}
Thing2.prototype = new Thing1();
var thing = new Thing2();
thing.logFoo(); //logs "foo";

在JavaScript中,函数可以嵌套函数,也就是你可以在函数里面继续定义函数。但内层函数是通过闭包获取外层函数里定义的变量值的,而不是直接继承this。

function Thing() {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
    var info = "attempting to log this.foo:";
    function doIt() {
        console.log(info, this.foo);
    }
    doIt();
}
var thing = new Thing();
thing.logFoo();  //logs "attempting to log this.foo: undefined"

上面示例中,doIt 函数中的this指代是全局作用域或者是undefined如果使用了"use strict";声明的话。对于很多新手来说,理解这点是非常头疼的。
还有更奇葩的。把实例的方法作为参数传递时,实例是不会跟着过去的。也就是说,此时方法中的this在调用时指向的是全局this或者是undefined在声明了"use strict";时。

function Thing() {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {  
    console.log(this.foo);   
}
function doIt(method) {
    method();
}
var thing = new Thing();
thing.logFoo(); //logs "bar"
doIt(thing.logFoo); //logs undefined

所以很多人习惯将this缓存起来,用个叫self或者其他什么的变量来保存,以将外层与内层的this区分开来。

function Thing() {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
    var self = this;
    var info = "attempting to log this.foo:";
    function doIt() {
        console.log(info, self.foo);
    }
    doIt();
}
var thing = new Thing();
thing.logFoo();  //logs "attempting to log this.foo: bar"

…但上面的方式不是万能的,在将方法做为参数传递时,就不起作用了。

function Thing() {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () { 
    var self = this;
    function doIt() {
        console.log(self.foo);
    }
    doIt();
}
function doItIndirectly(method) {
    method();
}
var thing = new Thing();
thing.logFoo(); //logs "bar"
doItIndirectly(thing.logFoo); //logs undefined

解决方法就是传递的时候使用bind方法显示指明上下文,bind方法是所有函数或方法都具有的。

function Thing() {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () { 
    console.log(this.foo);
}
function doIt(method) {
    method();
}
var thing = new Thing();
doIt(thing.logFoo.bind(thing)); //logs bar

同时也可以使用apply或call 来调用该方法或函数,让它在一个新的上下文中执行。

function Thing() {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () { 
    function doIt() {
        console.log(this.foo);
    }
    doIt.apply(this);
}
function doItIndirectly(method) {
    method();
}
var thing = new Thing();
doItIndirectly(thing.logFoo.bind(thing)); //logs bar

使用bind可以任意改变函数或方法的执行上下文,即使它没有被绑定到一个实例的原型上。

function Thing() {
}
Thing.prototype.foo = "bar";
function logFoo(aStr) {
    console.log(aStr, this.foo);
}
var thing = new Thing();
logFoo.bind(thing)("using bind"); //logs "using bind bar"
logFoo.apply(thing, ["using apply"]); //logs "using apply bar"
logFoo.call(thing, "using call"); //logs "using call bar"
logFoo("using nothing"); //logs "using nothing undefined"

避免在构造函数中返回作何东西,因为返回的东西可能覆盖本来该返回的实例。

function Thing() {
    return {};
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
    console.log(this.foo);
}
var thing = new Thing();
thing.logFoo(); //Uncaught TypeError: undefined is not a function

但,如果你在构造函数里返回的是个原始值比如字符串或者数字什么的,上面的错误就不会发生了,返回语句将被忽略。所以最好别在一个将要通过new来调用的构造函数中返回作何东西,即使你是清醒的。如果你想实现工厂模式,那么请用一个函数来创建实例,并且不通过new来调用。当然这只是个人建议。
诚然,你也可以使用Object.create从而避免使用new。这样也能创建一个实例。

function Thing() {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
    console.log(this.foo);
}
var thing =  Object.create(Thing.prototype);
thing.logFoo(); //logs "bar"

这种方式不会调用该构造函数。

function Thing() {
    this.foo = "foo";
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
    console.log(this.foo);
}
var thing =  Object.create(Thing.prototype);
thing.logFoo(); //logs "bar"

正因为Object.create没有调用构造函数,这在当你想实现一个继承时是非常有用的,随后你可能想要重写构造函数。

function Thing1() {
    this.foo = "foo";
}
Thing1.prototype.foo = "bar";
function Thing2() {
    this.logFoo(); //logs "bar"
    Thing1.apply(this);
    this.logFoo(); //logs "foo"
}
Thing2.prototype = Object.create(Thing1.prototype);
Thing2.prototype.logFoo = function () {
    console.log(this.foo);
}
var thing = new Thing2();

对象中的this

可以在对象的任何方法中使用this来访问该对象的属性。这与用new得到的实例是不一样的。

var obj = {
    foo: "bar",
    logFoo: function () {
        console.log(this.foo);
    }
};
obj.logFoo(); //logs "bar"

注意这里并没有使用new,也没有用Object.create,更没有函数的调用来创建对象。也可以将函数绑定到对象,就好像这个对象是一个实例一样。

var obj = {
    foo: "bar"
};
function logFoo() {
    console.log(this.foo);
}
logFoo.apply(obj); //logs "bar"

此时使用this没有向上查找原型链的复杂工序。通过this所拿到的只是该对象身上的属性而以。

var obj = {
    foo: "bar",
    deeper: {
        logFoo: function () {
            console.log(this.foo);
        }
    }
};
obj.deeper.logFoo(); //logs undefined

也可以不通过this,直接访问对象的属性。

var obj = {
    foo: "bar",
    deeper: {
        logFoo: function () {
            console.log(obj.foo);
        }
    }
};
obj.deeper.logFoo(); //logs "bar"

DOM 事件回调中的this

在DOM事件的处理函数中,this指代的是被绑定该事件的DOM元素。

function Listener() {
    document.getElementById("foo").addEventListener("click",
       this.handleClick);
}
Listener.prototype.handleClick = function (event) {
    console.log(this); //logs "<div id="foo"></div>"
}
var listener = new Listener();
document.getElementById("foo").click();

…除非你通过bind人为改变了事件处理器的执行上下文。

function Listener() {
    document.getElementById("foo").addEventListener("click", 
        this.handleClick.bind(this));
}
Listener.prototype.handleClick = function (event) {
    console.log(this); //logs Listener {handleClick: function}
}
var listener = new Listener();
document.getElementById("foo").click();

HTML中的this

HTML标签的属性中是可能写JS的,这种情况下this指代该HTML元素。

<div id="foo" onclick="console.log(this);"></div>
<script type="text/javascript">
document.getElementById("foo").click(); //logs <div id="foo"...
</script>

重写this

无法重写this,因为它是一个关键字。

function test () {
    var this = {};  // Uncaught SyntaxError: Unexpected token this 
}

eval中的this

eval 中也可以正确获取当前的 this。

function Thing () {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
    eval("console.log(this.foo)"); //logs "bar"
}
var thing = new Thing();
thing.logFoo();

这里存在安全隐患。最好的办法就是避免使用eval。
使用Function关键字创建的函数也可以获取this:

function Thing () {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = new Function("console.log(this.foo);");
var thing = new Thing();
thing.logFoo(); //logs "bar"

使用with时的this

使用with可以将this人为添加到当前执行环境中而不需要显示地引用this。

function Thing () {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
    with (this) {
        console.log(foo);
        foo = "foo";
    }
}
var thing = new Thing();
thing.logFoo(); // logs "bar"
console.log(thing.foo); // logs "foo"

正如很多人认为的那样,使用with是不好的,因为会产生歧义。

jQuery中的this

一如HTML DOM元素的事件回调,jQuery库中大多地方的this也是指代的DOM元素。页面上的事件回调和一些便利的静态方法比如$.each 都是这样的。

<div class="foo bar1"></div>
<div class="foo bar2"></div>
<script type="text/javascript">
$(".foo").each(function () {
    console.log(this); //logs <div class="foo...
});
$(".foo").on("click", function () {
    console.log(this); //logs <div class="foo...
});
$(".foo").each(function () {
    this.click();
});
</script

传递 this

如果你用过underscore.js或者lo-dash你便知道,这两个库中很多方法你可以传递一个参数来显示指定执行的上下文。比如_.each。自ECMAScript 5 标准后,一些原生的JS方法也允许传递上下文,比如forEach。事实上,上文提到的bind,apply还有call 已经给我们手动指定函数执行上下文的能力了。

function Thing(type) {
    this.type = type;
}
Thing.prototype.log = function (thing) {
    console.log(this.type, thing);
}
Thing.prototype.logThings = function (arr) {
   arr.forEach(this.log, this); // logs "fruit apples..."
   _.each(arr, this.log, this); //logs "fruit apples..."
}
var thing = new Thing("fruit");
thing.logThings(["apples", "oranges", "strawberries", "bananas"]);

这样可以使得代码简洁些,不用层层嵌套bind,也不用不断地缓存this。

08月 22

js 与或运算符 || && 妙用

var add_level = 0; 
switch(add_step){ 
case 5 : add_level = 1; 
break; 
case 10 : add_level = 2; 
break; 
case 12 : add_level = 3; 
break; 
case 15 : add_level = 4; 
break; 
default : add_level = 0; 
break; 

可以写成:

 var add_level = (add_step==5 && 1) || (add_step==10 && 2) || (add_step==12 && 3) || (add_step==15 && 4) || 0; 

var add_level={'5':1,'10':2,'12':3,'15':4}[add_step] || 0; 

巧用&&

if(a >=5){ 
alert("你好"); 
} 

可以写成:

a >= 5 && alert("你好");
07月 04

关于页面渲染的一些记录

浏览器是怎样渲染一个页面的?

从浏览器渲染页面的大概过程开始说起:

  1. 由从服务器接收到的 HTML 形成 DOM(文档对象模型)。
  2. 样式被加载和解析,形成 CSSOM(CSS 对象模型)。
  3. 紧接着 DOM 和 CSSOM 创建了一个渲染树,这个渲染树是一些被渲染对象的集合( Webkit 分别叫它们”renderer”和”render object”,而在Gecko 引擎中叫”frame”)。除了不可见的元素(比如 head 标签和一些有 display:none 属性的元素),渲染树映射了 DOM 的结构。在渲染树中,每一个文本字符串都被当做一个独立的 renderer。每个渲染对象都包含了与之对应的计算过样式的DOM 对象(或者一个文本块)。换句话说,渲染树描述了 DOM 的直观的表现形式。
  4. 对每个渲染元素来说,它的坐标是经过计算的,这被叫做“布局(layout)”。浏览器使用一种只需要一次处理的“流方法”来布局所有元素(tables需要多次处理)。
  5. 最后,将布局显示在浏览器窗口中,这个过程叫做“绘制(painting)”。

重绘

当在页面上修改了一些不需要改变定位的样式的时候(比如background-color,border-color,visibility),浏览器只会将新的样式重新绘制给元素(这就叫一次“重绘”或者“重新定义样式”)。

重排

当页面上的改变影响了文档内容、结构或者元素定位时,就会发生重排(或称“重新布局”)。重排通常由以下改变触发:

  • DOM 操作(如元素增、删、改或者改变元素顺序)。
  • 内容的改变,包括 Form 表单中文字的变化。
  • 计算或改变 CSS 属性。
  • 增加或删除一个样式表。
  • 改变”class”属性。
  • 浏览器窗口的操作(改变大小、滚动窗口)。
  • 激活伪类(如:hover状态)。

浏览器如何优化渲染?

浏览器尽最大努力限制重排的过程仅覆盖已更改的元素的区域。举个例子,一个 position 为 absolue 或 fixed 的元素的大小变化只影响它自身和子孙元素,而对一个 position 为 static 的元素做同样的操作就会引起所有它后面元素的重排。

另一个优化就是当运行一段Jjavascript 代码的时候,浏览器会将一些修改缓存起来,然后当代码执行的时候,一次性的将这些修改执行。举例来说,这段代码会触发一次重绘和一次重排:

var $body = $('body'); 
$body.css('padding', '1px'); // 重排, 重绘 
$body.css('color', 'red'); // 重绘 
$body.css('margin', '2px'); // 重排, 重绘 
// 实际上只有一次重排和重绘被执行。

如上面所说,访问一个元素的属性会进行一次强制重排。如果我们给上面的代码加上一行读取元素属性的代码,这个情况就会出现:

var $body = $('body'); 
$body.css('padding', '1px'); 
$body.css('padding'); // 这里读取了一次元素的属性,一次强制重排就会发生。 
$body.css('color', 'red'); 
$body.css('margin', '2px');

上面这段代码的结果就是,进行了两次重排。因此,为了提高性能,你应该讲读取元素属性的代码组织在一起(细节的例子可以看JSBin上的代码)。

有一种情况是必须触发一次强制重排的。例如:给元素改变同一个属性两次(比如margin-left),一开始设置100px,没有动画,然后通过动画的形式将值改为50px。具体可以看例子,当然,我在这里会讲更多的细节。

我们从一个有transition的CSS class开始:

.has-transition {
   -webkit-transition: margin-left 1s ease-out;
      -moz-transition: margin-left 1s ease-out;
        -o-transition: margin-left 1s ease-out;
           transition: margin-left 1s ease-out;
}

然后进行实现:

//我们的元素默认有"has-transition"属性 
var $targetElem = $('#targetElemId');

//删除包含transition的class 
$targetElem.removeClass('has-transition');

// 当包含transition的class已经没了的时候,改变元素属性 
$targetElem.css('margin-left', 100);

// 再将包含transition的class添加回来 
$targetElem.addClass('has-transition');

// 改变元素属性 
$targetElem.css('margin-left', 50);

上面的实现没有按照期望的运行。所有的修改都被浏览器缓存了,只在上面这段代码的最后才会执行。我们需要的是一次强制重排,我们可以通过进行以下修改来实现:

//删除包含transition的class 
$(this).removeClass('has-transition');

// 改变元素属性 
$(this).css('margin-left', 100);

//触发一次强制重排,从而使变化了的class或属性能够立即执行。 
$(this)[0].offsetHeight; // offsetHeight仅仅是个例子,其他的属性也可以奏效。

// 再将包含transition的class添加回来 
$(this).addClass('has-transition');

// 改变元素属性 
$(this).css('margin-left', 50);

现在这段代码如我们所期望的运行了。

实际的优化建议

汇总了一些有用的信息,我建议以下几点:

  1. 创建合法的 HTML 和 CSS ,别忘了制定文件编码,Style 应该写在 head 标签中,script 标签应该加载 body标签结束的位置。
  2. 试着简化和优化 CSS 选择器(这个优化点被大多数使用 CSS 预处理器的开发者忽略了)。将嵌套层数控制在最小。以下是CSS选择器的性能排行(从最快的开始):
    1.ID选择器:#id

2.class选择器: .class
3.标签: div
4.相邻的兄弟元素:a + i
5.父元素选择器: ul > li
6.通配符选择器: *
7.伪类和伪元素: a:hover,你应该记住浏览器处理选择器是从右向左的,这也就是为什么最右面的选择器会更快——#id或.class。

div * {...} // bad
.list li {...} // bad
.list-item {...} // good
'#'list .list-item {...} // good

在你的脚本中,尽可能的减少 DOM 的操作。把所有东西都缓存起来,包括属性和对象(如果它可被重复使用)。进行复杂的操作的时候,最好操作一个“离线”的元素(“离线”元素的意思是与 DOM 对象分开、仅存在内存中的元素),然后将这个元素插入到 DOM 中。

如果你使用 jQuery,遵循jQuery 选择器最佳实践

要改变元素的样式,修改“class”属性是最高效的方式之一。你要改变 DOM 树的层次越深,这一条就越高效(这也有助于将表现和逻辑分开)。

尽可能的只对 position 为 absolute 或 fix 的元素做动画。

当滚动时禁用一些复杂的 :hover 动画是一个很好的主意(例如,给 body 标签加一个 no-hover 的 class)

04月 11

关于js冒泡的一些玩意儿

什么是冒泡:
以点击事件为例,就是点击一个子元素,同时也把这个元素的父元素的点击事件触发了。
Linke this:

<div id="outer">
    <div id="inter"></div>
</div>
//JS:
$("#outer").click(function(){
    alert("outer");
})
$("#inter").click(function(){
    alert("inter");
})

上面的代码中,点击"#inter",会先弹出"inter",紧接着会弹出"outer".
为什么我点击子元素“#inter”的事件却会触发父元素的事件?
这就是冒泡了,点击子元素,同时会触发父元素的事件。

是不是理解起来 So Easy 啦 ^.^

如何阻止冒泡:
要阻止冒泡,就要用到event.stopPropagation()方法:

//HTML:
<div id="outer">
    <div id="inter"></div>
</div>
//JS:
$("#outer").click(function(){
    alert("outer");
})
$("#inter").click(function(event){
    event.stopPropagation()
    alert("inter");
})

在点击子元素的事件上添加event.stopPropagation()方法,就能阻止向父元素冒泡。

================= 万恶的分割线 ===================

当然以上是正常情况,作为一个苦逼的前端开发,都知道有个特例的,那就是万恶的IE;
那么IE怎么处理呢。。。。

function show1(e){
        var ev = e || window.event;
        if (ev.stopPropagation) {
            ev.stopPropagation();
        }
        else if (window.event) {//IE
            window.event.cancelBubble = true;//IE
        }
}

window.event是IE中的属性,如果是IE,就执行window.event.cancelBubble = true;来阻止冒泡。

04月 04

Javascript绝句欣赏

  1. 取整同时转成数值型:

    '10.567890'|0
    

结果: 10

'10.567890'^0

结果: 10

-2.23456789|0

结果: -2

~~-2.23456789

结果: -2

  1. 日期转数值:

    var d = +new Date(); //1295698416792

  2. 类数组对象转数组:

    var arr = [].slice.call(arguments)

  3. 漂亮的随机码:

    Math.random().toString(16).substring(2); //14位
    Math.random().toString(36).substring(2); //11位

  4. 合并数组:

    var a = [1,2,3];
    var b = [4,5,6];
    Array.prototype.push.apply(a, b);
    uneval(a); //[1,2,3,4,5,6]

  5. 用0补全位数:

    function prefixInteger(num, length) {
    return (num / Math.pow(10, length)).toFixed(length).substr(2);
    }

  6. 交换值:

    a= b, b=a;

  7. 将一个数组插入另一个数组的指定位置:

    var a = [1,2,3,7,8,9];
    var b = [4,5,6];
    var insertIndex = 3;
    a.splice.apply(a, Array.concat(insertIndex, 0, b));
    // a: 1,2,3,4,5,6,7,8,9

  8. 删除数组元素:

    var a = [1,2,3,4,5];
    a.splice(3,1);

  9. 快速取数组最大和最小值

    Math.max.apply(Math, [1,2,3]) //3
    Math.min.apply(Math, [1,2,3]) //1

(出自http://ejohn.org/blog/fast-javascript-maxmin/)

  1. 条件判断:

    var a = b && 1;

相当于

if (b) {
  a = 1
}

var a = b || 1; 

相当于

if (b) {
  a = b;
} else {
  a = 1;
}
  1. 判断IE:

    var ie = /@cc_on !@/false;

01月 26

几个超实用的JavaScript技巧及最佳实践

1.第一次给变量赋值时,别忘记var关键字
如果初次赋值给未声明的变量,该变量会被自动创建为全局变量,在JS开发中,应该避免使用全局变量,这是大家容易忽略的错误。

2.使用===而非==
并且永远不要使用=或!=。

[10] === 10    // is false  
[10]  == 10    // is true  
'10' == 10     // is true  
'10' === 10    // is false  
 []   == 0     // is true  
 [] ===  0     // is false  
 '' == false   // is true but true == "a" is false  
 '' ===   false // is false  

3.使用分号来作为行终止字符
在行终止的地方使用分号是一个很好的习惯,即使开发人员忘记加分号,编译器也不会有任何提示,因为在大多数情况下,JavaScript解析器会自动加上。

  function Person(firstName, lastName){  
        this.firstName =  firstName;  
        this.lastName = lastName;          
    }    
      
    var Saad = new Person("Saad", "Mousliki"); 

5.小心使用typeof、instanceof和constructor

var arr = ["a", "b", "c"];  
typeof arr;   // return "object"   
arr  instanceof Array // true  
arr.constructor();  //[]
 

6.创建一个自调用(Self-calling)函数
通常被称为自调用匿名函数或即刻调用函数表达式(LLFE)。当函数被创建的时候就会自动执行,如下:

 (function(){  
        // some private code that will be executed automatically  
    })();    
    (function(a,b){  
        var result = a+b;  
        return result;  
    })(10,20)  

7.给数组创建一个随机项

var items = [12, 548 , 'a' , 2 , 5478 , 'foo' , 8852, , 'Doe' , 2145 , 119];  
var  randomItem = items[Math.floor(Math.random() * items.length)];  

8.在特定范围里获得一个随机数
下面这段代码非常通用,当你需要生成一个测试的数据时,比如在最高工资和最低工资之间获取一个随机数的话。

var x = Math.floor(Math.random() * (max - min + 1)) + min;

9.在数字0和最大数之间生成一组随机数

 var numbersArray = [] , max = 100;  
for( var i=1; numbersArray.push(i++) < max;);  // numbers = [0,1,2,3 ... 100]  

10.生成一组随机的字母数字字符

 function generateRandomAlphaNum(len) {  
        var rdmstring = "";  
        for( ; rdmString.length < len; rdmString  += Math.random().toString(36).substr(2));  
        return  rdmString.substr(0, len);  
      
    }  

11.打乱数字数组

   var numbers = [5, 458 , 120 , -215 , 228 , 400 , 122205, -85411];  
    numbers = numbers.sort(function(){ return Math.random() - 0.5});  
    /* the array numbers will be equal for example to [120, 5, 228, -215, 400, 458, -85411, 122205]  */  

12.字符串trim函数
trim函数可以删除字符串两端的空白字符,可以用在Java、C#、PHP等多门语言里。

String.prototype.trim = function(){return this.replace(/^\s+|\s+$/g, "");};

13.数组追加

var array1 = [12 , "foo" , {name "Joe"} , -2458];  
var array2 = ["Doe" , 555 , 100];  
Array.prototype.push.apply(array1, array2);  
/* array1 will be equal to  [12 , "foo" , {name "Joe"} , -2458 , "Doe" , 555 , 100] */  

14.将参数对象转换为数组

var argArray = Array.prototype.slice.call(arguments); 

15.验证一个指定参数是否为数字

function isNumber(n){  
    return !isNaN(parseFloat(n)) && isFinite(n);  
}  
 

16.验证一个给定的参数为数组

function isArray(obj){  
        return Object.prototype.toString.call(obj) === '[object Array]' ;  
    }

注意,如果toString()方法被重写了,你将不会得到预期结果。
或者你可以这样写:

  Array.isArray(obj); // its a new Array method  

同样,如果你使用多个frames,你可以使用instancesof,如果内容太多,结果同样会出错。

var myFrame = document.createElement('iframe');  
    document.body.appendChild(myFrame);  
      
    var myArray = window.frames[window.frames.length-1].Array;  
    var arr = new myArray(a,b,10); // [a,b,10]    
      
    // instanceof will not work correctly, myArray loses his constructor   
    // constructor is not shared between frames  
    arr instanceof Array; // false  

17.从数字数组中获得最大值和最小值

var  numbers = [5, 458 , 120 , -215 , 228 , 400 , 122205, -85411];   
var maxInNumbers = Math.max.apply(Math, numbers);   
var minInNumbers = Math.min.apply(Math, numbers);  

18.清空数组

var myArray = [12 , 222 , 1000 ];    
myArray.length = 0; // myArray will be equal to [].  

19.不要用delete从数组中删除项目
开发者可以使用split来替代delete去删除数组中的项目。好的方式是使用delete去替换数组中undefined的数组项目,而不是使用delete去删除数组中项目。

 var items = [12, 548 ,'a' , 2 , 5478 , 'foo' , 8852, , 'Doe' ,2154 , 119 ];   
items.length; // return 11   
delete items[3]; // return true   
items.length; // return 11   
/* items will be equal to [12, 548, "a", undefined × 1, 5478, "foo", 8852, undefined × 1, "Doe", 2154,       119]   */

应该如下使用

var items = [12, 548 ,'a' , 2 , 5478 , 'foo' , 8852, , 'Doe' ,2154 , 119 ];   
items.length; // return 11   
items.splice(3,1) ;   
items.length; // return 10   
/* items will be equal to [12, 548, "a", 5478, "foo", 8852, undefined × 1, "Doe", 2154,       119]   */ 

delete方法应该用来删除一个对象属性。

20.使用length属性截短数组
如上文提到的清空数组,开发者还可以使用length属性截短数组。

var myArray = [12 , 222 , 1000 , 124 , 98 , 10 ];    
myArray.length = 4; // myArray will be equal to [12 , 222 , 1000 , 124].

如果你所定义的数组长度值过高,那么数组的长度将会改变,并且会填充一些未定义的值到数组里,数组的length属性不是只读的。

myArray.length = 10; // the new array length is 10   
myArray[myArray.length - 1] ; // undefined  

21. 使用逻辑AND/OR来处理条件语句

var foo = 10;  
foo == 10 && doSomething(); // is the same thing as if (foo == 10) doSomething(); 
foo == 5 || doSomething(); // is the same thing as if (foo != 5) doSomething();

逻辑AND也可以用来设置含糊参数缺省的值

Function doSomething(arg1){ 
    Arg1 = arg1 || 10; // arg1 will have 10 as a default value if it’s not already set
}

22. 使用map()函数方法来循环数组里的项目

var squares = [1,2,3,4].map(function (val) {  
    return val * val;  
}); 
// squares will be equal to [1, 4, 9, 16] 

23. 按小数点后N位来四舍五入

var num =2.443242342;
num = num.toFixed(4);  // num will be equal to 2.4432

24. 浮点问题

0.1 + 0.2 === 0.3 // is false 
9007199254740992 + 1 // is equal to 9007199254740992  
9007199254740992 + 2 // is equal to 9007199254740994

为什么? 0.1 + 0.2 等于 0.30000000000000004 。你应该知道所有的javascript数字在64位2进制内部都是使用浮点表示

这个来自于IEEE 754标准。更多信息介绍,请参考:相关博客

你可以使用上面介绍的toFixed()和toPrecision()来解决这个问题

25. 使用for-in循环来检查对象的指定属性

下面的代码片段非常实用,可以避免从对象的prototype来循环遍历对象的属性:

for (var name in object) {  
    if (object.hasOwnProperty(name)) { 
        // do something with name                    
    }  
}

26. 逗号操作符

var a = 0; 
var b = ( a++, 99 ); 
console.log(a);  // a will be equal to 1 
console.log(b);  // b is equal to 99

27. 缓存需要计算或者DOM查询的变量

使用jQuery的选择器,我们一定要记住缓存DOM元素,这样会提高执行效率:

var navright = document.querySelector('#right'); 
var navleft = document.querySelector('#left'); 
var navup = document.querySelector('#up'); 
var navdown = document.querySelector('#down');

28. 在传入isFinite()之前验证参数

isFinite(0/0) ; // false 
isFinite("foo"); // false 
isFinite("10"); // true 
isFinite(10);   // true 
isFinite(undifined);  // false 
isFinite();   // false 
isFinite(null);  // true  !!! 

29. 避免数组中index为负值

var numbersArray = [1,2,3,4,5]; 
var from = numbersArray.indexOf("foo") ;  // from is equal to -1 
numbersArray.splice(from,2);    // will return [5]

这里需要注意indexof的参数 不能为负值,但是splice可以

30. 序列化和反序列化(用来处理JSON)

var person = {name :'Saad', age : 26, department : {ID : 15, name : "R&D"} }; 
var stringFromPerson = JSON.stringify(person); 
/* stringFromPerson is equal to "{"name":"Saad","age":26,"department":{"ID":15,"name":"R&D"}}"   */ 
var personFromString = JSON.parse(stringFromPerson);  
/* personFromString is equal to person object  */

31. 避免使用eval或者Function构建器

使用eval或者function构建器是一件非常消耗资源的操作,因为每次调用script引擎都必须将源代码转换为可执行的代码

var func1 = new Function(functionCode); //避免使用!!
var func2 = eval(functionCode);//避免使用!!

32. 避免使用with()

使用with()可以用来插入一个变量到全局。然而,如果另外一个变量拥有同样的名字,将会导致非常混乱并且会覆盖数值

33. 避免在数组中使用for-in循环

不推荐使用:

var sum = 0;  
for (var i in arrayNumbers) {  
    sum += arrayNumbers[i];  
}

如下代码将会更好:

var sum = 0;  
for (var i = 0, len = arrayNumbers.length; i < len; i++) {  
    sum += arrayNumbers[i];  
}

作为额外的好处,i和len的实例化都执行一次,因为都是循环中的第一个语句,但是比下面执行速度更快:

for (var i = 0; i < arrayNumbers.length; i++)

为什么? arrayNumbers的长度在每次循环都计算一次

34. 传递函数,而非字符串到setTimeout()和setInterval()中

如果你传递一个字符串到setTimeout和setInterval中,处理方式和eval将会类似,速度会很慢,不要使用如下:

setInterval('doSomethingPeriodically()', 1000);  
setTimeOut('doSomethingAfterFiveSeconds()', 5000);
推荐使用如下

setInterval(doSomethingPeriodically, 1000);  
setTimeOut(doSomethingAfterFiveSeconds, 5000);

35. 使用switch/case语句而非一系列的if/else

如果多余两个条件,使用switch/case将会更快,而且语法更优雅(代码组织的更好)。对于多余10个条件的避免使用。

36. 使用switch/case语句处理数值区域

使用如下小技巧处理数值区域:

function getCategory(age) {  
    var category = "";  
    switch (true) {  
        case isNaN(age):  
            category = "not an age";  
            break;  
        case (age >= 50):  
            category = "Old";  
            break;  
        case (age <= 20):  
            category = "Baby";  
            break;  
        default:  
            category = "Young";  
            break;  
    };  
    return category;  
}  
getCategory(5);  // will return "Baby"

37. 创建一个prototype是指定对象的对象

使用如下代码可以生成一个prototype是指定对象的对象:

function clone(object) {  
    function OneShotConstructor(){}; 
    OneShotConstructor.prototype= object;  
    return new OneShotConstructor(); 
} 
clone(Array).prototype ;  // []

**39. 一个HTMLescaper方法**

function escapeHTML(text) {  
    var replacements= {"<": "&lt;", ">": "&gt;","&": "&amp;", "\"": "&quot;"};                      
    return text.replace(/[<>&"]/g, function(character) {  
        return replacements[character];  
    }); 
}

编译:当然,前台处理并不安全,后台处理更彻底

40. 在循环中避免使用try-catch-finally

不要使用如下代码:

var object = ['foo', 'bar'], i;  
for (i = 0, len = object.length; i <len; i++) {  
    try {  
        // do something that throws an exception 
    }  
    catch (e) {   
        // handle exception  
    } 
}

使用这段代码:

var object = ['foo', 'bar'], i;  
try { 
    for (i = 0, len = object.length; i <len; i++) {  
        // do something that throws an exception 
    } 
} 
catch (e) {   
    // handle exception  
} 

40. 设置XMLHttpRequests的timeout

如果一个XHR花费了太多时间,你可以在XHR调用中使用setTimeout来退出连接:

var xhr = new XMLHttpRequest (); 
xhr.onreadystatechange = function () {  
    if (this.readyState == 4) {  
        clearTimeout(timeout);  
        // do something with response data 
    }  
}  
var timeout = setTimeout( function () {  
    xhr.abort(); // call error callback  
}, 60*1000 /* timeout after a minute */ ); 
xhr.open('GET', url, true);  
 
xhr.send();

额外的好处,你可以完全避免同步AJAX调用

41. 处理WebSocket timeout

一般来说,当一个websocket连接建立后,服务器可以在30秒无响应的情况下time out你的连接。防火墙也可以做到。

为了处理timeout问题,你可以定时发送一个空的消息到服务器。为了实现,你可以添加两个方法到你的代码中:

一个保证连接的存在,另外一个取消连接。使用这个技巧,你可以处理timeout问题:

var timerID = 0; 
function keepAlive() { 
    var timeout = 15000;  
    if (webSocket.readyState == webSocket.OPEN) {  
        webSocket.send('');  
    }  
    timerId = setTimeout(keepAlive, timeout);  
}  
function cancelKeepAlive() {  
    if (timerId) {  
        cancelTimeout(timerId);  
    }  
}

keepAlive函数可以添加到webSocket的onOpen函数的最后。cancelKeepAlive添加到webSocket的onClose函数最后。

42. 记住,操作符比函数调用更快

不推荐使用:

var min = Math.min(a,b); 
A.push(v);

推荐使用:

var min = a < b ? a:b; 
A[A.length] = v;

43. 不要忘记使用代码美化工具。
在代码产品化前使用JSLint和代码压缩工具(例如,JSMin)来处理

05月 27

现代浏览器中内置的几个可以等效替代jQuery的功能

jQuery的体积在不断的增大。新功能要不断增加,这是必然结果。虽然从版本1.8.3开始的瘦身效果明显,但不可否认的是,对于移动手机端的网页开发,它仍然是不可接受的。当然,jQuery不是铁板一块,你可以对它进行定制,只打包进你想要的组件,但其中的一些用来兼容老式浏览器的代码你无法去除。

就我的个人习惯来说,不管开发什么项目,即使是一个很简单的demo,我做的第一件事就是引入jQuery,这样做主要是想使用它提供的DOM选择器功能。对于一些像IE6/IE7这样的老式浏览器,这种做法是显而易见的,但是,如今现代浏览器里已经内置了完整的DOM选择器功能,能让你使用原生的浏览器提供的方法来实现jQuery的功能。

document.queryselector

这里说的现代浏览器是指火狐、谷歌、Opera、Safri等新生代浏览器,如果是IE,你需要至少是IE8或以上。你可以简单的将document.querySelector()函数映射为

$

,它能返回在DOM中找到的第一个匹配选择条件的元素。任何的CSS选择符都可以作为它的参数。

注意:IE 8只支持 CSS2.1标准的选择器

<div class="container"><br />  <ul><br />    <li id="pink">Pink</li><br />    <li id="salmon">Salmon</li><br /><br />    <li id="blue">Blue</li><br />    <li id="green">Green</li><br />    <li id="red">Red</li><br />  </ul>  <br /><br /></div><br /><br /><script><br /><br />  // 创建全局的 '$' 变量<br />  window.$ = function(selector) {<br />    return document.querySelector(selector);<br />  };<br /><br />  (function() {<br />    // 通过id查找item1,将它的背景颜色修改为浅红<br />    var item = $("#salmon").style.backgroundColor="salmon";<br />    console.log(item);<br />  }());  <br /></script>

你需要使用原生的“style”方法,而且这里不能使用jQuery里的链式调用。console.log()输出的将会是backgroundColor方法返回的“salmon”。

原生DOM节点有时候会比封装后的jQuery对象更好用。例如,如果你想修改一个图片的src属性,比较一下下面使用jQuery的做法和直接使用DOM节点的做法。

// jQuery方式<br />$("#picture").attr("src", "http://placekitten.com/200/200");<br /><br />//使用将querySelector映射为$的原生js方式<br />$("#picture").src = "http://placekitten.com/200/200";

DOM对象能让你直接访问图片的src属性,相反,所有的jQuery对象都需要你通过attr函数来操作。

document.querySelector方法只返回一个元素。如果你的选择目标是一堆元素,它只会返回符合条件的第一个节点。要想返回所有节点,你需要使用document.querySelectorAll方法。

document.queryselectorall

一个很巧妙的做法是将querySelector映射为$,而将querySelectorAll函数映射为$$。但它返回的是一个节点列表,不如jQuery里返回的Array格式好用,我们可以使用Array.prototype.slice.call(nodeList)方法加工一下,让它更方便。

<div class="container"><br />  <ul><br />    <li id="pink">Pink</li><br /><br />    <li id="salmon">Salmon</li><br /><br />    <li id="blue">Blue</li><br />    <li id="green">Green</li><br />    <li id="red">Red</li><br /><br />  </ul>  <br /><br /></div><br /><br /><script><br /><br />  window.$ = function(selector) {<br />    return document.querySelector(selector);<br />  };<br /><br />  window.$$ = function(selector) {<br />    var items = {},<br />    results = [],<br />    length = 0,<br />    i = 0;<br /><br />    // 注意,IE8不支持这种做法<br />    results = Array.prototype.slice.call(document.querySelectorAll(selector));<br /><br />    length = results.length;<br /><br />    // add the results to the items object<br />    for ( ; i < length; ) {<br />      items[i] = results[i];<br />      i++;<br />    }<br /><br />    // 添加一些额外的属性,让它更像一个Array<br />    items.length = length;<br />    items.splice = [].splice();<br /><br />    // 添加 'each' 方法<br />    items.each = function(callback) {<br />      var i = 0;<br />      for ( ; i < length; ) {<br />        callback.call(items[i]);<br />        i++;<br />      }<br />    }<br /><br />    return items;<br />  };<br /><br />  (function() {<br /><br />    // 查找green项,修改字体大小<br />    $("#green").style.fontSize = "2em";<br /><br />    // 通过id查找,修改背景色<br />    $$("li").each(function() {<br />      this.style.backgroundColor = this.id;<br />    });<br />  }());<br /><br /></script>

注意,IE8是不支持将一个nodeList转换为Array的。

classList

使用jQuery对CSS类操作十分方便,幸运的是,现代浏览器里也内置了一下方法能够很方便的操作它们,主要用到了classList对象。下面是一些基本操作两种方式的对比。

window.$ = function(selector) {<br />    return document.querySelector(selector);<br />  };<br /><br />  //----增加CSS类------<br /><br />  /* jQuery */<br />  $(".main-content").addClass("someClass");<br /><br />  /* 等效内置方法 */<br />  $(".main-content").classList.add("someClass");<br /><br />  //----删除一个CSS类-----<br /><br />  /* jQuery */<br />  $(".main-content").removeClass("someClass");<br /><br />  /* 等效内置方法 */<br />  $(".main-content").classList.remove("someClass");<br /><br />  //----判断是否存在一个CSS类---<br /><br />  /* jQuery */<br />  if($(".main-content").hasClass("someClass"))<br /><br />  /* 等效内置方法 */<br />  if($(".main-content").classList.contains("someClass"))

相对于jQuery提供大量丰富的方法,上面说的这几个现代浏览器里内置的功能显得很简单,但如果项目中的要求不高,这些替代方法能大大的减少你的应用依赖。

最后要提醒的是,这些内置方法在不同的浏览器和不同的版本中支持程度不一样,下面是它们的支持程度参考表。

附图1:各种浏览器对querySelector/querySelectorAll的兼容支持

各种浏览器对querySelector/querySelectorAll的兼容支持

附图1:各种浏览器对classlist的兼容支持

各种浏览器对classlist的兼容支持

03月 13

ExtJs使用总结

1.Ext.get
var el = Ext.get('myElementId');//获取元素,等同于document.getElementById('myElementId');//会缓存
2. Ext.fly
var el = Ext.fly('myElementId')//不需要缓存。
注:享元模式(Flyweight Design Pattern)是一种节省内存的模式,该模式的大概原理是建立单个全体对象然后不断反复使用它。
3.Ext.getDom
var elDom = Ext.getDom('elId'); // 依据id来查dom节点
var elDom1 = Ext.getDom(elDom); // 依据dom节点来查dom节点

二、CSS元素
4.addClass
Ext.fly('elId').addClass('myCls'); // 加入元素的'myCls'的样式
5.radioClass
Ext.fly('elId').radioClass('myCls');//添加一个或多个className到这个元素,并移除其所有侧边(siblings)节点上的同名样式。
6.removeClass
Ext.fly('elId').removeClass('myCls'); // 移除元素的样式
7.toggleClass
Ext.fly('elId').toggleClass('myCls'); // 加入样式
Ext.fly('elId').toggleClass('myCls'); // 移除样式
Ext.fly('elId').toggleClass('myCls'); // 再加入样式
8.hasClass
if (Ext.fly('elId').hasClass('myCls')) {//判断是否已加上这个样式
// 是有样式的……
}
10.replaceClass
Ext.fly('elId').replaceClass('myClsA', 'myClsB');//替换样式
11.getStyle
var color = Ext.fly('elId').getStyle('color');//返回该元素的统一化当前样式和计算样式。
var zIndx = Ext.fly('elId').getStyle('z-index');//返回该元素的统一化当前样式和计算样式。
12.setStyle
Ext.fly('elId').setStyle({
display : 'block',
overflow : 'hidden',
cursor : 'pointer'
});//设置元素的样式,也可以用一个对象参数包含多个样式。
13.getColor
Ext.fly('elId').getColor('color');//为指定的CSS属性返回CSS颜色
14.setOpacity

Ext.fly('elId').setOpacity(.45, true);//设置元素的透明度。
15.clearOpacity
Ext.fly('elId').clearOpacity();//清除这个元素的透明度设置

三、Dom游历
16.Ext.fly('elId').select('li:nth-child(2n)').addClass('red');
17.is 测试当前元素是否与传入的选择符相符一致。
复制代码代码如下:
var el = Ext.get('elId'); if (el.is('p.myCls')) { // 条件成立 }

18.findParent
定位于此节点,以此节点为起点,向外围搜索外层的父节点,搜索条件必须符合并匹配传入的简易选择符。
Ext.fly('elId').findParent('div'); // 返回dom节点
Ext.fly('elId').findParent('div', 4); // 查找4个节点
Ext.fly('elId').findParent('div', null, true); // 返回Ext.Element
19.findParentNode
定位于此节点的“父节点”,以此节点的“父节点”为起点,向外围搜索外层的“父父”节点,搜索条件必须符合并匹配传入的简易选择符。
Ext.fly('elId').findParentNode('div');
20.up
沿着DOM,向外围搜索外层的“父父”节点,搜索条件必须符合并匹配传入的简易选择符。
Ext.fly('elId').up('div');
Ext.fly('elId').up('div', 5); // 限5层的内查找
21.select
传入一个CSS选择符的参数,然后依据该CSS选择符从当前元素下面,形成期待匹配子节点的集合,也就是“选择”的操作,最后以一个Ext.CompositeElement类型的组合元素的形式返回。如果以Ext.select()调用表示从document可是搜索。
// 返回结果的CompositeElement
Ext.fly('elId').select('div:nth-child(2)');
// 返回数组
Ext.fly('elId').select('div:nth-child(2)',
true);
// 整个document都会搜索
Ext.select('div:nth-child(2)');
22.query
进行一次query的查询,返回DOM 节点组成的数组。可选地第二参数设置为查询的起点,如不指定则为 document。
// 返回dom节点组成的数组
Ext.query('div:nth-child(2)');
23.child
基于送入的选择符,不限定深度进行搜索,符合的话选取单个子节点。
Ext.fly('elId').child('p.highlight'); // 返回的类型是Ext.Element
Ext.fly('elId').child('p.highlight', true); // 返回dom节点
24.down
基于该选择符,"直接"选取单个子节点。
Ext.fly('elId').down('span'); // 返回的类型是Ext.Element
Ext.fly('elId').down('span', true); // 返回dom节点

25.parent
返回当前节点的那个父节点,可选地可送入一个期待的选择符。
// 返回父节点,类型是Ext.Element
Ext.fly('elId').parent();
// 返回父节点,类型是html dom
Ext.fly('elId').parent("", true);
// 返回父级节点,但一定要是div的,找到就返回,类型是Ext.Element
Ext.fly('elId').parent("div");
26.next
获取下一个侧边节点,跳过文本节点。可选地可送入一个期待的选择符。
// 返回下一个侧边节点,类型是Ext.Element
Ext.fly('elId').next();
// 返回下一个侧边节点,类型是html dom
Ext.fly('elId').next("", true);
// 返回下一个侧边节点,但一定要是div的,找到就返回,类型是Ext.Element
Ext.fly('elId').next("div");
27.prev
获取上一个侧边节点,跳过文本节点。可选地可送入一个期待的选择符。
// 返回上一个侧边节点,类型是Ext.Element
Ext.fly('elId').prev();
// 返回上一个侧边节点,类型是html dom
Ext.fly('elId').prev("", true);
// 返回上一个侧边节点,但一定要是div的,找到就返回,类型是Ext.Element
Ext.fly('elId').prev("div");
28.first
获取第一个侧边节点,跳过文本节点。可选地可送入一个期待的选择符。
// 返回第一个侧边节点,类型是Ext.Element
Ext.fly('elId').first();
// 返回第一个侧边节点,类型是html dom
Ext.fly('elId').first("", true);
// 返回第一个侧边节点,但一定要是div的,找到就返回,类型是Ext.Element
Ext.fly('elId').first("div");
29.last
获取最后一个侧边节点,跳过文本节点。可选地可送入一个期待的选择符。
// 返回最后一个侧边节点,类型是Ext.Element
Ext.fly('elId').last();
// 返回最后一个侧边节点,类型是html dom
Ext.fly('elId').last("", true);
// 返回最后一个侧边节点,但一定要是div的,找到就返回,类型是Ext.Element
Ext.fly('elId').last("div");

四、DOM操控(DHTML常见的一项任务就是DOM元素的增、删、改、查)
30.appendChild
把送入的元素归为这个元素的子元素。
var el = Ext.get('elId1');
// 用id指定
Ext.fly('elId').appendChild('elId2');
// Ext.Element添加
Ext.fly('elId').appendChild(el);
// 选择符组合地添加
Ext.fly('elId').appendChild(['elId2','elId3']);
// 直接添加dom节点
Ext.fly('elId').appendChild(el.dom);
// 添加CompositeElement,一组的div
Ext.fly('elId').appendChild(Ext.select('div'));
31.appendTo
把这个元素添加到送入的元素里面。
var el = Ext.get('elId1');
// 'elId'添加到'elId2'里面
Ext.fly('elId').appendTo('elId2');
Ext.fly('elId').appendTo(el); //
添加到Ext.Element el
32.insertBefore
传入一个元素的参数,将其放置在当前元素之前的位置。
var el = Ext.get('elId1');
// dom节点在前面插入
Ext.fly('elId').insertBefore('elId2');
//Ext.Element el在前面插入
Ext.fly('elId').insertBefore(el);
33.insertAfter
传入一个元素的参数,将其放置在当前元素之后的位置。
var el = Ext.get('elId1');
// dom节点在后面插入
Ext.fly('elId').insertAfter('elId2');
// Ext.Element el在后面插入
Ext.fly('elId').insertAfter(el);
34.insertFirst
可以是插入一个元素,也可以是创建一个元素(要创建的话请使用“DomHelper配置项对象”作为参数传入),总之,这个元素作为当前元素的第一个子元素出现。
var el = Ext.get('elId1');
// 插入的dom节点作为第一个元素
Ext.fly('elId').insertFirst('elId2');
// 插入的Ext.Element作为第一个元素
Ext.fly('elId').insertFirst(el);
// 用DomHelper配置项创建新节点,新节点会作为第一个子元素被插入。
Ext.fly('elId').insertFirst({
tag: 'p',
cls: 'myCls',
html: 'Hi I am the new first child'
});
35.replace
用于当前这个元素替换传入的元素。
var el = Ext.get('elId1');
// 'elId'去替换'elId2'
Ext.fly('elId').replace('elId2');
// 'elId'去替换'elId1'
Ext.fly('elId').replace(el);
36.replaceWith
用传入的元素替换这个元素。参数可以是新元素或是要创建的DomHelper配置项对象。
var el = Ext.get('elId1');
Ext.fly('elId').replaceWith('elId2'); // 'elId2'替换掉'elId'.
Ext.fly('elId').replaceWith(el); //
'elId1'替换掉'elId'
// 用DomHelper配置项创建新节点,并用该节点换掉‘elId'。
Ext.fly('elId').replaceWith({
tag: 'p',
cls: 'myCls',
html: 'Hi I have replaced elId'
});

五、DomHelper配置项
37.createChild
传入一个DomHelper配置项对象的参数,将其创建并加入到该元素。
var el = Ext.get('elId');
var dhConfig = {
tag: 'p',
cls: 'myCls',
html: 'Hi I have replaced elId'
};
// 创建新的节点,放到'elId'里面
el.createChild(dhConfig);
// 创建新的节点,居el第一个子元素之前
el.createChild(dhConfig, el.first());
38.wrap
创建一个新的元素,包裹在当前元素外面。
Ext.fly('elId').wrap(); // div包着elId
// 用新建的一个元素来包着elId
Ext.fly('elId').wrap({
tag: 'p',
cls: 'myCls',
html: 'Hi I have replaced elId'
});

六、Html片断
38.insertHtml
插入HTML片断到这个元素。至于要插入的html放在元素的哪里,你可指定beforeBegin, beforeEnd, afterBegin, afterEnd这几种。第二个参数是插入HTML片断,第三个参数是决定是否返回一个Ext.Element类型的DOM对象。
Ext.fly('elId').insertHtml(
'beforeBegin',
'

点击我

',
true
); // 返回Ext.Element
39.remove
从DOM里面移除当前元素,并从缓存中删除。.
Ext.fly('elId').remove(); //
elId在缓存和dom里面都没有
40.removeNode
移除document的DOM节点。如果是body节点的话会被忽略。
Ext.removeNode(node); // 从dom里面移除(HTMLElement)

七、Ajax
41.load
直接访问Updater的Ext.Updater.update()方法(相同的参数)。参数与Ext.Updater.update()方法的一致。
Ext.fly('elId').load({url: 'serverSide.php'})
42.getUpdater
获取这个元素的UpdateManager。
var updr = Ext.fly('elId').getUpdater();
updr.update({
url: 'http://myserver.com/index.php',
params: {
param1: "foo",
param2: "bar"
}
});