JS Events
JS 设计模式

事件流 事件捕获 捕获阶段,事件依次传递的顺序是:window --> document --> html--> body --> 父元素、子元素、目标元素 事件冒泡 以下事件不冒泡:blur、focus、load、unload、onmouseenter、onmouseleave,可以通过event.bubbles检查。 阻止冒泡: element.onclick = function (event) { //阻止冒泡 event = event || window.event; if (event && event.stopPropagation) { event.stopPropagation(); } else { // < IE10 event.cancelBubble = true; } }; 事件委托 事件委托,是把一个元素响应事件(click、keydown......)的函数委托到另一个元素,如在父元素的响应函数中处理子元素的事件,事件委托利用了冒泡机制,减少了事件绑定的次数,减少内存消耗,提高性能。 focus、blur 之类的事件本身没有事件冒泡机制,所以无法委托。 mousemove、mouseout 这样的事件,虽然有事件冒泡,但是只能不断通过位置去计算定位,对性能消耗高,因此也是不适合于事件委托的。 // https://zhuanlan.zhihu.com/p/26536815 function eventDelegate(parentSelector, targetSelector, events, foo) { // 触发执行的函数 function triFunction(e) { // 兼容性处理 var event = e || window.event; // 获取到目标阶段指向的元素 var target = event.target || event.srcElement; // 获取到代理事件的函数 var currentTarget = event.currentTarget; // 处理 matches 的兼容性 if (!Element.prototype.matches) { Element.prototype.matches = Element.prototype.matchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.webkitMatchesSelector || function (s) { var matches = (this.document || this.ownerDocument).querySelectorAll( s ), i = matches.length; while (--i >= 0 && matches.item(i) !== this) {} return i > -1; }; } // 遍历外层并且匹配 while (target !== currentTarget) { // 判断是否匹配到我们所需要的元素上 if (target.matches(targetSelector)) { var sTarget = target; // 执行绑定的函数,注意 this foo.call(sTarget, Array.prototype.slice.call(arguments)); } target = target.parentNode; } } // 如果有多个事件的话需要全部一一绑定事件 events.split(".").forEach(function (evt) { // 多个父层元素的话也需要一一绑定 Array.prototype.slice .call(document.querySelectorAll(parentSelector)) .forEach(function ($p) { $p.addEventListener(evt, triFunction); }); }); } eventDelegate("#list",…
单例模式 前端面试指南:作用域和闭包 单例模式避免了重复实例化带来的内存开销。 // 单例模式 function Singleton() { this.data = "singleton"; } Singleton.getInstance = (function () { var instance; return function () { if (instance) { return instance; } else { instance = new Singleton(); return instance; } }; })(); var sa = Singleton.getInstance(); var sb = Singleton.getInstance(); console.log(sa === sb); // true console.log(sa.data); // 'singleton' References Learning JavaScript Design Patterns
JS 面向对象编程
JS 模块化

对象创建 {}对象字面量 工厂模式 /* * 使用工厂方法创建对象 * 通过该方法可以大批量的创建对象 */ function createPerson(name, age, gender) { //创建一个新的对象 var obj = new Object(); //向对象中添加属性 obj.name = name; obj.age = age; obj.gender = gender; obj.sayName = function () { alert(this.name); }; //将新的对象返回 return obj; } var obj2 = createPerson("猪八戒", 28, "男"); var obj3 = createPerson("白骨精", 16, "女"); var obj4 = createPerson("蜘蛛精", 18, "女"); 构造函数 构造函数需要使用 new 关键字来调用才有意义 以构造函数的形式调用时,this 指向新创建的实例对象 静态成员是构造函数的属性和方法,通过构造函数访问:Person.prototype 实例成员是实例化对象的属性和方法,通过实例化对象访问 // 创建一个构造函数,专门用来创建Person对象 function Person(name, age, gender) { this.name = name; this.age = age; this.gender = gender; this.sayName = function () { alert(this.name); }; } var per = new Person("孙悟空", 18, "男"); var per2 = new Person("玉兔精", 16, "女"); var per3 = new Person("奔波霸", 38, "男"); // 创建一个构造函数,专门用来创建 Dog 对象 function Dog() {} var dog = new Dog(); 浅拷贝、深拷贝 浅拷贝的推荐实现:Object.assign 原型和原型链 __proto__ 是非标准属性,建议使用 ES6 新增的 Reflect.getPrototypeOf 和 Object.setPrototypeOf Function.__proto__等于Function.prototype 处理原型链污染 检测constructor、__proto__这些敏感键值 使用Object.create(null)创建原型为null的对象,保证对原型的修改无效 使用Object.freeze()冻结对象 [ ] 建立 JSON schema ,在解析用户输入内容时,通过 JSON schema 过滤敏感键名 规避不安全的递归性合并 原型链继承 原型链继承->组合继承->寄生组合继承。 原型链继承 通过修改子类构造函数原型为父类构造函数实例实现。 存在的问题: 在创建子类实例的时候,不能向超类型的构造函数中传递参数 这样创建的子类原型会包含父类的实例属性,造成引用类型属性同步修改的问题 组合继承 使用call在子类构造函数中调用父类构造函数解决原型链继承的问题。 存在的问题: 父类的构造函数被调用了两次(创建子类原型时调用了一次,创建子类实例时又调用了一次),导致子类原型上会存在父类实例属性,浪费内存 寄生组合继承 使用 Object.create(Parent.prototype) 创建一个新的原型对象赋予子类从而解决组合继承的缺陷。 // 寄生组合继承实现 function Parent(value) { this.value = value; } Parent.prototype.getValue =…
Closure 闭包:有权访问另一个函数作用域中变量的函数。 一个作用域可以访问另外一个函数内部的局部变量,就产生闭包,局部变量在函数执行完后不会被立即销毁,而是等所有函数调用完该变量后再销毁。 闭包的主要作用:延伸变量的作用范围。 过度使用闭包会造成内存泄漏。 应用 模拟类私有属性 // 模拟私有属性 function getGeneratorFunc() { var _name = "John"; var _age = 22; return function () { return { getName: function () { return _name; }, getAge: function () { return _age; }, }; }; } var obj = getGeneratorFunc()(); obj.getName(); // John obj.getAge(); // 22 obj._age; // undefined 柯里化(currying) 柯里化(currying),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。 柯里化的优势之一就是参数的复用,它可以在传入参数的基础上生成另一个全新的函数,如函数bind方法的实现。 // bind Function.prototype.myBind = function (context = window) { if (typeof this !== "function") throw new Error("Error"); let selfFunc = this; let args = [...arguments].slice(1); return function F() { // 因为返回了一个函数,可以 new F(),所以需要判断 if (this instanceof F) { return new selfFunc(...args, arguments); } else { // bind 可以实现类似这样的代码 f.bind(obj, 1)(2),所以需要将两边的参数拼接起来 return selfFunc.apply(context, args.concat(arguments)); } }; }; // Example function typeOf(value) { return function (obj) { const toString = Object.prototype.toString; const map = { "[object Boolean]": "boolean", "[object Number]": "number", "[object String]": "string", "[object Function]": "function", "[object Array]": "array", "[object Date]": "date", "[object RegExp]": "regExp", "[object Undefined]": "undefined", "[object Null]": "null", "[object Object]": "object", }; return map[toString.call(obj)] === value; }; } var isNumber = typeOf("number"); var isFunction…
JS Engine
JS Tips
JS 数组
JS 日期格式化
JS Function
JavaScript MD5 function
JS 引擎加载脚本文件后:语法分析、预编译、解释执行。 匿名函数不参与预编译,只有在解释执行阶段才会进行变量初始化。 JS 执行线程 javascript 引擎执行的过程的理解--执行阶段 JS 是单线程的是指永远只有 JS 引擎线程在执行 JS 脚本程序,其他的三个线程只协助,不参与代码解析与执行。 JS 引擎线程:也称为 JS 内核,负责解析执行 Javascript 脚本程序的主线程(例如 V8 引擎)。 事件触发线程:归属于浏览器内核进程,不受 JS 引擎线程控制。主要用于控制事件(例如鼠标,键盘等事件),当该事件被触发时候,事件触发线程就会把该事件的处理函数推进事件队列,等待 JS 引擎线程执行。 定时器触发线程:主要控制计时器 setInterval 和延时器 setTimeout,用于定时器的计时,计时完毕,满足定时器的触发条件,则将定时器的处理函数推进事件队列中,等待 JS 引擎线程执行。(注:W3C 在 HTML 标准中规定 setTimeout 低于 4ms 的时间间隔算为 4ms。) HTTP 异步请求线程:通过 XMLHttpRequest 连接后,通过浏览器新开的一个线程,监控 readyState 状态变更时,如果设置了该状态的回调函数,则将该状态的处理函数推进事件队列中,等待 JS 引擎线程执行。 注:浏览器对同一域名请求的并发连接数是有限制的,Chrome 和 Firefox 限制数为 6 个,ie8 则为 10 个 JS 异步执行机制——Even Loop JS 宏任务、微内核 Tasks, microtasks, queues and schedules 内存管理 内存泄漏 可能造成内存泄漏的原因 闭包的使用 全局变量的无意创建 DOM 元素绑定事件未随 DOM 元素的移除而注销 内存泄漏排查 内存泄露的排查手段 内存泄漏的解决方案 使用严格模式,避免不经意间的全局变量泄露 关注 DOM 生命周期,在销毁阶段记得解绑相关事件,或者可以使用事件委托的手段统一处理事件,减少由于事件绑定带来的额外内存开销 避免过度使用闭包 引申 setTimeout 与 setInterval 区别 防抖和节流
任何变量,如果未经声明就赋值,此变量是属于 window 的属性,而且不会做变量提升。(注意,无论在哪个作用域内赋值) function foo() { var a = (b = 100); // a和b的区别 } defer and async The best thing to do to speed up your page loading when using scripts is to put them in the head, and add a defer attribute to your script tag. Efficiently load JavaScript with defer and async
Fundamental ECMAScript中new Array(len)的操作 判断 len 是否为合法数字(小于 2^32 - 1 的正整数),如果不是则抛出错误; 创建一个 JavaScript Array 实例; 将这个实例对象的 length 属性设置为入参的值; 但该数组此时并没有包含任何实际的元素,而且不能理所当然地认为它包含 len 个值为 undefined 的元素 More empty和undefined的区别 导致数组的map、some、filter、includes、for in、for of、findIndex、sort等方法的差异 稀疏数组、密集数组的互相转换 V8 访问对象有两种模式:字典模式 和 快速模式 JavaScript 之稀疏数组与密集数组 稀疏数组与密集数组 伪数组(ArrayLike) 按索引方式储存数据 length不会动态变化 伪数组的原型链中没有 Array.prototype,因此不具有push、forEach等方法 常见的如arguments、DOM children 元素集。 // 伪数组转真数组 Array.prototype.slice.call(ArrayLike); [].slice.call(ArrayLike); Array.from(ArrayLike); sort方法 默认按 Unicode 编码排序 自定义排序规则:return 大于 0 的值——元素交换位置,return 小于 0 的值——元素位置不变,return 等于 0 的值——不交换位置 // 冒泡排序 arr.sort((a, b) => a - b); forEach会改变原数组吗,map()会吗 :question: arr.reduce(function (previousValue, currentValue, currentIndex, arr) {}, initialValue); e.g. 统计元素出现的次数、找最大值等 清空数组 array.splice(0); //方式1:删除数组中所有项目 array.length = 0; //方式2:length属性可以赋值,在其它语言中length是只读 array = []; //方式3:推荐 join的应用 相比字符串拼接 由于字符串的不变性,str 拼接过多的话,性能差,且容易导致内存溢出(很多个 str 都堆放在栈里)
JS日期格式化转换方法 Date.prototype.format = function(fmt) { var o = { "M+" : this.getMonth()+1, //月份 "d+" : this.getDate(), //日 "h+" : this.getHours(), //小时 "m+" : this.getMinutes(), //分 "s+" : this.getSeconds(), //秒 "q+" : Math.floor((this.getMonth()+3)/3), //季度 "S" : this.getMilliseconds() //毫秒 }; if(/(y+)/.test(fmt)) { fmt=fmt.replace(RegExp.$1, (this.getFullYear()+"").substr(4 - RegExp.$1.length)); } for(var k in o) { if(new RegExp("("+ k +")").test(fmt)){ fmt = fmt.replace(RegExp.$1, (RegExp.$1.length==1) ? (o[k]) : (("00"+ o[k]).substr((""+ o[k]).length))); } } return fmt; } 龙恩0707 JS日期格式化转换方法 References Moment.js 轻量级的JavaScript时间库
函数声明的方式 function关键字 函数表达式(匿名函数)var 变量名 = function([形参1,形参2...形参N]){} 构造函数var 变量名/函数名 = new Function('形参1', '形参2', '函数体'); 函数调用 func() func.call() (function(){})(); new func() 事件调用 定时调用 函数类数组实参arguments 函数调用隐含传入上下文对象this和封装实参的对象arguments 在递归调用中用arguments.callee代替自身函数名可以接触函数体内代码与函数名的耦合,但会导致函数体内的this对象被更改,同时访问arguments是个很昂贵的操作,因为它是个很大的对象,每次递归调用时都需要重新创建,影响现代浏览器的性能,还会影响闭包。 函数预编译 函数预编译,发生在函数执行的前一刻。 JS 预编译、变量提升 创建 Active Object 对象,即执行期上下文。 寻找函数的形参和变量声明,将变量和形参名作为 AO 对象的属性名,值设定为 undefined. 将形参和实参相统一,即更改形参后的 undefined 为具体的形参值。 寻找函数中的函数声明,将函数名作为 AO 属性名,值为函数体。 JS 函数和变量声明提升 函数声明提升优先于变量声明 函数初始化也会提升 console.log(a); // [Function: a] var a = 1; console.log(a); // 1 function a() {} console.log(a); // 1 function b(a) { console.log(a); // [Function: a] var a = 2; console.log(a); // 2 function a() {} console.log(a); // 2 } b(3); this 指向 以函数的形式(包括普通函数、定时器函数、立即执行函数)调用时,this 的指向永远都是 window。 以方法的形式调用时,this 指向调用方法的那个对象 以构造函数的形式调用时,this 指向实例对象 以事件绑定函数的形式调用时,this 指向绑定事件的对象 使用 call 和 apply 调用时,this 指向指定的那个对象 箭头函数中 this 的指向会继承外层函数调用的 this 绑定(无论 this 绑定到什么) var name = "window"; var obj = { name: "obj", arrowFunc: () => { console.log(this, this.name); }, func: function () { console.log(this, this.name); }, }; function func() { console.log(this, this.name); } // Window "window" || Object "obj" || Window "window" func() || obj.func() || obj.arrowFunc(); call, apply, bind call func.call(thisArg, ...argArray); 调用一个函数,同时可以改变这个函数内部的 this 指向 实现继承 function Father(myName, myAge) { this.name = myName; this.age = myAge; }…
default 32bit const md5 = function (message, bit) { var sMessage = new String(message); function RotateLeft(lValue, iShiftBits) { return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits)); } function AddUnsigned(lX, lY) { var lX4, lY4, lX8, lY8, lResult; lX8 = (lX & 0x80000000); lY8 = (lY & 0x80000000); lX4 = (lX & 0x40000000); lY4 = (lY & 0x40000000); lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF); if (lX4 & lY4) return (lResult ^ 0x80000000 ^ lX8 ^ lY8); if (lX4 | lY4) { if (lResult & 0x40000000) return (lResult ^ 0xC0000000 ^ lX8 ^ lY8); else return (lResult ^ 0x40000000 ^ lX8 ^ lY8); } else return (lResult ^ lX8 ^ lY8); } function F(x, y, z) { return (x & y) | ((~x) & z); } function G(x, y, z) { return (x & z) | (y & (~z)); } function H(x, y, z) { return (x ^ y ^ z); } function I(x, y, z) { return (y ^ (x | (~z))); } function FF(a, b, c, d, x, s, ac)…