javascript中的一些核心知識點以及須要注意的地方 javascript中的一些核心知識點以及須要注意的地方

原文: javascript中的一些核心知識點以及須要注意的地方

前言

近期瑣事甚多,這些事情的積累對知識體系的提高有好處,可是卻不能整理出來,也整理不出來javascript

好比說我最近研究的Hybrid在線聯調方案便過於依賴於業務,就算分享也不會有人讀懂,如果抽一點來分享又意義不大css

又拿最近作webapp view 轉場動畫研究,就是幾個demo不斷測試,感受沒有什麼可說的html

最後甚至對webapp中的History的處理方案也是有一些心得,一點方案,可是依舊難以整理成文,因而便開始文荒了前端

這個時候不妨便溫故知新吧,對javascript的一些老知識點進行整理回顧,以後有大動做再說吧!java

文中知識僅是我的積累總結,有誤請指出web

閉包

做用域鏈

閉包是javascript中一個重要知識點,也是javascript中一塊魔法,咱們在不熟悉他的狀況下可能便常用了,熟悉他了解他是從初級至中級的一個標誌windows

要真正瞭解閉包,就得從做用域鏈提及數組

javascript中,做用域鏈的做用是控制變量的訪問順序,僅此而已

首先,javascript在運行時須要一個環境,這個環境即是咱們所謂執行上下文(execution context瀏覽器

執行上下文決定了變量或者函數有權利訪問其它數據,每一個執行環境都有一個與之關聯的變量對象,用於存儲執行上下文中定義的變量或者函數閉包

通常狀況下咱們所處的全局執行上下文即是window對象,因此全局範圍內建立的全部對象所有是window的屬性或者方法

函數的變量對象通常是其活動對象(activation Object)

其次,javascript沒有塊級做用域的概念,可是每一個函數有本身的執行上下文,這個即是變相的塊級做用域

每執行一個函數時,函數的執行上下文會被推入一個上下文棧中,函數若執行結束,這個上下文棧便會被彈出,控制權變回以前的執行上下文

當代碼在執行上下文中執行時,變回建立一個做用域鏈,這個做用域鏈控制着執行上下文數據訪問順序

function test() {
  var a = 2;
  console.log(a);
}
var a = 1;
test();

在這裏便具備兩個執行上下文,一個是window,一個是test函數

首先,在test執行以前,咱們全局執行上下文已經存在,他即是window,這個時候咱們會有a與test在做用域最前端

執行test時候,造成test執行上下文,因而最前端的執行上下文變成了test,這個時候會先造成活動對象,包括arguments以及a

在console.log時,會訪問做用域鏈最近的a變量,也就是2,這個是打印出2的根本緣由,如果沒有做用域鏈這個順序就壞了

下面是test執行時候的圖示:

因此做用域鏈相關的知識點是:

① 控制變量訪問順序
② 執行上下文包含一個做用域鏈的指針
③ 該層函數外部有幾個函數,便會有幾個活動對象待處理,做用域鏈指針會指向其外部活動對象
④ 做用域鏈爲執行上下文時函數內部屬性,不要妄想去操做

閉包的造成

閉包的造成即是一個函數執行上下文中有一個變量被其內部函數使用了,而且這個內部函數被返回了,便造成了一個閉包

因爲函數調用後,外部臨時變量保存着內部的引用,執行時會造成內部上下文環境,內部的函數會包含外部的做用域鏈指向的變量對象,

這個時候就算外部執行環境消耗,因爲外部保存着外部函數的活動對象的引用,因此這個變量對象不會被消耗,這個是閉包產生的緣由

function test() {
  var a = 2;
  return function () {
    console.log(a);
  };
}
var b = test();
b();

這裏會造成三個執行環境,一個是全局的,一個是test的,一個是匿名函數(最終是b函數)的,咱們依舊從test執行時提及

當test函數執行時:

var b = test();

會造成一個執行上下文,執行上下文包含一個做用域鏈指針,而且會造成一個活動對象

這裏test的做用域鏈只是一個指針,他只是引用這個活動對象,執行結束後執行上下文會被釋放,做用域鏈也會消失,可是其活動對象未必會GC

在b執行時,其匿名函數的做用域鏈便指向了外部函數的活動對象,不要問他怎麼得到這個指針引用的,他就是知道,因而test的活動對象將一直被保存,直到b調用結束

這裏b執行的關係是:

經典例子

關於閉包有一個經典的例子,他即是for循環的例子:

function createFn() {
  var ret = [], i;
  for (i = 0; i < 10; i++) {
    ret[i] = function () {
      return i;
    };
  }
  return ret;
}
var fns = createFn();

這段代碼很是簡單,根據一個數組造成10個函數,每一個函數返回其索引值,這類應用在實際工做中會常常用到,只不過咱們須要的是其索引對應的數據,而不是簡單的索引了

這類會createFn執行時會有兩個執行環境,一個是本身的,一個是windows的,內部執行環境做用域鏈會指向一個活動對象

固然fns數組中任意一個函數執行時,其會使用到createFn的活動對象中的數據i,而該活動對象是被10個函數共用的,都是10,因此與預期不合

該問題的處理即是各自造成本身的閉包:

function createFn() {
  var ret = [], i;
  for (i = 0; i < 10; i++) {
    ret[i] = (function (i) {
      return function () {
        return i;
      };
    })(i);
  }
  return ret;
}
var fns = createFn();

這裏循環中會造成10個獨立的執行上下文,其中的10個活動對象的arguments都保存了外部i的獨立數據,而內部又造成一個閉包訪問當即執行函數的數據,因此數據正確了......

其它閉包

requireJS中的閉包

標準的requireJS來講都是一個AMD的模塊,好比:

define(function () {
  var add = function (x, y) {
    return x + y;
  };
  return {
    add: add
  };
});

咱們知道,requireJS每一次加載其模塊皆會被執行一次,而且只會執行一次,這個模塊會被requireJS所保存,因此這個匿名函數活動對象是不會被釋放的,且是惟一的

這個時候咱們不少組件即可以統一使用其功能便可,好比生成uuid什麼的......固然,這種不釋放的問題,也會致使heap值的提高,這個是否是有問題便須要各位去驗證了

webapp中的閉包

webapp通常會使用requireJS管理模塊,而內部又會造成許多view的實例,這個實例而且會保存下來,這樣也會致使不少函數的活動對象得不到釋放

一來二往之間,heap值會比傳統網站高,這個是webapp一塊比較頭疼的地方,須要慢慢優化

原型鏈

最初javascript沒有class的概念,咱們使用的類是以function模擬,繼承的實現手段通常依靠原型鏈,繼承的使用也是評價一個jser的重要指標

每一個函數都會包含一個原型對象prototype

原型對象prototype包含一個指向構造函數的指針constructor

實例對象包含一個內部屬性__proto__指針指向原型對象prototype

這是他們之間的三角關係:

(function () {
    var Person = function (name) {
        this.name = name;
    };
    //Person.prototype = {};//這句將影響十分具備constructor屬性
    Person.prototype.getName = function () {
        return this.name;
    };

    var Student = function (name, sex, id) {
        this.name = name || '無名氏';
        this.sex = sex || '不明';
        this.id = id || '未填'; //學號
    };
    //至關於將其prototype複製了一次,如果包含constructor的話將指向Person
    Student.prototype = new Person();
    Student.prototype.getId = function () {
        return this.id;
    }
    var y = new Person();
    var s = new Student;
    var s1 = y instanceof Person;
    var s2 = s instanceof Student;
    var s3 = s instanceof Person;
    var s4 = Student.prototype.constructor === Person;
    var s5 = Student.constructor === Person;
    var s6 = Student.constructor === Function;

    var s = '';
})();

通常形式的繼承方式如上,偶爾咱們會這樣幹:

Student.prototype = {}

可是這樣會致使prototype對象的constructor對象丟失,因此須要找回來,另一個問題是,這裏繼承須要執行父類的構造方法,這樣是有問題的

好比,父類的構造函數中有一些事件綁定什麼的與子類無關,便會致使該類繼承無用,因此不少時候咱們須要本身實現繼承,比較優雅的是prototype的作法,我這裏對其進行了必定改造

var arr = [];
var slice = arr.slice;

function create() {
  if (arguments.length == 0 || arguments.length > 2) throw '參數錯誤';

  var parent = null;
  //將參數轉換爲數組
  var properties = slice.call(arguments);

  //若是第一個參數爲類(function),那麼就將之取出
  if (typeof properties[0] === 'function')
    parent = properties.shift();
  properties = properties[0];

  function klass() {
    this.initialize.apply(this, arguments);
  }

  klass.superclass = parent;
  klass.subclasses = [];

  if (parent) {
    var subclass = function () { };
    subclass.prototype = parent.prototype;
    klass.prototype = new subclass;
    parent.subclasses.push(klass);
  }

  var ancestor = klass.superclass && klass.superclass.prototype;
  for (var k in properties) {
    var value = properties[k];

    //知足條件就重寫
    if (ancestor && typeof value == 'function') {
      var argslist = /^\s*function\s*\(([^\(\)]*?)\)\s*?\{/i.exec(value.toString())[1].replace(/\s/i, '').split(',');
      //只有在第一個參數爲$super狀況下才須要處理(是否具備重複方法須要用戶本身決定)
      if (argslist[0] === '$super' && ancestor[k]) {
        value = (function (methodName, fn) {
          return function () {
            var scope = this;
            var args = [function () {
              return ancestor[methodName].apply(scope, arguments);
            } ];
            return fn.apply(this, args.concat(slice.call(arguments)));
          };
        })(k, value);
      }
    }

    klass.prototype[k] = value;
  }

  if (!klass.prototype.initialize)
    klass.prototype.initialize = function () { };

  klass.prototype.constructor = klass;

  return klass;
}
View Code

首先,繼承時使用一個空構造函數實現,這樣不會執行原構造函數的實例方法,再規範化必須實現initialize方法,保留構造函數的入口,這類實現比較優雅,建議各位試試

javascript中的DOM事件

事件流

PS:javascript的事件一塊我說的夠多了,這裏再說一次吧......

javascript註冊dom事件的手段不少:

① 直接寫在dom標籤上,onclick的作法

② 在js中這樣寫:el.onclick = function

上述作法事實上是很差的,由於他們沒法屢次定義,也沒法註銷,更加不用說使用事件委託機制了

上述兩種作法的最終仍然是調用addEventListener方式進行註冊冒泡級別的事件,因而這裏又扯到了javascript事件的幾個階段

在DOM2級事件定義中規定事件包括三個階段,這個是現有DOM事件的基礎,這個一旦改變,前端DOM事件便須要重組
三個階段是事件事件捕獲階段、處於目標階段、冒泡階段
事件捕獲由最早接收到事件的元素往最裏面傳
事件冒泡由最具體元素往上傳至document

通常而言是先捕獲後冒泡,可是處於階段的事件執行只與註冊順序有關,好比:
每次點擊一個DOM時候咱們會先判斷是否處於事件階段,如果到了處於階段的話便不存在捕獲階段了
直接按照這個DOM的事件註冊順序執行,而後直接進入冒泡階段邏輯,其判斷的依舊是e.target與e.currentTarget是否相等

這個涉及到一個瀏覽器內建事件對象,咱們註冊事件方式多種多樣
除了addEventListener能夠註冊捕獲階段事件外,其他方式皆是最後調用addEventListener接口註冊冒泡級別事件
註冊的事件隊列會根據DOM樹所處位置進行排列,最早的是body,到最具體的元素
每次咱們點擊頁面一個區域便會先作判斷,是否處於當前階段,好比:
我當前就是點擊的是一個div,若是e.target==e.currentTarget,這個時候便會按註冊順序執行其事件,不會理會事件是捕獲仍是冒泡,而跳過捕獲流程,結束後會執行冒泡級別的事件,如果body上有冒泡點擊事件(沒有捕獲)也會觸發,以上即是DOM事件相關知識點

事件冒泡是事件委託實現的基石,咱們在頁面的每次點擊最終都會冒泡到其父元素,因此咱們在document處能夠捕捉到全部的事件,事件委託實現的核心知識點是解決如下問題:

① 咱們事件是綁定到document上面,那麼我怎麼知道我如今是點擊的什麼元素呢

② 就算我能根據e.target獲取當前點擊元素,可是我怎麼知道是哪一個元素具備事件呢

③ 就算我能根據selector肯定當前點擊的哪一個元素須要執行事件,可是我怎麼找獲得是哪一個事件呢

若是能解決以上問題的話,咱們後面的流程就比較簡單了

肯定當前元素使用 e.target便可,因此咱們問題以解決,其次便根據該節點搜索其父節點便可,發現父節點與傳入的選擇器有關便執行事件回調便可

這裏還須要從新e.currentTarget,不重寫所有會綁定至document,簡單實現:

var arr = [];
var slice = arr.slice;
var extend = function (src, obj) {
  var o = {};
  for (var k in src) {
    o[k] = src[k];
  }
  for (var k in obj) {
    o[k] = obj[k];
  }
  return o;
};

function delegate(selector, type, fn) {
  var callback = fn;

  var handler = function (e) {
    //選擇器找到的元素
    var selectorEl = document.querySelector(selector);
    //當前點擊元素
    var el = e.target;
    //肯定選擇器找到的元素是否包含當前點擊元素,若是包含就應該觸發事件
    /*************
    注意,此處只是簡單實現,實際應用會有許多判斷
    *************/
    if (selectorEl.contains(el)) {
      var evt = extend(e, { currentTarget: selectorEl });
      evt = [evt].concat(slice.call(arguments, 1));
      callback.apply(selectorEl, evt);
      var s = '';
    }
    var s = '';
  };

  document.addEventListener(type, handler, false);
}
View Code

事件委託因爲所有事件是綁定到document上的,因此會致使阻止冒泡失效,不少初學的同窗不知道,這裏要注意

事件模擬

事件模擬是dom事件的一種高級應用,通常狀況下用不到,可是一些極端狀況下他是解決實際問題的殺手鐗

事件模擬是javascript事件機制中至關有用的功能,理解事件模擬與善用事件模擬是判別一個前端的重要依據,因此各位必定要深刻理解

事件通常是由用戶操做觸發,其實javascript也是能夠觸發的,比較重要的是,javascript模擬的觸發遵循事件流機制!!!

意思就是,javascript觸發的事件與瀏覽器自己觸發實際上是同樣的,簡單模擬事件點擊:

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <style type="text/css">
         #p { width: 300px; height: 300px; padding: 10px;  border: 1px solid black; }
         #c { width: 100px; height: 100px; border: 1px solid red; }
    </style>
</head>
<body>
    <div id="p">
        parent
        <div id="c">
            child
        </div>
    </div>
    <script type="text/javascript">
        alert = function (msg) {
            console.log(msg);
        }

        var p = document.getElementById('p'),
        c = document.getElementById('c');
        c.addEventListener('click', function (e) {
            console.log(e);
            alert('子節點捕獲')
        }, true);
        c.addEventListener('click', function (e) {
            console.log(e);
            alert('子節點冒泡')
        }, false);

        p.addEventListener('click', function (e) {
            console.log(e);
            alert('父節點捕獲')
        }, true);

        p.addEventListener('click', function (e) {
            console.log(e);
            alert('父節點冒泡')
        }, false);

        document.addEventListener('keydown', function (e) {
            if (e.keyCode == '32') {
                var type = 'click'; //要觸發的事件類型
                var bubbles = true; //事件是否能夠冒泡
                var cancelable = true; //事件是否能夠阻止瀏覽器默認事件
                var view = document.defaultView; //與事件關聯的視圖,該屬性默認便可,無論
                var detail = 0;
                var screenX = 0;
                var screenY = 0;
                var clientX = 0;
                var clientY = 0;
                var ctrlKey = false; //是否按下ctrl
                var altKey = false; //是否按下alt
                var shiftKey = false;
                var metaKey = false;
                var button = 0; //表示按下哪個鼠標鍵
                var relatedTarget = 0; //模擬mousemove或者out時候用到,與事件相關的對象
                var event = document.createEvent('Events');
                event.myFlag = '葉小釵';
                event.initEvent(type, bubbles, cancelable, view, detail, screenX, screenY, clientX, clientY,
ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget);
                
                console.log(event);
                c.dispatchEvent(event);
            }
        }, false);
    </script>
</body>
</html>
View Code

模擬點擊事件是解決移動端點擊響應的基石,有興趣的同窗本身去研究下吧,我這裏很少說

延時執行

延時執行settimeout是javascript中的一道利器,不少時候一旦解決不了咱們便會使用settimeout,可是對settimeout的理解上,不少初學的朋友有必定誤區

初學的朋友通常認爲settimeout是在多少毫秒後便會被執行,事實上其後面的數據表明的是一個時間片,或者說是優先級,settimeout的回調會在主幹程序以後執行

好比:

var a = 0, b = 1;
setInterval(function () {
  a = 1;
}, 0)
while (1) {
  //...
  b++;
  if(a == 1)
    break;
}

如下代碼會致使瀏覽器假死,由於settimeout中的代碼永遠不會執行

settimeout真正的的用法是:

① 延時請求,減小沒必要要的請求

② 須要過多的操做dom結構時,爲了閉包瀏覽器假死,可使用settimeout

另外,zepto中有一段與settimeout有關的恥辱代碼,在模擬tap事件時候,zepto使用dom模擬click事件的方式實現了:

.on('touchend MSPointerUp pointerup', function(e){
  if((_isPointerType = isPointerEventType(e, 'up')) &&
    !isPrimaryTouch(e)) return
  cancelLongTap()

  // swipe
  if ((touch.x2 && Math.abs(touch.x1 - touch.x2) > 30) ||
      (touch.y2 && Math.abs(touch.y1 - touch.y2) > 30))

    swipeTimeout = setTimeout(function() {
      touch.el.trigger('swipe')
      touch.el.trigger('swipe' + (swipeDirection(touch.x1, touch.x2, touch.y1, touch.y2)))
      touch = {}
    }, 0)

  // normal tap
  else if ('last' in touch)
    if (deltaX < 30 && deltaY < 30) {
      tapTimeout = setTimeout(function() {

        var event = $.Event('tap')
        event.cancelTouch = cancelAll
        touch.el.trigger(event)

        if (touch.isDoubleTap) {
          if (touch.el) touch.el.trigger('doubleTap')
          touch = {}
        }
        else {
          touchTimeout = setTimeout(function(){
            touchTimeout = null
            if (touch.el) touch.el.trigger('singleTap')
            touch = {}
          }, 250)
        }
      }, 0)
    } else {
      touch = {}
    }
    deltaX = deltaY = 0
})
View Code

比較狗血的是,他在tap這裏使用了settimeout,致使了一個延時,這個延時效果直接的影響即是其event參數失效

也就是這裏,touchend時候傳入的event參數不會被tap事件用到,什麼e.preventDefault之類的操做便於tap無關了,此類實現至今未改

其它

localstorage

localstorage的使用在我廠webapp的應用中,達到了一個史無前例的高度,咱們驚奇的發現,其真實容量是:

localstorage 的最大限制按字符數來算,中英文都是最多500多萬個字符,webkit爲5242880個

因而不少時候,localstorage的濫用便會引起localstorage存儲失效,致使業務錯誤

而且localstorage的濫用還表如今存儲業務關鍵信息致使url對外不可用的狀況,因此使用localstorage的朋友要慎重!

其它

......

結語

今天咱們花了一點時間回顧了一些javascript的核心知識點,但願對各位有用,我這裏先撤退了,文中理解有誤請提出

相關文章
相關標籤/搜索