/**
* 函数节流
* @param {function} func 需要节流的函数
* @param {number} interval 函数的执行间隔(毫秒)
* @param {object} options { leading: true, tailing: false }
*/
function throttle(func, interval, options) {
let canRun = false, // 是否可以执行函数
lastInvokeTime = 0, // 函数上一次被执行的时刻(时间戳)
tid = null; // 定时器id,设置间隔过后再次执行一次函数
// leading和tailing不能同时为false
const { leading = true, tailing = false } = options || {};
const throttled = function throttled(...args) {
let now = Date.now();
if (!lastInvokeTime) {
if (leading) {
canRun = true;
}
} else {
if (now - lastInvokeTime >= interval) {
canRun = true;
}
}
if (canRun) {
if (tid) {
clearTimeout(tid);
tid = null;
}
canRun = false;
lastInvokeTime = now;
func.apply(this, args);
} else if (!tid && tailing) {
tid = setTimeout(function () {
func.apply(this, args);
lastInvokeTime = 0;
tid = null;
}, now - lastInvokeTime);
}
}
/**
* 取消最后一次的函数执行
*/
throttled.cancel = function cancel() {
lastInvokeTime = 0;
if (tid) {
clearTimeout(tid);
tid = null;
}
}
return throttled;
}
// test
let count = 0;
function increment() {
count += 1;
console.log((new Date().getSeconds()), count);
}
const incrementThrottled = throttle(increment, 3000);
while (count < 5) {
incrementThrottled();
}
// test result
// 47 1
// 50 2
// 53 3
// 56 4
// 59 5
-
实现函数节流
-
利用reduce方法实现map方法
/** * 利用reduce方法实现map方法 * @param {function} callback map回调函数 * @param {object} context callback执行时的this上下文对象 */ Array.prototype.mapImplementedByReduce = function (callback, context = null) { const reducer = function (accumulator, currentValue, index, array) { const result = callback.call(context, currentValue, index, array); accumulator.push(result); return accumulator; } return this.reduce(reducer, []); } /** * test */ const arr = [1, 3, 5]; const callback = function (number, index, array) { return Math.pow(number, 3); } console.log(arr.mapImplementedByReduce(callback)); // [ 1, 27, 125 ]
-
redux源代码阅读
Redux是一个常用的组件状态管理库,通过将应用的状态数据集中存储并且限制可以更改状态数据的路径,使得开发者可以更好的追踪以及预测应用状态的变化过程,Redux的源代码不到1000行,是一个学习源代码分析的不错对象.
在浏览器中通过
script
标签加载后,脚本会在全局作用域下添加Redux
变量.对应的源代码如下
/** * 先探测脚本所处的运行环境和加载机制,然后将redux的方法和属性挂载到对应的作用域 * 1. Node.js环境下和AMD加载机制下挂载到模块的导出对象 * 2. script标签加载挂载到window对象 */ (function (global, factory) { // Node.js环境 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : // 浏览器环境(通过AMD方式加载) typeof define === 'function' && define.amd ? define(['exports'], factory) : // 浏览器环境(通过script标签加载) (global = global || self, factory(global.Redux = {})); }(this, function (exports) { /* other code... */ exports.__DO_NOT_USE__ActionTypes = ActionTypes; exports.applyMiddleware = applyMiddleware; exports.bindActionCreators = bindActionCreators; exports.combineReducers = combineReducers; exports.compose = compose; exports.createStore = createStore; Object.defineProperty(exports, '__esModule', { value: true }); }));
__DO_NOT_USE__ActionTypes
-
js中的in操作符和Object.keys方法
in操作符
in
和typeof
一样,只是操作符,不是函数,它用来判断对象是否拥有某个属性,例如:console.log('alert' in window); // true console.log('xxoo' in window); // false
在js中,对象的属性查找和继承是基于原型链来实现的,可以使用
hasOwnProperty
方法判断一个属性是属于对象本身,还是属于它的上层原型:console.log(window.hasOwnProperty('alert')); // true console.log(([]).hasOwnProperty('slice')); // false
-
洗牌算法
洗牌算法,用于随机打乱一个有限列表内的元素顺序,目前公认最好的洗牌算法就是Fisher-Yates算法,使用JavaScript实现Fisher-Yates算法对一个数组进行顺序打乱的代码如下.
/** * Fisher-Yates算法 * 将数组元素的顺序原地打乱 * 时间复杂度O(N) * @param {Array<any>} 数组 */ function shuffle(arr) { for (let i = arr.length - 1; i > 0; i--) { const idx = Math.floor(Math.random() * (i + 1)); const tmp = arr[i]; arr[i] = arr[idx]; arr[idx] = tmp; } }
-
script标签的async和defer属性
首先,async和defer属性的作用都是指示浏览器加载外联脚本的具体方式,所以两个属性都必须用在外联脚本的script的标签上,否则无效.
<script src="./a.js" async></script> <script src="./b.js" defer></script>
不使用async和defer
正常情况下,script标签会阻塞浏览器对HTML文档的解析,遇到script标签,浏览器会遵循下列步骤:
下载脚本 → 运行脚本 → 继续解析后面的文档内容.async
-
js中的对象继承方式以及优劣对比分析
原型链继承
// 父类构造函数 function Parent() { this.name = 'John'; this.habits = ['basketball', 'cook', 'boxing']; } // 父类原型 Parent.prototype.sayHello = function sayHello() { console.log('hello, world'); } // 子类构造函数 function Child() { // @empty } // 将子类的原型指向父类的一个实例 Child.prototype = new Parent(); const child_1 = new Child(); console.log(child_1.name); // John console.log(child_2.habits); // ['basketball', 'cook', 'boxing'] const child_2 = new Child(); console.log(child_1.habits === child_2.habits); // true child_1.habits.push('running'); console.log(child_1.habits); // ['basketball', 'cook', 'boxing', 'running']
缺点:父类实例中的引用类型对象由所有的子类实例共享,只要其中一个子类实例对其进行了修改,便会影响其他子类实例,容易“牵一发动全身”,造成容易忽略的BUG.
构造函数继承
function Parent(name) { this.name = name; this.habits = ['boxing']; this.sayHello = function sayHello() { console.log('hello, world!'); } } function Child() { Parent.call(this, 'frank'); } const child_1 = new Child(); const child_2 = new Child(); child_1.habits.push('cooking'); console.log(child_1.habits); // ['boxing', 'cooking'] console.log(child_2.habits); // ['boxing'] console.log(child_1.sayHello === child_2.sayHello); // false