Underscore.js 源码学习笔记(下)

上接 Underscore.js 源码学习笔记(上)

 

=== 756 行开始 函数部分。

 

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 (柯里化)

通过  args.concat(callArgs)  实现了柯里化

bound 是绑定 this 后的函数,func 是传入的函数

 if (!(callingContext instanceof boundFunc))  如果 callingContext 不是 boundFunc 的实例 就通过 apply 实现指定函数运行的 this

如果 callingContext 是 boundFunc 的实例,那意味着你可能是这么使用的

function foo() {}
var bindFoo = _.bind(foo, context/*没写定义,随便什么东西*/);
var bindFooInstance = new bindFoo();

此时 bindFoo() 的 this 就是 bindFoo 的一个实例

那么 bindFooInstance 的 this 是应该绑定到 context 还是 bindFoo 的实例还是什么呢?

JavaScript 中 this 一共有四种绑定 默认绑定 < 隐式绑定 < 显示绑定 < new绑定

所以 这里应该优先使用... foo 的实例

思考一下嘛 如果是 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 为当前时间 并执行 funcif (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 传入的参数除了第一个都被合成了一个数组 keysvar 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 属性)就继续返回链式对象// 否则直接返回 objreturn 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 checksvar 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) {// 如果不是数组也不是对象的话 其他情况都已经比较完了 所以一定是 falseif (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 memberkey = 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 不能小于 0iteratee = 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'); // 生成的正则表达式 /(?:&|<|>|"|'|`)/greturn 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 中属性名相同来覆盖 templateSettingssettings = _.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: _ 使用于可以在插值中使用 _ 里的函数// 函数体 sourcerender = new Function(settings.variable || 'obj', '_', source);/* 生成函数 renderfunction 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 这三部分花了很长时间思考。当然抽丝剥茧后搞懂明白的感觉,真的很爽呀哈哈哈哈哈

 

总之,完结撒花吧~

转载于:https://www.cnblogs.com/wenruo/p/10136541.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/249986.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

软件项目管理(四)

上一篇介绍了软件项目的需求分析和任务分解&#xff0c;即软件项目的范围计划&#xff0c;那么对于一个软件项目来说&#xff0c;我们还需要知道它需要多少时间多少成本&#xff0c;如何得到这些数据就是这次要解决的问题&#xff0c;即项目的成本计划 在项目未完成之前谁也不…

js中的时间与毫秒数互相转换

【1】js毫秒时间转换成日期时间var oldTime (new Date("2012/12/25 20:11:11")).getTime(); //得到毫秒数 //不是上面格式的时间需要转换//starttime 2012-12-25 20:17:24;starttime starttime.replace(new RegExp("-","gm"),"/");…

Spring Cloud Sleuth 中id的使用

Spring Cloud Sleuth采用的是Google的开源项目Dapper的专业术语。 Span&#xff1a;基本工作单元&#xff0c;发送一个远程调度任务 就会产生一个Span&#xff0c;Span是一个64位ID唯一标识的&#xff0c;Trace是用另一个64位ID唯一标识的&#xff0c;Span还有其他数据信息&…

软件项目管理(五)

在get到成本计划后&#xff0c;我们便要着手开始对项目的进度进行计划&#xff0c;即这次的核心计划之一进度计划。 进度计划的重要性&#xff1a;按时完成项目是项目经理最大的挑战之一&#xff0c;时间是项目规划中灵活性最小的因素&#xff0c;进度问题是项目冲突的主要原因…

BZOJ2301: [HAOI2011]Problem b(莫比乌斯反演)

Description 对于给出的n个询问&#xff0c;每次求有多少个数对(x,y)&#xff0c;满足a≤x≤b&#xff0c;c≤y≤d&#xff0c;且gcd(x,y) k&#xff0c;gcd(x,y)函数为x和y的最大公约数。Input 第一行一个整数n&#xff0c;接下来n行每行五个整数&#xff0c;分别表示a、b、c…

Js正则表达式数字或者带小数点的数字

function chk() {var patrn /^\d(\.\d)?$/;var result true;$("input[typetext]").each(function () {if (!patrn.exec(this.value)) {alert("请输入正确的数字&#xff01;");result false;}})return result;}转载于:https://www.cnblogs.com/smzd/p/…

FastJson/spring boot: json输出

1.引入FastJson依赖包 <!-- FastJson --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.15</version></dependency>pom.xml参考 <project xmlns"http://maven.apa…

safari 调试iPhone web页面

safari设置-打开Safari偏好者设置&#xff0c;选中“高级菜单”&#xff0c;在页面最下方看到“在菜单中显示开发菜单”的复选框&#xff0c;在复选框内打钩&#xff0c;这样设置完毕就能在Safari菜单中看到开发菜单了iPhone 设置-打开iPhone手机设置app 选择Safari&#xff0c…

new函数

使用new函数是另一种创建变量的方式。创建一个未命名的T类型变量&#xff0c;初始化为T类型的零值&#xff0c;并返回其地址。例如&#xff1a; p : new(int)使用new函数创建变量和取其地址的普通局部变量没有不同&#xff0c;只是不需要引入声明时的一个名字&#xff0c;有语法…

软件项目管理

目 录 前言 2 如何做业务调研&#xff1f; 2.1 调研工作如何组织&#xff1f; 2.2 调研准备阶段容易犯哪些错误&#xff1f; 2.3 调研准备阶段容易犯哪些错误&#xff1f;) 2.4 调研准备阶段容易犯哪些错误&#xff1f; 2.5 现场调研阶段容易犯哪些错误&#xff1f; 2.…

Python 列表元组字典集合

列表&#xff08;list&#xff09; 有序性&#xff0c;可存储任意类型的值通过偏移存取&#xff0c;支持索引来读取元素&#xff0c;第一个索引为0 &#xff0c;倒数第一个索引为-1可变性 &#xff0c;支持切片、合并、删除等操作可通过索引来向指定位置插入元素可通过pop()方法…

ios兼容问题

滑动卡顿&#xff1a; -webkit-overflow-scrolling:touch; 转载于:https://www.cnblogs.com/smzd/p/7891722.html

postgresql 高可用 etcd + patroni 之二 patroni

os: centos 7.4 postgresql: 9.6.9 etcd: 3.2.18 patroni: 1.4.4 patroni etcd 是在一个postgrsql 开源大会上 亚信的一个哥们讲解的高可用方案。 依然是基于 postgreql stream replication。 ip规划 192.168.56.101 node1 master 192.168.56.102 node2 slave 192.168.56.103 …

vue对象侦测

http://blog.csdn.net/yihanzhi/article/details/74200618 数组&#xff1a;this.$set(this.arr,index,value) 转载于:https://www.cnblogs.com/smzd/p/8390626.html

Laravel 5.4 migrate时报错: Specified key was too long error

Laravel 5.4默认使用utf8mb4字符编码&#xff0c;而不是之前的utf8编码。因此运行php artisan migrate 会出现如下错误&#xff1a; [Illuminate\Database\QueryException] SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key leng…

springboot工具类

ClassPathResource 在类路径下读取资源 public final String getPath() public boolean exists() public InputStream getInputStream() WebUtils 获取web资源工具类 public static String getRealPath(ServletContext servletContext, String path) public static Object g…

MySQL中事物的详解

1. 事物的定义及特性 事务是一组操作数据库的SQL语句组成的工作单元&#xff0c;该工作单元中所有操作要么同时成功&#xff0c;要么同时失败。事物有如下四个特性&#xff0c;ACID简称“酸性”。 1&#xff09;原子性&#xff1a;工作单元中所有的操作要么都成功&#xff0c;要…

记了老是忘记那就写下来吧宏任务微任务

宏任务&#xff1a;script 定时器 微任务&#xff1a;promiss process.nexttick new Promise(function(resolve){console.log(3);//此为同步程序resolve();//同步 是否异步 由内部函数决定console.log(4); }).then(function(){ //。then 异步console.log(5); });async function…

SPRING自定义注入CONTROLLER变量

问题描述 在SpringMVC中默认可以注入Model&#xff0c;ModelAndView&#xff0c;RequestParam&#xff0c;PathVariable 等&#xff0c;那么这个是怎么实现的&#xff0c;以及怎么注入一个自定义的参数呢 HandlerMethodArgumentResolver 在SpringMVC中有一个接口HandlerMethod…