一次項目中移除jQuery的實踐

爲何要從項目中移除jQuery

早期的項目爲了下降兼容瀏覽器的成本使用了jQuery,在項目使用期間,也逐漸暴露了一些問題。因爲咱們是sdk項目,須要集成進公司其餘部門的產品,因此對適配以及兼容性要求比較高。在應用過程當中,就發生了與移動端三方庫zepto變量$衝突的問題。當時爲了解決這個問題,新建了一個變量提供給jQuery以免衝突。後期有了充裕的時間,便考慮寫一個基類用來代替jQuery,同時還能減小一次網絡請求,何樂而不爲呢。css

我把這個基類起名叫作BaseElement,因爲項目中實際使用的dom節點不是不少,不須要考慮渲染的性能消耗,因此只實現了一些節點操做必備的方法。html

用一個基類來封裝dom節點,必然會有不一樣的節點類型和不一樣的樣式,有時也會有文本,因此要給構造函數加入幾個參數:css3

constructor (tag, className, text) {
        this._element = null;
        if (typeof tag === 'string') {
            this._element = document.createElement(tag);

        } else if (tag instanceof HTMLElement) {
            this._element = tag;
        }

        if (this._element) {
            if (typeof className === 'string' && className !== '') {
                this._element.className = className;
            }
            if (typeof text === 'string' && text !== '') {
                this._element.innerText = text;
            }
        }
    }
    
    get element () {
        return this._element;
    }
複製代碼

這樣BaseElement基類就用_element這個變量持有了dom節點,接下來的一系列方法也都與dom節點有關。瀏覽器

首先是顯示與隱藏的方法,並加入了變量_visible用於記錄當前節點的顯示狀態,節點默認爲顯示:網絡

constructor () {
        this._visible = true;
    }
    
    show () {
        this._element.style.display = '';
        this._visible = true;
        return this;
    }
    
    hide () {
        this._element.style.display = 'none';
        this._visible = false;
        return this;
    }
    
    get visible () {
        return this._visible;
    }
複製代碼

這兩個方法都返回了當前節點基類,方便外部調用時用一行代碼實現多種功能,例如:app

let element = new BaseElement('div');
element.show().hide();
複製代碼

基類光有顯示和隱藏方法並不夠,還須要把這個節點加入到節點樹中才可以真正的顯示出來,這裏實現了與jQuery方法名相同的appendpreAppend方法:dom

append (child) {
        if (this._element instanceof HTMLElement) {
            if (child instanceof BaseElement) {
                this._element.appendChild(child.element);
            } else if (child instanceof HTMLElement) {
                this._element.appendChild(child);
            }
            return this;
        }
        return null;
    }
    
    preAppend (child) {
        if (this._element instanceof HTMLElement) {
            if (child instanceof BaseElement) {
                this._element.prepend(child.element);
            } else if (child instanceof HTMLElement) {
                this._element.prepend(child);
            }
            return child;
        }
        return null;
    }
複製代碼

再來一個移除的方法:ide

remove (child) {
        if (this._element instanceof HTMLElement)
            if (child instanceof BaseElement) {
                this._element.removeChild(child.element);
            } else if (child instanceof HTMLElement) {
                this._element.removeChild(child);
            }
            return this;
        }
        return null;
    }
複製代碼

再實現一個移除自身全部子節點的方法:函數

empty () {
        if (this._element instanceof HTMLElement) {
            let children = this._element.children;
            while (children.length) {
                this._element.removeChild(children[0]);
            }
        }
    }
複製代碼

實現這些方法以後,節點基類已經能夠自由的添加與移除節點,接下來就須要處理節點的行間內容與樣式了。性能

節點行間內容用兩個存取器能夠輕鬆實現:

set text (value) {
        if (typeof value === 'string' && this._element instanceof HTMLElement) {
            this._element.innerText = value;
        }
    }
    
    get text () {
        if (this._element instanceof HTMLElement) {
            return this._element.innerText;
        }
        return '';
    }
    
    set html (value) {
        if (typeof value === 'string' && this._element instanceof HTMLElement) {
            this._element.innerHTML = value;
        }
    }
    
    get html () {
        if (this._element instanceof HTMLElement) {
            return this._element.innerHTML;
        }
        return '';
    }
複製代碼

至於樣式,也提供了不一樣的方法來操做:

css (key, value) {
        if (value !== null && this._element instanceof HTMLElement) {
            this._element.style[key] = value;
        }
        return this;
    }
    
    addClass (className) {
        if (this._element instanceof HTMLElement) {
            let tmpClassName = this._element.className;
            if (className !== '' && !tmpClassName.split(' ').includes(className)) {
                tmpClassName += ' ' + className;
                this._element.className = tmpClassName;
            }
            return this;
        }
        return null;
    }
    
    removeClass (className) {
        if (this._element instanceof HTMLElement) {
            let tmpClassName = this._element.className;
            if (className !== '') {
                this._element.className = tmpClassName.split(' ').filter((value) => {
                    return value !== className && value !== '';
                }).join(' ');
            }
            return this;
        }
        return null;
    }
    
    hasClass (className) {
        if (this._element instanceof HTMLElement) {
            if(className !== '') {
                return this._element.className.indexOf(className) >= 0;
            }
        }
        return false;
    }
複製代碼

css方法能夠直接設置節點的某項樣式,例如:

let element = new BaseElement('div');
element.css('width', '100%');
複製代碼

addClass方法則直接給節點設置一個class,須要寫相應的css來配合。

有時咱們還須要獲取節點的lefttop值進行相應的計算,再提供一個_offset方法來獲取這些值,_offset方法經過循環調用offsetParent來獲取當前節點與頁面左上角的偏移,關於offsetParent的詳情請查看這裏

_offset (direction) {
        if (this._element instanceof HTMLElement) {
            let offsetDir: string = 'offset' + direction[0].toUpperCase() + direction.substring(1);

            let realNum: number = this._element[offsetDir];
            var positionParent: Element = (this._element as HTMLElement).offsetParent;

            while(positionParent !== null) {
                realNum += positionParent[offsetDir];
                positionParent = (positionParent as HTMLElement).offsetParent;
            }
            return realNum;
        }
        return 0;
    }
    
    get left () {
        return this._offset('left');
    }
    
    get top () {
        return this._offset('top');
    }
複製代碼

在jQuery中,動畫是依照計時器來計算節點屬性的,實現起來須要必定的代碼量,而項目應用環境也都是現代瀏覽器,因此動畫方面就使用css3來實現了。這裏有一個小技巧,若是須要實現重複性的動畫,好比popup效果,能夠寫兩個css,好比animate_0animate_1,再加上這樣的代碼就沒問題了:

this._animateIndex = 0;

applyAnimate () {
    let animateName = `animate_${this._animateIndex}`;
    this._element.removeClass(animateName);
    this._animateIndex = 1 - this._animateIndex;
    this._element.addClasss(animateName);
}
複製代碼

最後須要給節點添加一些事件偵聽,畢竟如今純展現性的項目不多,絕大多數的項目都須要與用戶產生交互,方法以下:

on (type, callback) {
        if (this._element instanceof HTMLElement) {
            if (this._element.addEventListener) {
                this._element.addEventListener(type, callback);
            } else if (this._element['attachEvent']) {
                this._element['attachEvent']('on' + type, callback);
            } else {
                this._element['on' + type] = callback;
            }
        }
    }
複製代碼

還有一個在交互中喜聞樂見的hover方法:

hover (inCallback, outCallback) {
        // mouseover和mouseout在進入子節點時也會觸發,這裏使用mouseenter和mouseleave
        this.on('mouseenter', inCallback);
        this.on('mouseleave', outCallback);
    }
複製代碼

節點基類到這裏,支持一般的項目開發已經足夠了,若是有一些特殊的節點類型,好比video之類,再自行擴展便可。

總結

在項目中用BaseElement構建dom節點,因爲使用的都是封裝好的方法,也可使全部dom節點的操做都有相對統一的入口,這自己就秉持着面向對象的思想。

也許這個基類的適用面並無很廣,但只要可以起到拋磚引玉的做用,那也是很好的結果了。

相關文章
相關標籤/搜索