let a = 100; typeof a;//number a = undefined; typeof a;//undefined a = null; typeof a;//object a instanceof Object;//false. instanceof 运算符检测 Object.prototype 是否存在于 a 的原型链上
5 == true;//false。true会先转换为1,false转换为0 '12' + 1;//'123'。数字先转换成字串 '12' - 1;//11。字串先转换成数字 [] == false;//true。数组转换为其他类型时,取决于另一个比较者的类型 [] == 0;//true [2] == '2';//true [2] == 2;//true [] - 2;//-2 12 + {a:1};//"12[object Object]"。这里对象先被转换成了字串 null == false;//false null == 0;//false null - 1;//-1
let str = '23abGdH4d4Kd'; str = str.replace(/([a-z]*)([A-Z]*)/g, function(match, $1 , $2){return $1.toUpperCase() + $2.toLowerCase()});//"23ABgDh4D4kD" str = 'web-application-development'; str = str.replace(/-([a-z])/g, (replace)=>replace[1].toUpperCase());//"webApplicationDevelopment"(驼峰转换)
str = Math.random().toString(36).substring(2)
3.2 / 1.7 | 0 = 1; ~~(3.2 / 1.7) = 1;//~~可还原原数 3.2 / 1.7 >> 0 = 1; 3.2 / 1.7 ^ 0 = 1; -3.2 / 1.7 & 1 = 1;//& 1操作无法保持符号不变 if (!~str.indexOf(substr)) {//~按位取反,等价于符号取反然后减一 //do some things }
let a = 2, b = 2.3; //方法一:Math.round() Math.round(a) === a;//true Math.round(b) === b;//false //方法二:模1运算 typeof a == "number" && (a % 1) === 0;//true typeof b == "number" && (b % 1) === 0;//false //方法三:ES6 Number.isInteger() Number.isInteger(a);//true Number.isInteger(b);//false //方法四:parseInt() parseInt(a, 10) === a;//true parseInt(b, 10) === b;//false //方法五:异或0运算 a ^ 0 === a;//true b ^ 0 === b;//false
true && 0;//0 false && 2;//false 1 && 2;//2 1 || 2;//1 0 || 1;//1 ![] || !{} || 3;//3 [] == ![];//true !NaN && !undefined && !null && !0 && !'' && 6;//6
//方法一:通过中间数组完成交换 let a = 1, b = 2; a = [b, b = a][0]; //方法二:通过加减运算完成交换 let a = 1, b = 2; a = a + b; b = a - b; a = a - b; //方法三:通过加减运算完成交换 let a = 1, b = 2; a = a - b; b = a + b; a = b - a; //方法四:通过两次异或还原完成交换。另外,这里不会产生溢出 let a = 1, b = 2; a = a ^ b; b = a ^ b; a = a ^ b; //方法五:通过乘法运算完成交换 let a = 1, b = 2; a = b + (b = a) * 0; //方法六:通过ES6解构赋值 let a = 1, b = 2; [a, b] = [b, a];
/、%、**运算符:
5 / 3;//1.6666666666666667 4.53 % 2;//0.5300000000000002 5 % 3;//2 2 ** 3;//8,相比于Math.pow(2,3)要简洁多了
//方法一:先排序后去重 Array.prototype.unique = function(){ this.sort(); //先排序 let res = [this[0]]; for(let i = 1; i < this.length; i++){ if(this[i] !== res[res.length - 1]){ res.push(this[i]); } } return res; } //方法二:利用对象属性唯一性避免重复元素 Array.prototype.unique = function(){ let res = [], deduplication = {}; for(let i = 0; i < this.length; i++){ if(!deduplication[this[i]]){ res.push(this[i]); deduplication[this[i]] = 1; } } return res; } //方法三:利用过滤函数递代去重处理,该方法可保持数组原序不变 Array.prototype.unique = function () { let self = this, res = [], target; while (self.length) { target = self.shift(); res.push(target); self = self.filter(item => target !== item); } return res; } //方法四:利用ES6新增加的Set数据类型做中间转换可去重 Array.prototype.unique = function () { return [...new Set(this)]; }
Math.max.apply(null, [2,5,30,1,20]);//30 Math.min.apply(null, [2,5,30,1,20]);//1 Math.min(...[2,5,30,1,20]);//1.ES6
1. encodeURI()对 URI 进行完整的编码,但不会对“;/?:@&=+$,#”这些在 URI 中具有特殊含义的 ASCII 标点符号进行编码,同时也不编码“- _ . ! ~ * ' ( )”这些ASCII字符; 2. encodeURIComponent()可把字符串作为 URI 组件进行编码,会对“;/?:@&=+$,#”这些在 URI 中具有特殊含义的 ASCII 标点符号进行编码,但不编码“- _ . ! ~ * ' ( )”这些ASCII字符。
浏览器的内核是多线程的,它们在内核控制下相互配合以保持同步,一个浏览器至少实现三个常驻线程:JavaScript引擎线程、GUI渲染线程、事件触发线程 .JavaScript引擎线程是基于事件驱动单线程执行的,JavaScript引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个JavaScript线程在运行JavaScript程序。 .GUI渲染线程负责渲染浏览器界面,当界面需要重绘(Repaint)或由于某种操作引发回流(Reflow)时,该线程就会执行。但需要注意,GUI渲染线程与JavaScript引擎是互斥的,当JavaScript引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JavaScript引擎空闲时立即被执行。 .事件触发线程在一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JavaScript引擎的处理。这些事件可来自JavaScript引擎当前执行的代码块如setTimeout、也可来自浏览器内核的其他线程如鼠标点击、Ajax异步请求等,但由于JavaScript的单线程关系所有这些事件都得排队等待JavaScript引擎处理(当线程中没有执行任何同步代码的前提下才会执行异步代码)。
1. setTimeout()和setInterval()共用一个编号池,clearTimeout()和 clearInterval() 在技术上可以互换;但是为了避免混淆,不要混用取消定时函数 2. 需要注意的是setTimeout()和setInterval()延迟执行的函数或代码中的this会指向一个错误的值,因为那些函数或代码是在一个分离的环境空间里被执行的,这些代码中包含的 this 关键字在非严格模式会指向 window (或全局)对象,严格模式下为 undefined 3. IE < 9环境下setTimeout() 或者 setInterval()都不支持传递额外的参数,可以考虑自行重新定义兼容代码同名取代这两个函数 4. setTimeout()的最小延迟时间与浏览器及操作系统有关,但不是0。在John Resig的《Javascript忍者的秘密》中:Browsers all have a 10ms minimum delay on OSX and a(approximately) 15ms delay on Windows.另外Firefox中定义的最小时间间隔(DOM_MIN_TIMEOUT_VALUE)是10毫秒,HTML5定义的最小时间间隔是4毫秒 5. 延迟只是表明可能的最小延迟时间,而不保证确切延迟时间。如果任务队列很长,耗时很多,最后延迟时间可能远远超出参数中指定的值
function ClassA(sColor) { this.color = sColor; } ClassA.prototype.sayColor = function () { alert(this.color); }; function ClassB(sColor, sName) { ClassA.call(this, sColor); this.name = sName; } ClassB.prototype = new ClassA(); ClassB.prototype.sayName = function () { alert(this.name); }; var objA = new ClassA("blue"); var objB = new ClassB("red", "John"); objA.sayColor(); //输出 "blue" objB.sayColor(); //输出 "red" objB.sayName(); //输出 "John"
var obj = { foo: 1, get bar() { return 2; } }; var copy = Object.assign({}, obj); // { foo: 1, bar: 2 } // copy.bar的值来自obj.bar的getter函数的返回值
call、apply和bind:call和apply的用途都是用来调用当前函数,且用法相似,它们的第一个参数都用作 this 对象,但其他参数call要分开列举,而apply要以一个数组形式传递;bind给当前函数定义预设参数后返回这个新的函数(初始化参数改造后的原函数拷贝),其中预设参数的第一个参数是this指定(当使用new
操作符调用新函数时,该this指定无效),新函数调用时传递的参数将位于预设参数之后与预设参数一起构成其全部参数,bind
最简单的用法是让一个函数不论怎么调用都有同样的 this
值。下边的list()也称偏函数(Partial Functions):
function list() { return Array.prototype.slice.call(arguments); } var list1 = list(1, 2, 3); // [1, 2, 3] // Create a function with a preset leading argument var leadingThirtysevenList = list.bind(undefined, 37); var list2 = leadingThirtysevenList(); // [37] var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]
Memoization技术: 替代函数中太多的递归调用,是一种可以缓存之前运算结果的技术,这样我们就不需要重新计算那些已经计算过的结果。In computing, memoization or memoisation is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again. Although related to caching, memoization refers to a specific case of this optimization, distinguishing it from forms of caching such as buffering or page replacement. In the context of some logic programming languages, memoization is also known as tabling.
function memoizer(fundamental, cache) { let cache = cache || {}, shell = function(arg) { if (! (arg in cache)) { cache[arg] = fundamental(shell, arg); } return cache[arg]; }; return shell; }
let singleton = function(){ let obj; return function(){ return obj || (obj = new Object()); } }();
let variables = { key1: 'value1', key2: 'value2' }, fnBody = 'return this.key1 + ":" + this.key2', fn = new Function(fnBody); console.log(fn.apply(variables));
var currying = function (fn) { var _args = []; return function () { if (arguments.length === 0) { return fn.apply(this, _args); } Array.prototype.push.apply(_args, [].slice.call(arguments)); return arguments.callee; } }; var add=function () { var total = 0; for (var i = 0, c; c = arguments[i++];) { total += c; } return total; }; var sum = currying(add); sum(100,200)(300);//ƒ () { ... } sum(400);//ƒ () { ... } sum();//1000
Function.prototype.uncurrying = function (){ var _this = this; return function (){ return Function.prototype.call.apply(_this,arguments); } }; function fn(){ console.log(`Hello ${this.value},${[].slice.call(arguments)}`); } var uncurryfn = fn.uncurrying(); uncurryfn({value:'World'},'Javascript'); //Hello World,Javascript
DocumentFragment: Roughly speaking, a DocumentFragment is a lightweight container that can hold DOM nodes. 在维护页面DOM树时,使用文档片段document fragments 通常会起到优化性能的作用
let ul = document.getElementsByTagName("ul")[0], docfrag = document.createDocumentFragment(); const browserList = [ "Internet Explorer", "Mozilla Firefox", "Safari", "Chrome", "Opera" ]; browserList.forEach((e) => { let li = document.createElement("li"); li.textContent = e; docfrag.appendChild(li); }); ul.appendChild(docfrag);
forEach()遍历:另外,适当时候也可以考虑使用for 或 for ... in 或 for ... of 语句结构
1. 数组实例遍历: arr.forEach(function(item, key){ //do some things }) 2. 非数组实例遍历: Array.prototype.forEach.call(obj, function(item, key){ //do some things }) 3. 非数组实例遍历: Array.from(document.body.childNodes[0].attributes).forEach(function(item, key){ //do some things. Array.from()是ES6新增加的 })
DOM事件流:Graphical representation of an event dispatched in a DOM tree using the DOM event flow.If the bubbles
attribute is set to false, the bubble phase will be skipped, and if stopPropagation()
has been called prior to the dispatch, all phases will be skipped.
(1) 事件捕捉(Capturing Phase):event通过target的祖先从window传播到目标的父节点。IE不支持Capturing
(2) 目标阶段(Target Phase):event到达event的target。如果事件类型指示事件不冒泡,则event在该阶段完成后将停止
(3) 事件冒泡(Bubbling Phase):event以相反的顺序在目标祖先中传播,从target的父节点开始,到window结束
事件绑定:
1. Tag事件属性绑定: 2. 元素Node方法属性绑定:btnNode.onclick = function(){//do some things} 3. 注册EventListener:btnNode.addEventListener('click', eventHandler, bubble);另外,IE下实现与W3C有点不同,btnNode.attachEvent(‘onclick’, eventHandler)
递归与栈溢出(Stack Overflow):递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误;而尾递归优化后,函数的调用栈会改写,只保留一个调用记录,但这两个变量(func.arguments、func.caller,严格模式“use strict”会禁用这两个变量,所以尾调用模式仅在严格模式下生效)就会失真。在正常模式下或者那些不支持该功能的环境中,采用“循环”替换“递归”,减少调用栈,就不会溢出
function Fibonacci (n) { if ( n <= 1 ) {return 1}; return Fibonacci(n - 1) + Fibonacci(n - 2); } Fibonacci(10); // 89 Fibonacci(50);// 20365011074,耗时10分钟左右才能出结果 Fibonacci(100);// 这里就一直出不了结果,也没有错误提示 function Fibonacci2 (n , ac1 = 1 , ac2 = 1) { if( n <= 1 ) {return ac2}; return Fibonacci2 (n - 1, ac2, ac1 + ac2); } Fibonacci2(100) // 573147844013817200000 Fibonacci2(1000) // 7.0330367711422765e+208 Fibonacci2(10000) // Infinity. "Uncaught RangeError:Maximum call stack size exceeded"
可见,经尾递归优化之后,性能明显提升。如果不能使用尾递归优化,可使用蹦床函数(trampoline)将递归转换为循环:蹦床函数中循环的作用是申请第三者函数来继续执行未完成的任务,而保证自己函数可以顺利退出。另外,这里的第三者和自己可能是同一函数定义
function trampoline(f) { while (f && f instanceof Function) { f = f(); } return f; } //改造Fibonacci2()的定义 function Fibonacci2 (n , ac1 = 1 , ac2 = 1) { if( n <= 1 ) {return ac2}; return Fibonacci2.bind(undefined, n - 1, ac2, ac1 + ac2); } trampoline(Fibonacci2(100));//573147844013817200000
预取与延迟加载:预取如提前加载图片,当用户需要查看时可直接从本地渲染,而不需要去服务端下载,它保证了图片快速、无缝地发布,让用户在浏览时获得更好的用户体验;延迟加载如先加载小图或示例图作为占位图,只有进入浏览器可视区域内时,才加载完整图片并显示出来,在页面图片数量多且比较大的时候非常有用
mousemove
事件,执行多次监听函数;但如果对监听函数用 100ms 的Debounce去弹跳后,那么浏览器只会在第 3.1s 的时候执行这个监听函数一次。下边是Debounce的实现:
/** * * @param fn {Function} 该函数定义了事件触发后的业务处理逻辑 * @param delay {Number} 延迟时间,单位是毫秒(ms) * * @return {Function} 目标事件将要触发执行的监听函数 */ function debounce(fn, delay) { // 定时器,用来 setTimeout let timer // 返回一个“去弹跳”了的监听函数 return function () { // 保存函数调用时的上下文和参数,传递给 fn let context = this, args = arguments; // 先清除定时器,保证 delay 毫秒内 fn 不执行多次 clearTimeout(timer) // 用户停止当前连续操作后的第 delay 毫秒执行 fn timer = setTimeout(function () { fn.apply(context, args) }, delay) } }
/** * * @param fn {Function} 该函数定义了事件触发后的业务处理逻辑 * @param delay {Number} 执行间隔,单位是毫秒(ms) * * @return {Function} 目标事件将要触发执行的监听函数 */ function throttle(fn, threshhold) { // 记录上次执行的时间 let last, // 定时器 timer; // 默认间隔为 250ms threshhold || (threshhold = 250); // 返回一个“经节流”了的监听函数 return function () { // 保存函数调用时的上下文和参数,传递给 fn let context = this, args = arguments, now = +new Date(); // 如果距离上次执行 fn 函数的时间小于 threshhold,那么就放弃 // 执行 fn,并重新计时 if (last && now < last + threshhold) { clearTimeout(timer) timer = setTimeout(function () { last = now fn.apply(context, args) }, threshhold) } else {// 在连续动作刚开始或超过threshhold间隔时立即执行 fn last = now fn.apply(context, args) } } }