防抖和节流 防抖 触发高频事件后 n 秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 function now ( ) { return +new Date () } function debounce (func, wait = 50 , immediate = true ) { let timer, context, args const later = ( ) => setTimeout (() => { timer = null if (!immediate) { func.apply (context, args) context = args = null } }, wait) return function (...params ) { if (!timer) { timer = later () if (immediate) { func.apply (this , params) } else { context = this args = params } } else { clearTimeout (timer) timer = later () } } }
节流 高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 _.throttle = function (func, wait, options ) { var context, args, result; var timeout = null ; var previous = 0 ; if (!options) options = {}; var later = function ( ) { previous = options.leading === false ? 0 : _.now (); timeout = null ; result = func.apply (context, args); if (!timeout) context = args = null ; }; return function ( ) { var now = _.now (); if (!previous && options.leading === false ) previous = now; var remaining = wait - (now - previous); context = this ; args = arguments ; if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout (timeout); timeout = null ; } previous = now; result = func.apply (context, args); if (!timeout) context = args = null ; } else if (!timeout && options.trailing !== false ) { timeout = setTimeout (later, remaining); } return result; }; };
变量提升\函数提升 js 在正式执行之前先进行了一次预编译,在这个过程中,将变量声明和函数声明提升至当前作用域的最顶端,然后进行接下来的处理。
在 ES6 中,let
和 const
所声明的变量有暂时性死区,不存在变量提升;并且不能重复已存在的变量,声明变量的作用域是块级的。
注:同名函数和变量,在提升阶段,函数会覆盖变量,但是会被变量赋值之后覆盖。
作用域 表示变量或函数起作用的区域,指代了它们在什么样的上下文中执行,亦即上下文执行环境。
js 的函数作用域有全局作用域,函数作用域,块级作用域(ES6)。
作用域链 是由当前环境与上层环境的一系列作用域共同组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。
闭包 是指有权访问另一个函数作用域中变量的函数
如:
1 2 3 4 5 6 7 8 function A ( ) { const a = 1 ; return function B (b ) { return a + b; } } const b = A ();b (2 );
深入贯彻闭包思想,全面理解JS闭包形成过程
原型 对于所有对象(null
除外)在创建的时候就会与之关联另一个对象,这个对象就是原型,每一个对象都会从原型”继承”属性。
对于函数对象,除了 __proto__
属性之外,还有 prototype
属性,当一个函数被用作构造函数来创建实例时,该函数的 prototype
属性值将被作为原型赋值给所有对象实例(也就是设置实例的 __proto__
属性)。
1 2 3 4 5 6 7 8 9 10 11 12 function Person (name, age ) { this .name = name; this .age = age; } const person = new Person ('person' , 13 );console .log (person.__proto__ === Person .prototype ); console .log (Person .prototype .Constructor === Person ); console .log (person.Constructor === Person );
每个实例的 Constructor 属性指向它的构造函数。
每个实例的 proto 属性指向它的实例原型。
每个构造函数的原型的 Constructor 属性指向它的构造函数。
原型链 当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Object .prototype Function .prototype Object .Constructor === Function Object .__proto__ === Function .prototype Object .prototype .__proto__ === null Object .prototype .Constructor === Object Function .Constructor === Function Function .__proto__ === Function .prototype Function .prototype .__proto__ === Object .prototype Function .prototype .Constructor === Function
继承 new 运算符 new
运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。
new
关键字会进行如下的操作:
创建一个空的简单 JavaScript 对象(即 {});
链接该对象(即设置该对象的构造函数)到另一个对象 ;
将步骤1新创建的对象作为 this
的上下文 ;
如果该函数没有返回对象,则返回 this
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function new2 (Constructor, ...args ) { const obj = {}; Object .setPrototypeOf (obj, Constructor .prototype ); const result = Constructor .apply (obj, args); return typeof result === 'object' ? result : obj; } function new2 (Constructor, ...args ) { const obj = Object .create (Constructor .prototype ); const result = Constructor .apply (obj, args); return typeof result === 'object' ? result : obj; } function new2 ( ) { var obj = {}; var Constructor = [].shift .call (arguments ); obj.__proto__ = Constructor .prototype ; var result = Constructor .apply (obj, arguments ); return typeof result === 'object' ? result : obj; };
函数参数值传递 ECMAScript 中所有函数的参数都是按值传递的。
也就是说,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。
JavaScript深入之参数按值传递
this 指向问题 1.this
永远指向一个对象
2.this
的指向完全取决于函数调用的位置
Function.prototype.bind 能显示的将当前函数与指定的对象绑定,并返回一个新函数,这个新函数无论以什么样的方式调用,其 this
永远指向绑定的对象。
Function.prototype.call 和 Function.prototype.apply 使用一个指定的 this
值和单独给出的一个或多个参数来调用一个函数。
它们只有一个区别,就是 call()
方法接受的是一个参数列表,而 apply()
方法接受的是一个包含多个参数的数组。
不过使用 apply
和 call
的时候仍然需要注意,如果目录函数本身是一个绑定了 this
对象的函数,那 apply
和 call
不会像预期那样执行。
ES6箭头函数 箭头函数没有自己的 this
绑定。箭头函数中使用的 this
,其实是直接包含它的那个函数或函数表达式中的 this
。
实现 call、apply、bind call 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Function .prototype .call2 = function (context ) { if (context) context = context || window ; context.__fn__ = this ; var args = []; for (var i = 1 , len = arguments .length ; i < len; i++) { args.push ('arguments[' + i + ']' ) } var result = eval ('context.__fn__(' + args + ')' ); delete context.__fn__ return result } Function .prototype .call2 = function (context, ...args ) { context = context || window ; const fn = Symbol () context[fn] = this ; const result = context[fn](...args); delete context[fn]; return result; }
apply 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 Function .prototype .apply2 = function (context, arr ) { context = context || window ; context.fn = this ; var result; if (!arr) { result = context.fn (); } else { var args = []; for (var i = 0 , len = arr.length ; i < len; i++) { args.push ('arr[' + i + ']' ); } result = eval ('context.fn(' + args + ')' ) } delete context.fn return result; } Function .prototype .apply2 = function (context,args ) { context = context || window ; const fn = Symbol () context[fn] = this ; const result = context[fn](args); delete context[fn]; return result; }
bind 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 Function .prototype .bind = function (oThis ) { if (typeof this !== 'function' ) { throw new TypeError ('Function.prototype.bind - what is trying to be bound is not callable' ); } var aArgs = Array .prototype .slice .call (arguments , 1 ); var fToBind = this ; var fNOP = function ( ) { }; var fBound = function ( ) { var bindArgs = Array .prototype .slice .call (arguments ); return fToBind.apply (this instanceof fBound ? this : oThis, aArgs.concat (bindArgs)); }; if (this .prototype ) { fNOP.prototype = this .prototype ; } fBound.prototype = new fNOP (); return fBound; };
JavaScript深入之call和apply的模拟实现 JavaScript深入之bind的模拟实现
深拷贝、浅拷贝问题(immutable是怎么实现的?) 浅拷贝
Object.assign
还有一些注意的点是:
不会拷贝对象继承的属性
不可枚举的属性
属性的数据属性/访问器属性
可以拷贝 Symbol 类型
深拷贝
JSON.parse(JSON.stringify(obj))
通过 JSON.stringify
实现深拷贝有几点要注意:
拷贝的对象的值中如果有函数,undefined,symbol 则经过 JSON.stringify()
序列化后的 JSON 字符串中这个键值对会消失
无法拷贝不可枚举的属性,无法拷贝对象的原型链
拷贝 Date 引用类型会变成字符串
拷贝 RegExp 引用类型会变成空对象
对象中含有 NaN、Infinity 和 -Infinity,则序列化的结果会变成 null
无法拷贝对象的循环应用
实现深拷贝 1 2 3 4 5 6 7 8 9 10 11 12 13 function clone (source ) { const target = {}; for (const i in source) { if (source.hasOwnProperty (i)) { if (typeof source[i] === 'object' ) { target[i] = clone (source[i]); } else { target[i] = source[i]; } } } return target; }
但以上有几个问题。
没有对参数做检验
判断是否对象的逻辑不够严谨
没有考虑数组的兼容
源对象内部 循环引用
的问题
当对象中有 undefined
, functionn
, symbol
等数据类型的时,没有对其做处理
lodash深拷贝的实现
**深入探究Immutable.js的实现机制(一) **深入探究Immutable.js的实现机制(二)
Typed Array TypedArray
对象描述一个底层的二进制数据缓存区的一个类似数组(array-like)视图。
TypedArray
0.2 + 0.1 不等于 0.3 问题(浮点数精度) Javascript 使用 Number 类型表示数字(整数和浮点数),遵循 IEEE754
标准通过64位来表示一个数字。
在计算时,计算机无法直接对十进制的数字进行运算,所以先按照 IEEE754
的标准转化成相应的二进制,然后进行对阶运算。
1 2 3 4 5 6 // 0.1 和 0.2 都转化成二进制后再进行运算 0.00011001100110011001100110011001100110011001100110011010 + 0.0011001100110011001100110011001100110011001100110011010 = 0.0100110011001100110011001100110011001100110011001100111 // 转成十进制正好是 0.30000000000000004
精度损失可能出现在进制转化和对阶运算过程中,只要在这两步中产生了精度损失,计算结果就会出现偏差。
JavaScript 浮点数陷阱及解法
什么是 iterator ?for of ? 遍历器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令 for...of
循环,Iterator 接口主要供 for...of
消费。
Iterator 的遍历过程是这样的。
(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
(2)第一次调用指针对象的 next
方法,可以将指针指向数据结构的第一个成员。
(3)第二次调用指针对象的 next
方法,指针就指向数据结构的第二个成员。
(4)不断调用指针对象的 next
方法,直到它指向数据结构的结束位置。
一个数据结构只要部署了 Symbol.iterator
属性,就被视为具有 iterator 接口,就可以用 for...of
循环遍历它的成员。也就是说,for...of
循环内部调用的是数据结构的 Symbol.iterator
方法。
for...of
循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如 arguments
对象、DOM NodeList 对象)、Generator 对象,以及字符串。
Iterator 和 for…of 循环
es6 其他特性用过吗?(Class、Map、Set、Decorator 等分别考察) 阮一峰《ECMAScript6 入 门》
promise 实现原理(怎么实现 promise all、race、finally 等?) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 Promise .all = function (promiseFns ) { return new Promise ((resolve, reject ) => { let values = []; let count = 0 ; for (let [i, p] of promiseFns.entries ()) { this .resolve (p).then (res => { values[i] = res; count++; if (count === promiseFns.length ) resolve (values) }, err => { reject (err) }) } }) } Promise .race = function (promiseFns ) { return new Promise ((resolve, reject ) => { for (let p of promiseFns) { this .resolve (p).then (res => { resolve (res) }, err => { reject (err) }) } }) } Promise .prototype .finally = function (cb ) { return this .then ( value => Promise .resolve (cb ()).then (() => value), reason => Promise .resolve (cb ()).then (() => { throw reason }) ) }
剖析Promise内部结构
Generator 又是什么? 是 ES6 提供的一种异步编程解决方案,执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
async await 知识点(await 的作用,async 返回的是什么) async
函数是 Generator 函数的语法糖。
异步变成解决方案,以同步方式写异步代码。
async
函数对 Generator 函数的改进,体现在以下四点。
内置执行器
更好的语义
更广的适用性
async 返回值是 Promise
event loop 参考
输入 URL,浏览器的执行过程又是怎么样的? 参考
v8 垃圾回收机制 参考
堆、栈、队列是什么?都有什么区别?有什么应用? 在 JS 中,栈内存一般储存基础数据类型,堆内存一般储存引用数据类型。
对象放在 heap(堆)里,常见的基础类型和函数放在 stack(栈)里,函数执行的时候在栈里执行。
队列是先进先出的一种数据结构。栈和队列都可以用数组模拟。
栈:
存储基础数据类型
按值访问
存储的值大小固定
由系统自动分配内存空间
空间小,运行效率高
先进后出,后进先出
堆:
存储引用数据类型
按引用访问
存储的值大小不定,可动态调整
主要用来存放对象
空间大,但是运行效率相对较低
无序存储,可根据引用直接获取