Underscore.js 源碼學習筆記(下)

上接 Underscore.js 源碼學習筆記(上)

 

=== 756 行開始 函數部分。php

 

var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) {
  if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
  var self = baseCreate(sourceFunc.prototype);
  var result = sourceFunc.apply(self, args);
  if (_.isObject(result)) return result;
  return self;
};

_.bind = restArguments(function(func, context, args) {
  if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
  var bound = restArguments(function(callArgs) {
    return executeBound(func, bound, context, this, args.concat(callArgs));
  });
  return bound;
});

 _.bind(func, context, args)  就是將 func 的 this 綁定到 context 而且預先傳入參數 args (柯里化)html

經過  args.concat(callArgs)  實現了柯里化前端

bound 是綁定 this 後的函數,func 是傳入的函數vue

 if (!(callingContext instanceof boundFunc))  若是 callingContext 不是 boundFunc 的實例 就經過 apply 實現指定函數運行的 thisnode

若是 callingContext 是 boundFunc 的實例,那意味着你多是這麼使用的git

function foo() {}
var bindFoo = _.bind(foo, context/*沒寫定義,隨便什麼東西*/);
var bindFooInstance = new bindFoo();

此時 bindFoo() 的 this 就是 bindFoo 的一個實例github

那麼 bindFooInstance 的 this 是應該綁定到 context 仍是 bindFoo 的實例仍是什麼呢?ajax

JavaScript 中 this 一共有四種綁定 默認綁定 < 隱式綁定 < 顯示綁定 < new綁定正則表達式

因此 這裏應該優先使用... foo 的實例express

思考一下嘛 若是是 ES5 中 new foo.bind(context) 是否是應該先使用 foo 的實例嘛 bound 只是一箇中間函數

而後就是判斷 foo 是否有返回值 有的話直接返回該值 不然返回 this 也是操做符 new 的規定

 

_.partial = restArguments(function(func, boundArgs) {
  var placeholder = _.partial.placeholder;
  var bound = function() {
    var position = 0, length = boundArgs.length;
    var args = Array(length);
    for (var i = 0; i < length; i++) {
      args[i] = boundArgs[i] === placeholder ? arguments[position++] : boundArgs[i];
    }
    while (position < arguments.length) args.push(arguments[position++]);
    return executeBound(func, bound, this, this, args);
  };
  return bound;
});

_.partial.placeholder = _;
// e.g.
function add(a, b) { return a + b; }
var addOne = _.partial(add, 1, _);
addOne(3); // 4

默認佔位符是 _ 先給函數指定部分參數 不指定的就用下劃線佔位 生成一個新的只須要填寫剩餘參數的函數

 

_.bindAll = restArguments(function(obj, keys) {
  keys = flatten(keys, false, false);
  var index = keys.length;
  if (index < 1) throw new Error('bindAll must be passed function names');
  while (index--) {
    var key = keys[index];
    obj[key] = _.bind(obj[key], obj);
  }
});
// e.g.
var obj = {
  name: 'xiaoming',
  age: '25',
  getName() {
    return this.name;
  },
  getAge() {
    return this.age;
  },
  sayHello() {
    return 'hello, I am ' + this.name + ' and i am ' + this.age + ' years old.';
  }
}
name = 'global name';
_.bindAll(obj, 'getName', 'getAge');
var getName = obj.getName, getAge = obj.getAge, sayHello = obj.sayHello;
getName();  // xiaoming
getAge();   // 25
sayHello(); // hello, I am global name and i am undefined years old.

把一個對象的指定方法綁定到該對象。keys 能夠是要綁定的函數數組或函數。

 

_.memoize = function(func, hasher) {
  var memoize = function(key) {
    var cache = memoize.cache;
    var address = '' + (hasher ? hasher.apply(this, arguments) : key);
    if (!has(cache, address)) cache[address] = func.apply(this, arguments);
    return cache[address];
  };
  memoize.cache = {};
  return memoize;
};

這個函數仍是簡單實用的,經過緩存一個變量 cache 當傳入相同的參數時直接返回上一次的結果便可。

hasher 是入參的哈希函數,來判斷屢次入參是否相同。若是不傳哈希函數的話,默認就用第一個參數判斷是否重複。因此若是入參不是隻有一個的話,記得傳 hasher 函數。

好比在計算斐波那契數列  fib(n) = fib(n - 1) + fib(n - 2) 能夠經過記憶化遞歸防止大量重複計算。

 

_.delay = restArguments(function(func, wait, args) {
  return setTimeout(function() {
    return func.apply(null, args);
  }, wait);
});

封裝了一個函數,每次調用時都要等待 wait 毫秒再執行。

 

_.defer = _.partial(_.delay, _, 1);

經過 _.defer 來執行函數 _.defer(log) 可使函數放到異步調用隊列中,防止一些奇怪的錯誤吧。(確實遇到了一些時候須要  setTimeout(()=>{...}, 0)  來執行函數纔有效的狀況,可是還不知道怎麼總結規律= =)

 

// 在指定時間間隔 wait 內只會被執行一次
// 在某一時間點 函數被執行 那麼以後 wait 時間內的調用都不會被當即執行 而是設置一個定時器等到間隔等於 wait 再執行
// 若是在計時器等待的時間又被調用了 那麼定時器將執行在等待時間內的最後一次調用
// options 有兩個字段可填  { leading: false } 或 { trailing: false }
// { leading: false } 表示調用時不會當即執行 而是等待 wait 毫秒以後執行
// { trailing: false } 表示執行以後的 wait 時間內的調用都忽略掉
// 不要同時設置這兩個字段
_.throttle = function(func, wait, options) {
  var timeout, context, args, result;
  var previous = 0;
  if (!options) options = {};

  // later 函數是定時器指定執行的函數 context, args 不是設置定時器時指定的 而是執行 later 時決定的
  var later = function() {
    // 若是 options.leading = false 的話就將 previous 設置爲 0 做爲標記 下一次執行 func 時就不會被當即執行了
    previous = options.leading === false ? 0 : _.now();
    timeout = null;
    result = func.apply(context, args);
    // 這裏判斷 !timeout 真的好迷啊...
    if (!timeout) context = args = null;
  };

  var throttled = function() {
    var now = _.now();
    // 若是沒有上一次調用 或者 以前的調用已經結束 且 leading = false 會設置 previous = 0
    // previous = 0 且 options.leading = false 說明上一次 func 執行完成 這次的 fun 不須要當即執行 等 wait ms 再執行
    if (!previous && options.leading === false) previous = now;
    // 根據當前時間和上一次調用的時間間隔與 wait 比較判斷
    var remaining = wait - (now - previous);
    context = this; // 注意每一次調用都會更新 context 和 args 而執行 later 用到的是這兩個參數
    args = arguments; // 也就是說設置定時器時對應的參數 不必定是執行對應的參數~
    // remaining <= 0 則證實距離上次調用間隔大於 wait 了 能夠被執行
    // 理論上 remaining > wait 不會存在 除非 now < previous 也就是系統時間出錯了(被修改了
    if (remaining <= 0 || remaining > wait) {
      // 當設置了 leading 是不會進入這個分支的= =
      // 刪除定時器 重置 previous 爲當前時間 並執行 func
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      previous = now;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    }
    // 不然若是有 timeout 證實隔一段已經設置一段時間後執行 再也不設置定時器

    // 間隔小於 wait 並且沒有 timeout 的話 就設置一個定時器 到指定時間間隔後再執行
    // 若是 options.trailing = false 則忽略此次調用 由於時間間隔在 timeout 以內
    else if (!timeout && options.trailing !== false) {
      // 設置了 trailing 不會進入這個分支
      timeout = setTimeout(later, remaining);
    }
    return result;
  };
  // 重置 throttled 的狀態 同時取消尚未執行的定時器
  throttled.cancel = function() {
    clearTimeout(timeout);
    previous = 0;
    timeout = context = args = null;
  };

  return throttled;
};
// e.g.
function log(sth) {
  console.log('===> ' + sth + '  ' + new Date().toLocaleTimeString());
}
var tLog = _.throttle(log, 1000);
// === start === 20:29:54
// ===> 1  20:29:54
// ===> 4  20:29:55
var tLog = _.throttle(log, 1000, { leading: false });
// === start === 20:30:15
// ===> 4  20:30:16
var tLog = _.throttle(log, 1000, { trailing: false });
// === start === 20:30:39
// ===> 1  20:30:39
// 不要同時設置 leading 和 trailing ~ 不然永遠都不會被執行
// var tLog = _.throttle(log, 1000, { leading: false, trailing: false });

console.log('=== start === ' + new Date().toLocaleTimeString());
tLog(1);
tLog(2);
tLog(3);
tLog(4);

經典的函數來了= =

被稱做節流函數 做用是在必定時間範圍內只會被調用一次 即便被屢次觸發

 

_.debounce = function(func, wait, immediate) {
  var timeout, result;

  var later = function(context, args) {
    timeout = null;
    if (args) result = func.apply(context, args);
  };

  var debounced = restArguments(function(args) {
    if (timeout) clearTimeout(timeout);
    if (immediate) {
      var callNow = !timeout;
      // 雖然有 timeout 可是這裏的 later 沒有傳參因此不會執行 func
      // 只是爲了標記以後的 wait 時間內都不會再執行函數
      // 若是等待的過程當中又被調用 那麼就從那個時間點開始再進行 wait 時間的不執行
      timeout = setTimeout(later, wait);
      if (callNow) result = func.apply(this, args);
    } else {
      timeout = _.delay(later, wait, this, args);
    }

    return result;
  });

  debounced.cancel = function() {
    clearTimeout(timeout);
    timeout = null;
  };

  return debounced;
};

debounce 防抖函數 只有當隔指定時間沒有重複調用該函數時纔會執行,可應用於輸入和頁面滑動等狀況

能夠分紅兩種狀況看 傳 immediate 和不傳 immediate

不傳 immediate 的話 就是調用後設置定時器 wait 秒以後執行 這中間又被調用 那麼從調用時刻開始從新計時

傳 immediate 表示第一次調用就會被執行 而後標記以後的 wait ms 內不會被執行 這中間又被調用 那麼從調用時刻開始從新計時

 

// _.partial(wrapper, func) 是預先給 wrapper 傳入參數 func
// 因此 _.wrap(func, wrapper) 就是 返回 wrapper 先傳入 func 後返回的函數
_.wrap = function(func, wrapper) {
  return _.partial(wrapper, func);
};
// e.g.
function func(name) {
  return 'hi ' + name;
}
function wrapper(func, ...args) {
  return func(args).toUpperCase();
}
var sayHi = _.wrap(func, wrapper);
sayHi('saber', 'kido'); // HI SABER,KIDO

 

_.compose = function() {
  var args = arguments;
  var start = args.length - 1;
  return function() {
    var i = start;
    // 從最後一個函數開始執行
    var result = args[start].apply(this, arguments);
    // 每個函數的入參是上一個函數的出參
    while (i--) result = args[i].call(this, result);
    return result;
  };
};
// e.g.
function getName(firstname, lastname) { return firstname + ' ' + lastname; }
function toUpperCase(str) { return str.toUpperCase(); }
function sayHi(str) { return 'Hi ' + str; }
_.compose(sayHi, toUpperCase, getName)('wenruo', 'duan'); // Hi WENRUO DUAN

我記得以前寫過這個函數啊= =可是沒找到 記憶出錯了

就是一個把一堆函數從右到左連起來執行的函數。函數式編程中很重要的函數。

 

_.after = function(times, func) {
  return function() {
    if (--times < 1) {
      return func.apply(this, arguments);
    }
  };
};
// e.g.
function ajax(url, fn) {
  console.log(`獲取 ${url} 資源...`);
  setTimeout(() => {
    console.log(`獲取 ${url} 資源完成`);
    fn();
  }, Math.random() * 1000);
}
function finish() {
  console.log('資源所有獲取完成 能夠進行下一步操做...');
}


var urls = ['urla', 'urlb', 'urlc'];
var finishWithAfter = _.after(urls.length, finish);

for (var i = 0; i < urls.length; i++) {
  ajax(urls[i], finishWithAfter);
}
// 獲取 urla 資源...
// 獲取 urlb 資源...
// 獲取 urlc 資源...
// 獲取 urla 資源完成
// 獲取 urlc 資源完成
// 獲取 urlb 資源完成
// 資源所有獲取完成 能夠進行下一步操做...

函數調用 times 遍纔會被執行

 

_.before = function(times, func) {
  var memo;
  return function() {
    if (--times > 0) {
      memo = func.apply(this, arguments);
    }
    if (times <= 1) func = null;
    return memo;
  };
};

// 調用前 times-1 次執行 以後每一次都返回以前的運行的值
var foo = _.before(3, _.identity);

console.log(foo(1)) // 1
console.log(foo(2)) // 2
console.log(foo(3)) // 2 (第 n 次開始調用再也不執行 func 直接返回上一次的結果
console.log(foo(4)) // 2

只有前 times-1 次執行傳入的函數 func 後面就直接返回上一次調用的值。

 

_.once = _.partial(_.before, 2);

就是隻有一次調用的時候會只執行,後面直接返回以前的值。

使用場景好比……單例模式?

 

_.restArguments = restArguments;

將 restArguments 函數導出。

 

969行===下面是對象相關的函數了

 

// Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed.
var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');
var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
  'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'];

var collectNonEnumProps = function(obj, keys) {
  var nonEnumIdx = nonEnumerableProps.length;
  var constructor = obj.constructor;
  var proto = _.isFunction(constructor) && constructor.prototype || ObjProto;

  // Constructor is a special case.
  var prop = 'constructor';
  if (has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);

  while (nonEnumIdx--) {
    prop = nonEnumerableProps[nonEnumIdx];
    if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
      keys.push(prop);
    }
  }
};

IE9一下瀏覽器有bug就是一些屬性重寫後 不能在 for ... in 中遍歷到,因此要單獨判斷。

 

_.keys = function(obj) {
  if (!_.isObject(obj)) return [];
  if (nativeKeys) return nativeKeys(obj);
  var keys = [];
  for (var key in obj) if (has(obj, key)) keys.push(key);
  // Ahem, IE < 9.
  if (hasEnumBug) collectNonEnumProps(obj, keys);
  return keys;
};

若是ES5的  Object.keys 存在就直接調用,不然經過 for..in 獲取全部的屬性。

 

_.allKeys = function(obj) {
  if (!_.isObject(obj)) return [];
  var keys = [];
  for (var key in obj) keys.push(key);
  // Ahem, IE < 9.
  if (hasEnumBug) collectNonEnumProps(obj, keys);
  return keys;
};

獲取對象的全部屬性,包括原型鏈上的。

 

_.values = function(obj) {
  var keys = _.keys(obj);
  var length = keys.length;
  var values = Array(length);
  for (var i = 0; i < length; i++) {
    values[i] = obj[keys[i]];
  }
  return values;
};

全部對象自有屬性的值的集合

 

_.mapObject = function(obj, iteratee, context) {
  iteratee = cb(iteratee, context);
  var keys = _.keys(obj),
      length = keys.length,
      results = {};
  for (var index = 0; index < length; index++) {
    var currentKey = keys[index];
    results[currentKey] = iteratee(obj[currentKey], currentKey, obj);
  }
  return results;
};
// e.g.
var _2camel = str => str.replace(/_(\w)/g, (item, letter) => letter.toUpperCase());
var obj = { first: 'mo_li_xiang_pian', second: 'yong_ren_zi_rao' };
_.mapObject(obj, _2camel); // { first: 'moLiXiangPian', second: 'yongRenZiRao' }

對對象中每個值執行 iteratee 函數,和 _.map 的區別是它返回的是對象。

 

_.pairs = function(obj) {
  var keys = _.keys(obj);
  var length = keys.length;
  var pairs = Array(length);
  for (var i = 0; i < length; i++) {
    pairs[i] = [keys[i], obj[keys[i]]];
  }
  return pairs;
};

返回一個數組,每一項都是鍵、值組成的數組。

 

_.invert = function(obj) {
  var result = {};
  var keys = _.keys(obj);
  for (var i = 0, length = keys.length; i < length; i++) {
    result[obj[keys[i]]] = keys[i];
  }
  return result;
};

對象的鍵值互換,值要變成建,因此確保值是可序列化的。

 

_.functions = _.methods = function(obj) {
  var names = [];
  for (var key in obj) {
    if (_.isFunction(obj[key])) names.push(key);
  }
  return names.sort();
};

對象中全部屬性值爲函數的屬性名的集合按照字典序排序後返回。

 

var createAssigner = function(keysFunc, defaults) { // [defaults] {Boolean}
  return function(obj) {
    var length = arguments.length;
    if (defaults) obj = Object(obj); //  把 obj 轉成對象
    if (length < 2 || obj == null) return obj;
    for (var index = 1; index < length; index++) {
      var source = arguments[index],
          keys = keysFunc(source), // keysFunc 是獲取對象指定的 key 集合的函數
          l = keys.length;
      for (var i = 0; i < l; i++) {
        var key = keys[i];
        // 若是設置 defaults 則只有在在當前對象沒有 key 屬性的時候 才添加 key 屬性
        // 不然就爲 obj 添加 key 屬性 存在就替換
        if (!defaults || obj[key] === void 0) obj[key] = source[key];
      }
    }
    return obj;
  };
};

_.extend = createAssigner(_.allKeys); 
// _.extend(obj, ...otherObjs)
// 把 otherObjs 上面的全部的屬性都添加到 obj 上 相同屬性後面會覆蓋前面的

_.extendOwn = _.assign = createAssigner(_.keys);
// _.extendOwn(obj, ...otherObjs)
// 把 otherObjs 上面的全部的自有屬性都添加到 obj 上 相同屬性後面會覆蓋前面的

_.defaults = createAssigner(_.allKeys, true);
// _.extend(obj, ...otherObjs)
// 對 otherObjs 上面的全部的屬性 若是 obj 不存在相同屬性名的話 就添加到 obj 上 相同屬性後面被忽略

擴展對象的一些函數。

 

var keyInObj = function(value, key, obj) {
  return key in obj;
};

_.pick = restArguments(function(obj, keys) {
  // 經過 restArguments 傳入的參數除了第一個都被合成了一個數組 keys
  var result = {}, iteratee = keys[0];
  if (obj == null) return result;
  if (_.isFunction(iteratee)) {
    // 若是 iteratee (keys[0]) 是一個函數
    // 能夠看作是 _.pick(obj, iteratee, context)
    // obj 中符合 iteratee(value, key, obj) 的鍵值對被返回
    if (keys.length > 1) iteratee = optimizeCb(iteratee, keys[1]);
    keys = _.allKeys(obj);
  } else {
    // 若是 iteratee (keys[0]) 不是函數
    // 將 keys 數組遞歸壓平 成爲一個新數組 keys
    // 對於 obj 中的屬性在 keys 中的鍵值對被返回
    iteratee = keyInObj;
    keys = flatten(keys, false, false);
    obj = Object(obj);
  }
  for (var i = 0, length = keys.length; i < length; i++) {
    var key = keys[i];
    var value = obj[key];
    if (iteratee(value, key, obj)) result[key] = value;
  }
  return result;
});

篩選對象中部分符合條件的屬性。

 

_.omit = restArguments(function(obj, keys) {
  var iteratee = keys[0], context;
  if (_.isFunction(iteratee)) {
    iteratee = _.negate(iteratee);
    if (keys.length > 1) context = keys[1];
  } else {
    keys = _.map(flatten(keys, false, false), String);
    iteratee = function(value, key) {
      return !_.contains(keys, key);
    };
  }
  return _.pick(obj, iteratee, context);
});

邏輯同上,至關於反向 pick 了。

 

_.create = function(prototype, props) {
  var result = baseCreate(prototype);
  if (props) _.extendOwn(result, props);
  return result;
};

給定原型和屬性建立一個對象。

 

_.clone = function(obj) {
  if (!_.isObject(obj)) return obj;
  return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};

淺克隆一個對象。

 

看到  _.tap 有點沒看懂,感受事情有點不簡單……因而向下翻到了 1621 行,看到這有一堆代碼……

首先一開始的時候 (42行) 咱們看過   _  的定義,_ 是一個函數,_(obj) 返回一個 _ 實例,該實例有一個 _wrapped 屬性是傳入的 obj 。

 

咱們上面的函數都是 _ 的屬性,因此 _(obj) 中是沒有這些屬性的(_.prototype 中的屬性才能被得到)

 

// chain 是一個函數 傳入一個對象 obj 返回一個下劃線的實例,該實例有一個 _wrapped 屬性爲 obj 同時有 _chain 屬性爲 true 標記此對象用於鏈式調用
_.chain = function(obj) {
  var instance = _(obj);
  instance._chain = true;
  return instance;
};

// 返回鏈式結果 若是當前實例就有 _chain 則將結果包裝成鏈式對象返回 不然就直接返回對象自己
var chainResult = function(instance, obj) {
  return instance._chain ? _(obj).chain() : obj;
};

// 將對象 obj 中的函數添加到 _.prototype
_.mixin = function(obj) {
  // 對於 obj 中每一爲函數的屬性
  _.each(_.functions(obj), function(name) {
    // 都將該屬性賦值給下劃線
    var func = _[name] = obj[name];
    // 同時在下劃線的原型鏈上掛這個函數 同時這個函數能夠支持鏈式調用
    _.prototype[name] = function() {
      var args = [this._wrapped];
      push.apply(args, arguments);
      // 將 this._wrapped 添加到 arguments 最前面傳入 func
      // 由於 this._wrapped 就是生成的一個下劃線實例的原始的值
      // func 運行的 this 是 _ 把 this._wrapped 也就是上一個鏈式函數的運行結果 傳入 func
      // 將 this 和 func 的返回值傳入 chainResult 
      // 若是 this 是一個鏈式對象(有 _chain 屬性)就繼續返回鏈式對象
      // 不然直接返回 obj
      return chainResult(this, func.apply(_, args));
    };
  });
  return _;
};

// Add all of the Underscore functions to the wrapper object.
// 將 _ 傳入 mixin
// 下劃線上每個函數都會被綁定到 _.prototype 這樣這些函數才能被實例訪問
_.mixin(_);

// Add all mutator Array functions to the wrapper.
// 把一些數組相關的函數也加到 _.prototype
_.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
  var method = ArrayProto[name];
  _.prototype[name] = function() {
    var obj = this._wrapped;
    method.apply(obj, arguments);
    if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
    return chainResult(this, obj);
  };
});

// Add all accessor Array functions to the wrapper.
_.each(['concat', 'join', 'slice'], function(name) {
  var method = ArrayProto[name];
  _.prototype[name] = function() {
    return chainResult(this, method.apply(this._wrapped, arguments));
  };
});

// 從一個含有鏈式的 _ 實例中獲取原本的值 
_.prototype.value = function() {
  return this._wrapped;
};

在 _.prototype 上添加一個函數,同時支持鏈式調用。驚歎於其實現的巧妙。

 

如今能夠繼續看 _.tap 做用就是插入一個鏈式調用中間,查看中間值。

_.tap = function(obj, interceptor) {
  interceptor(obj);
  return obj;
};
// e.g.
let obj = [1, 2, 3];
let interceptor = (x) => { console.log('中間值是:', x) }
let result = _(obj).chain().map(x => x * x).tap(interceptor).filter(x => x < 5).max().value();
//              [1,2,3]      [1,4,9]        打印中間值         [1,4]            取最大值 4
//  .value() 就是從 _ 實例 這裏是 { [Number: 4] _wrapped: 4, _chain: true } 獲取原本的數據
console.log(result);  
// 中間值是: [ 1, 4, 9 ]
// 4

經過例子能夠感覺的更清晰。 接下來_.isMatch 前面看過了,略。

 

// Internal recursive comparison function for `isEqual`.
var eq, deepEq;
eq = function(a, b, aStack, bStack) {
  // Identical objects are equal. `0 === -0`, but they aren't identical.
  // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
  if (a === b) return a !== 0 || 1 / a === 1 / b;
  // `null` or `undefined` only equal to itself (strict comparison).
  if (a == null || b == null) return false;
  // `NaN`s are equivalent, but non-reflexive.
  if (a !== a) return b !== b;
  // Exhaust primitive checks
  var type = typeof a;
  if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
  return deepEq(a, b, aStack, bStack);
};

// Internal recursive comparison function for `isEqual`.
deepEq = function(a, b, aStack, bStack) {
  // Unwrap any wrapped objects.
  if (a instanceof _) a = a._wrapped;
  if (b instanceof _) b = b._wrapped;
  // Compare `[[Class]]` names.
  var className = toString.call(a);
  if (className !== toString.call(b)) return false;
  switch (className) {
    // Strings, numbers, regular expressions, dates, and booleans are compared by value.
    case '[object RegExp]':
    // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
    case '[object String]':
      // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
      // equivalent to `new String("5")`.
      return '' + a === '' + b;
    case '[object Number]':
      // `NaN`s are equivalent, but non-reflexive.
      // Object(NaN) is equivalent to NaN.
      if (+a !== +a) return +b !== +b;
      // An `egal` comparison is performed for other numeric values.
      return +a === 0 ? 1 / +a === 1 / b : +a === +b;
    case '[object Date]':
    case '[object Boolean]':
      // Coerce dates and booleans to numeric primitive values. Dates are compared by their
      // millisecond representations. Note that invalid dates with millisecond representations
      // of `NaN` are not equivalent.
      return +a === +b;
    case '[object Symbol]':
      return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b);
  }

  var areArrays = className === '[object Array]';
  if (!areArrays) {
    // 若是不是數組也不是對象的話 其餘狀況都已經比較完了 因此必定是 false
    if (typeof a != 'object' || typeof b != 'object') return false;

    // Objects with different constructors are not equivalent, but `Object`s or `Array`s
    // from different frames are.
    // 若是都是自定義類型的實例 都有 constructor 的話 那麼構造函數必定要相等
    var aCtor = a.constructor, bCtor = b.constructor;
    if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor &&
                             _.isFunction(bCtor) && bCtor instanceof bCtor)
                        && ('constructor' in a && 'constructor' in b)) {
      return false;
    }
  }
  // Assume equality for cyclic structures. The algorithm for detecting cyclic
  // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.

  // Initializing stack of traversed objects.
  // It's done here since we only need them for objects and arrays comparison.
  // 比較 stack 是爲了防止對象的一個屬性是對象自己這種狀況
  // let obj = {}; obj.prop = obj;
  // 這種狀況下比較對象再比較對象的每個屬性 就會發生死循環
  // 因此比較到每個屬性的時候都要判斷和以前的對象有沒有相等的
  // 若是相等的話 就判斷另外一個對象是否是也這樣 來判斷兩個對象是否相等
  // 而不須要繼續比較下去了~ 是否是很巧妙~
  aStack = aStack || [];
  bStack = bStack || [];
  var length = aStack.length;
  while (length--) {
    // Linear search. Performance is inversely proportional to the number of
    // unique nested structures.
    if (aStack[length] === a) return bStack[length] === b;
  }

  // Add the first object to the stack of traversed objects.
  aStack.push(a);
  bStack.push(b);

  // Recursively compare objects and arrays.
  if (areArrays) {
    // 若是是數組的話 須要比較其每一項都相等
    // Compare array lengths to determine if a deep comparison is necessary.
    length = a.length;
    if (length !== b.length) return false;
    // Deep compare the contents, ignoring non-numeric properties.
    while (length--) {
      if (!eq(a[length], b[length], aStack, bStack)) return false;
    }
  } else {
    // 若是是對象的話 須要比較其每個鍵都相等 對應的值再深度比較
    // Deep compare objects.
    var keys = _.keys(a), key;
    length = keys.length;
    // Ensure that both objects contain the same number of properties before comparing deep equality.
    if (_.keys(b).length !== length) return false;
    while (length--) {
      // Deep compare each member
      key = keys[length];
      if (!(has(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
    }
  }
  // Remove the first object from the stack of traversed objects.
  // 討論一個爲何要出棧 這個有點像 dfs 哈
  // obj = { a: { a1: ... }, b: { b1: ... } }
  // 判斷屬性 a 的時候棧裏是 [obj] 而後判斷 a != obj
  // 接下來會遞歸判斷 a1 以及其下屬性
  // 到 a1 的時候 棧中元素爲 [obj, a]
  // 當屬性 a 被判斷徹底相等後 須要繼續比較 b 屬性
  // 當比較到 b 的時候 棧中應該是 [obj] 而不是 [obj, a]
  // a == b 不會形成死循環 咱們不須要對不是父子(或祖先)關係的屬性進行比較
  // 綜上 這裏須要出棧(大概沒講明白...反正我明白了...
  aStack.pop();
  bStack.pop();
  return true;
};

// Perform a deep comparison to check if two objects are equal.
_.isEqual = function(a, b) {
  return eq(a, b);
};

深度比較兩個對象是否相等。我已經開始偷懶了,英文有註釋的地方不想翻譯成中文了。

雖然很長,可是真的,考慮的很全面。

 

_.isEmpty = function(obj) {
  if (obj == null) return true;
  if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0;
  return _.keys(obj).length === 0;
};

判斷一個值是否爲空。爲 null、undefined、長度爲空的(類)數組、空字符串、沒有本身可枚舉屬性的對象。

 

_.isElement = function(obj) {
  return !!(obj && obj.nodeType === 1);
};

判斷一個值是不是 DOM 元素。

nodeType 屬性返回節點類型。

若是節點是一個元素節點,nodeType 屬性返回 1。

若是節點是屬性節點, nodeType 屬性返回 2。

若是節點是一個文本節點,nodeType 屬性返回 3。

若是節點是一個註釋節點,nodeType 屬性返回 8。

該屬性是隻讀的。

 

_.isArray = nativeIsArray || function(obj) {
  return toString.call(obj) === '[object Array]';
};

// Is a given variable an object?
_.isObject = function(obj) {
  var type = typeof obj;
  return type === 'function' || type === 'object' && !!obj;
};

isArray 判斷一個值是不是數組 

isObject 判斷對象是不是 object 或 function 注意判斷 null

 

_.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error', 'Symbol', 'Map', 'WeakMap', 'Set', 'WeakSet'], function(name) {
  _['is' + name] = function(obj) {
    return toString.call(obj) === '[object ' + name + ']';
  };
});

批量增長一些判斷類型的函數,邏輯和 isArray 同樣呀。Map WeakMap Set WeakSet 都是 ES6 新增的數據類型。WeakSet 和 WeakMap 都沒聽過。該補習一波了~~~

 

if (!_.isArguments(arguments)) {
  _.isArguments = function(obj) {
    return has(obj, 'callee');
  };
}

一開始看到的,這個文件就是一個大的IIFE因此會有 arguments ,在 IE 低版本有 bug 不能經過

Object.prototype.toString.apply(arguments) === '[object Arguments]'

來判斷。callee 是 arguments 對象的一個屬性。能夠經過該屬性來判斷。

都 8102 年了 放過 IE 很差嗎?Edge 都開始使用 Chromium 內核了~~~~

 

// Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8,
// IE 11 (#1621), Safari 8 (#1929), and PhantomJS (#2236).
var nodelist = root.document && root.document.childNodes;
if (typeof /./ != 'function' && typeof Int8Array != 'object' && typeof nodelist != 'function') {
  _.isFunction = function(obj) {
    return typeof obj == 'function' || false;
  };
}

優化 isFunction 由於在一些平臺會出現bug 看了下提到的 issue #1621 (https://github.com/jashkenas/underscore/issues/1621)也不是很明白……

反正我試了下 nodejs v8 和最新版 Chrome 都進入了這個分支……emmm無論了……

 

// Is a given object a finite number?
_.isFinite = function(obj) {
  return !_.isSymbol(obj) && isFinite(obj) && !isNaN(parseFloat(obj));
};

// Is the given value `NaN`?
_.isNaN = function(obj) {
  return _.isNumber(obj) && isNaN(obj);
};

// Is a given value a boolean?
_.isBoolean = function(obj) {
  return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
};

// Is a given value equal to null?
_.isNull = function(obj) {
  return obj === null;
};

// Is a given variable undefined?
_.isUndefined = function(obj) {
  return obj === void 0;
};

emmm 顯而易見了吧

 

_.has = function(obj, path) {
  if (!_.isArray(path)) {
    return has(obj, path);
  }
  var length = path.length;
  for (var i = 0; i < length; i++) {
    var key = path[i];
    if (obj == null || !hasOwnProperty.call(obj, key)) {
      return false;
    }
    obj = obj[key];
  }
  return !!length;
};
// e.g.
let obj = { a: { b: { c: 1 } } };
_.has(obj, ['a', 'b', 'c']); // true
_.has(obj, ['a', 'b', 'd']); // false
_.has(obj, []); // false

判斷一個對象是否有指定屬性,若是是數組則判斷嵌套屬性。空數組返回 false。和前面 deepGet 不一樣的是這裏有 hasOwnProperty 判斷是不是自有屬性。

 

=== 1390 行 下面是 Utility Functions 一些工具方法 勝利在望✌️

 

_.noConflict = function() {
  root._ = previousUnderscore;
  return this;
};

若是運行在瀏覽器等環境 不能直接導出變量 只能將 _ 賦值到全局變量 若是以前已經有變量叫作 _ 能夠經過  var underscore = _.noConflict();  得到_工具函數同時將 _ 賦值回原來的值。

 

_.identity = function(value) {
  return value;
};

是一個傳入什麼就返回什麼的函數。看起來好像沒什麼用,可是前面有用到噠,能夠做爲 map 等函數的默認 iteratee 

var a = [null, null, [1,2,3], null, [10, 12], null];
a.filter(_.identity)

參考 Stack Overflow 上面的一個找到的 >_<

 

_.constant = function(value) {
  return function() {
    return value;
  };
};
// e.g.
// api: image.fill( function(x, y) { return color })
image.fill( _.constant( black ) );

代碼不難 一樣讓人困惑的是用途,在 Stack Overflow 找到一個用法舉例。

 

_.noop = function(){};

返回一個空函數。能夠用在須要填寫函數但又不須要作任何操做的地方。

 

_.propertyOf = function(obj) {
  if (obj == null) {
    return function(){};
  }
  return function(path) {
    return !_.isArray(path) ? obj[path] : deepGet(obj, path);
  };
};

_.propertyOf 返回獲取指定對象屬性的方法。

 

_.times = function(n, iteratee, context) {
  var accum = Array(Math.max(0, n)); // n 不能小於 0
  iteratee = optimizeCb(iteratee, context, 1);
  for (var i = 0; i < n; i++) accum[i] = iteratee(i);
  return accum;
};
// e.g.
_.times(6, i => i * i); // [ 0, 1, 4, 9, 16, 25 ]
_.times(6, _.identity); // [ 0, 1, 2, 3, 4, 5 ]

運行一個函數 n 次來生成一個數組。每一次參數都是運行的次數,從 0 開始。

 

_.now = Date.now || function() {
  return new Date().getTime();
};

Date.now 是 ES5(仍是6)新增的,舊版本沒有,經過new Date().getTime()得到

 

// 一些 HTML 的轉義字符
var escapeMap = {
  '&': '&amp;',
  '<': '&lt;',
  '>': '&gt;',
  '"': '&quot;',
  "'": '&#x27;',
  '`': '&#x60;'
};
var unescapeMap = _.invert(escapeMap);

// Functions for escaping and unescaping strings to/from HTML interpolation.
var createEscaper = function(map) {
  // 以傳入 escapeMap 舉例
  var escaper = function(match) {
    // 返回對應的轉義後的字符串
    return map[match];
  };
  // 生成一個正則表達式用來匹配全部的須要轉義的字符 (?:&|<|>|"|'|`)
  // 正則表達式有兩種建立方式 經過 /.../ 字面量直接建立 或者經過 new RegExp(regStr) 建立
  // 這裏的 ?: 表示正則表達不捕獲分組 若是不添加這個的話 在 replace 中可以使用 $i 代替捕獲的分組
  // 好比
  // '2015-12-25'.replace(/(\d{4})-(\d{2})-(\d{2})/g,'$2/$3/$1'); --> "12/25/2015"
  // '2015-12-25'.replace(/(?:\d{4})-(\d{2})-(\d{2})/g,'$2/$3/$1'); -->  "25/$3/12"
  // 爲了防止 $1 變成捕獲的字符串這裏使用了 ?: (其實好像也用不到吧= =
  var source = '(?:' + _.keys(map).join('|') + ')';
  var testRegexp = RegExp(source); // 生成的正則表達式 /(?:&|<|>|"|'|`)/
  var replaceRegexp = RegExp(source, 'g'); // 生成的正則表達式 /(?:&|<|>|"|'|`)/g
  return function(string) {
    string = string == null ? '' : '' + string;
    return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
  };
};
_.escape = createEscaper(escapeMap);
_.unescape = createEscaper(unescapeMap);
// e.g.
_.escape('<html></html>') // &lt;html&gt;&lt;/html&gt;
_.unescape('&lt;html&gt;&lt;/html&gt;') // <html></html>

html實體字符的一些轉義和反轉義。

 

_.result = function(obj, path, fallback) {
  if (!_.isArray(path)) path = [path];
  var length = path.length;
  if (!length) {
    return _.isFunction(fallback) ? fallback.call(obj) : fallback;
  }
  for (var i = 0; i < length; i++) {
    var prop = obj == null ? void 0 : obj[path[i]];
    if (prop === void 0) {
      prop = fallback;
      i = length; // Ensure we don't continue iterating.
    }
    obj = _.isFunction(prop) ? prop.call(obj) : prop;
  }
  return obj;
};
// e.g.
_.result({ a: { b: 2 } }, ['a','d'], () => 'failed');   // failed
_.result({ a: { b: 2 } }, ['a','b'], () => 'failed');   // 2
_.result({ a: () => ({ b: 2 }) }, ['a','b'], 'failed'); // 2
_.result({ a: () => ({ b: 2 }) }, ['a','d'], 'failed'); // failed

又是一個看得莫名其妙的函數...

根據 path 獲取 obj 的屬性值,當獲取不到時就返回 fallback 的執行結果。當遇到屬性爲函數時就把 上一層對象做爲 this 傳入執行函數而後繼續向下查找。

 

var idCounter = 0;
_.uniqueId = function(prefix) {
  var id = ++idCounter + '';
  return prefix ? prefix + id : id;
};
// e.g.
_.uniqueId('DWR'); // DWR1
_.uniqueId('DWR'); // DWR2
_.uniqueId('XIA');   // XIA3

就是經過閉包 返回一個不斷遞增的 id

 

_.template 我以爲值得用單獨一篇博客來說 = = 但其實我都是胡謅的!

首先要理解一下這個函數的用法

學過 jsp 的同窗應該知道 jsp 中表達式能夠寫在 <%= %> 之間 而腳本能夠寫在 <% %> 在渲染的時候 會將腳本執行 表達式也會替換成實際值

這裏的用法和那個基本同樣

let template = `
<lable>用戶ID:</lable><span><%= userId %></span>
<lable>用戶名:</lable><span><%= username %></span>
<lable>用戶密碼:</lable><span><%- password %></span>
<% 
if (userId === 1) { console.log('管理員登陸...') }
else { console.log('普通用戶登陸...') } 
%>
`

let render = _.template(template);

render({userId: 1, username: '管理員', password: '<pwd>'});
/* render 返回:
<lable>用戶ID:</lable><span>1</span>
<lable>用戶名:</lable><span>管理員</span>
<lable>用戶密碼:</lable><span>&lt;pwd&gt;</span>
*/
// 同時控制檯打印: 管理員登陸...

前端三門語言中 只有 JavaScript 是圖靈完備語言,你覺得你寫的模板是 html 添加了一些數據、邏輯,實際上 html 並不能處理這些代碼

因此咱們須要使用 JS 來處理它。處理後在生成對應的 HTML

把模板先生成一個 render 函數 而後爲函數傳入數據 就能生成對應 html 了。

除了上面的基礎用法 咱們能夠自定義模板的語法 注意 key 要和 underscore 中定義的相等

默認是這樣的

_.templateSettings = {
evaluate: /<%([\s\S]+?)%>/g, // <% %> js腳本
interpolate: /<%=([\s\S]+?)%>/g, // <%= %> 表達式
escape: /<%-([\s\S]+?)%>/g // <%- %> 表達式 生成後對 html 字符進行轉義 如 < 轉義爲 &lt; 防止 XSS 攻擊
};

咱們能夠自定義

let settings = { interpolate: /{{([\s\S]+?)}}/ }

如今 Vue 不是很火嘛 用一下 Vue 的語法

let template = `
<div>歡迎{{ data }}登陸</div>
`;
let render = _.template(template, { interpolate: /{{([\s\S]+?)}}/, variable: 'data' });
render('OvO'); // <div>歡迎OvO登陸</div>

variable 指定了做用域 不指定時傳入 render 的參數爲 obj 的話 那麼插值中 prop 獲取到是 obj.prop 的值

variable 指定傳入 render 函數參數的名字


理解了用法 如今思考怎樣實現 若是讓你寫程序傳入一段 js 代碼輸出運行結果 你會怎麼辦

憋說寫一個解釋器 >_<

大概就兩種選擇 eval() 和 new Function() (原諒我學藝不精 還有其餘辦法嗎?)而 eval 只能運行一次 function 是生成一個函數 能夠運行屢次

生成的 render 有一個參數 source 是生成的函數字符串

這樣咱們能夠達到預編譯的效果 就像 vue 打包後的文件裏面是沒有 template 的 都是編譯好的 render 函數

爲何要預編譯?咱們應該不想每一次運行都 new Function 吧 這個效率低你們應該都知道。其次,動態生成的函數,debug 不方便。

咱們傳入字符串 但這個字符串中不僅有 js 代碼還有些不相關的字符串。因此須要使用正則表達式將其中的 js 代碼找出來,templateSettings 定義的就是這個正則表達式

若是是表達式就把運行結果和先後的字符串鏈接起來 若是是腳本就執行

具體看代碼就行了

// \s  匹配一個空白字符,包括空格、製表符、換頁符和換行符。
// \S  匹配一個非空白字符。
// 因此 \s\S 就是匹配全部字符 和 . 比起來它多匹配了換行
_.templateSettings = {
  evaluate: /<%([\s\S]+?)%>/g,      // <%  %>
  interpolate: /<%=([\s\S]+?)%>/g,  // <%= %>
  escape: /<%-([\s\S]+?)%>/g        // <%- %>
};

// 這是一個必定不會匹配的正則表達式
var noMatch = /(.)^/;

// 由於後面要拼接一個函數體 有些字符放到字符串須要被轉義 這裏定義了須要轉義的字符
// \u2028 和 \u2029 不知道是啥 不想查了= =
var escapes = {
  "'": "'",
  '\\': '\\',
  '\r': 'r',
  '\n': 'n',
  '\u2028': 'u2028',
  '\u2029': 'u2029'
};

var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g;

var escapeChar = function(match) {
  return '\\' + escapes[match];
};

_.template = function(text, settings, oldSettings) { // oldSettings 爲了向下兼容 能夠無視
  if (!settings && oldSettings) settings = oldSettings;
  // 能夠傳入 settings 要和 _.templateSettings 中屬性名相同來覆蓋 templateSettings
  settings = _.defaults({}, settings, _.templateSettings);

  // reg.source 返回正則表達式兩個斜槓之間的字符串 /\d+/g --> "\d+"
  // matcher 就是把三個正則連起來 /<%-([\s\S]+?)%>|<%=([\s\S]+?)%>|<%([\s\S]+?)%>|$/g
  // 加了一個 $ 表示匹配字符串結尾
  var matcher = RegExp([
    (settings.escape || noMatch).source,
    (settings.interpolate || noMatch).source,
    (settings.evaluate || noMatch).source
  ].join('|') + '|$', 'g');

  var index = 0;
  var source = "__p+='";
  // 假設傳入的 text 是 '<p><%=x+1%></p>'
  text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
    // 函數的參數分別是:
    // 匹配的字符串
    // 匹配的分組(有三個括號,因此有三個分組,分別表示 escape, interpolate, evaluate 匹配的表達式)
    // 匹配字符串的下標
    // 第一次匹配: "<p><%=x+1%></p>" 會和 interpolate: /<%=([\s\S]+?)%>/g 匹配 interpolate 的值爲 "x+1"
    // index = 0, offset 匹配的起始下標 就是截取字符串最前面未匹配的那一段
    // text.slice(index, offset) 就是 "<p>" 此時的 source 就是 "__p+='<p>"
    // replace(escapeRegExp, escapeChar) 的做用是:
    // source 拼接的是一個 '' 包裹的字符串 有些字符放到 ' ' 裏須要被轉義

    // 第二次匹配:匹配字符串("<p><%=x+1%></p>")結尾
    // text.slice(index, offset) 此時獲取的是 "</p>"
    // 拼接後 source 爲 "__p+='<p>'+\n((__t=(x+1))==null?'':__t)+\n'</p>"
    source += text.slice(index, offset).replace(escapeRegExp, escapeChar);
    index = offset + match.length; // 匹配的起始下標+匹配字符串長度 就是匹配字符串末尾的下標

    if (escape) {
      // ((__t = (_.escape(escape))) == null ? '' : __t)
      // _.escape 是將生成的表達式中的 html 字符進行轉義
      source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
    } else if (interpolate) {
      // ((__t = (interpolate)) == null ? '' : __t)
      source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
    } else if (evaluate) {
      // 前面的字符串加分號 同時執行該腳本
      source += "';\n" + evaluate + "\n__p+='";
    }
    // 第一次匹配後 interpolate 爲 "x+1"
    // 此時 source 是 "__p+='<p>'+\n((__t=(x+1))==null?'':__t)+\n'"
    // 第二次匹配 escape、interpolate、evaluate 都不存在 不會改變 source

    // Adobe VMs need the match returned to produce the correct offset.
    // 返回 match 只是爲了獲取正確的 offset 而替換後的 text 並無改變
    return match;
  });
  source += "';\n";

  // 若是沒有指定 settings.variable 就添加 with 指定做用域
  // 添加 with 以後 source 爲 "with(obj||{}){\n__p+='<p>'+\n((__t=(x+1))==null?'':__t)+\n'</p>\';\n}\n"
  if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';

  source = "var __t,__p='',__j=Array.prototype.join," +
    "print=function(){__p+=__j.call(arguments,'');};\n" +
    source + 'return __p;\n';
  // 最後生成的 source 爲
  // "var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};
  // with(obj||{}){
  // __p+='<p>'+
  // ((__t=(x+1))==null?'':__t)+
  // '</p>';\n}\nreturn __p;
  // "

  var render;
  try {
    // 傳入的參數1: settings.variable || obj
    // 傳入的參數2: _ 使用於能夠在插值中使用 _ 裏的函數
    // 函數體 source
    render = new Function(settings.variable || 'obj', '_', source);
    /* 生成函數 render
      function anonymous(obj, _) {
        var __t, __p = '',
          __j = Array.prototype.join,
          print = function() {
            __p += __j.call(arguments, '');
          };
        with(obj || {}) {
          __p += '<p>' +
            ((__t = (x + 1)) == null ? '' : __t) +
            '</p>';
        }
        return __p;
      }
    */
  } catch (e) {
    e.source = source;
    throw e;
  }

  var template = function(data) {
    return render.call(this, data, _);
  };

  // Provide the compiled source as a convenience for precompilation.
  var argument = settings.variable || 'obj';
  template.source = 'function(' + argument + '){\n' + source + '}';

  return template;
};

var template = _.template("<p><%=x+1%></p>");
template({x: 'void'}) // <p>void1</p>

儘管我看的只知其一;不知其二,可是仍是感受學到了好多。

 

再下面就是 OOP 的部分上面已經基本分析過了

_.prototype.value = function() {
  return this._wrapped;
};

_.prototype.valueOf = _.prototype.toJSON = _.prototype.value;

_.prototype.toString = function() {
  return String(this._wrapped);
};

重寫下劃線的實例的 valueOf 、 toJSON 和 toString 函數

 

if (typeof define == 'function' && define.amd) {
  define('underscore', [], function() {
    return _;
  });
}

AMD(異步模塊定義,Asynchronous Module Definition),這裏爲了兼容 amd 規範。

 

 

到此就把 下劃線 1693 行所有看完了。

其實這是我第二遍看,到此次才能說勉強看懂,第一次真的是一頭霧水。這期間看了點函數式編程的文章,也許有點幫助吧。

也開始理解了你們爲何說新手想閱讀源碼的話推薦這個,由於短、耦合度低、並且涉及到不少基礎知識。

總體看下來,executeBound、OOP部分 和 _.template 這三部分花了很長時間思考。固然抽絲剝繭後搞懂明白的感受,真的很爽呀哈哈哈哈哈

 

總之,完結撒花吧~

相關文章
相關標籤/搜索