闭包(全是面试题)
闭包的概念及应用
什么是闭包?
简单来说,闭包就是函数嵌套函数,目的是为了访问一个函数中的局部变量,让这个局部变量可以全局访问,即使这个变量所在的函数被释放之后。
为什么不使用全局变量?
造成全局污染
闭包原理?
是利用了浏览器的垃圾回收机制,当被销毁的内容被垃圾回收机制清除的时候,发现这个内容正在被另一个函数所使用,则这个内容将永远不被回收,长驻内存,所以会造成内存泄露。
闭包的优缺点?
优点:可以让一个局部变量在全局访问
缺点:由于闭包的变量是始终保存在内存中,内存消耗会变大,导致网页性能问题。在IE浏览器中可能还会导致内存泄漏等问题;故在退出函数之前,将所有变量删除。
如何解决内存泄露的问题?
将使用完毕的变量赋值为null
闭包的应用
模拟私有变量
const User = (function(){
//定义私有变量_password
let _password = null;
class User{
constructor(username,password){
//初始化私有变量_password
_password = password
this.username = username;
}
login(){
console.log(this.username,_password);
}
}
return User;
})();
let user = new User('小明',123);
console.log(user.username); //小明
console.log(user.password); //undefined
console.log(user._password); //undefined
user.login(); //小明 123
防抖
在浏览器的各种事件中,有一些容易频繁触发的事件,比如scroll、resize、鼠标事件(比如 mousemove、mouseover)、键盘事件(keyup、keydown )等。频繁触发回调导致大量的计算会引发页面抖动甚至卡顿,影响浏览器性能。防抖和节流就是控制事件触发的频率的两种手段。
防抖的中心思想是:在某段时间内,不管你触发了多少次回调,我都只执行最后一次。
// fn是我们需要包装的事件回调, delay是每次推迟执行的等待时间
function debounce(fn, delay) {
// 定时器
let timer = null;
// 将debounce处理结果当作函数返回
return function () {
// 保留调用时的this上下文
let context = this
// 保留调用时传入的参数
let args = arguments
// 每次事件被触发时,都去清除之前的旧定时器
if(timer) {
clearTimeout(timer)
}
// 设立新定时器
timer = setTimeout(function () {
fn.apply(context, args)
}, delay)
}
}
// 用debounce来包装scroll的回调
window.onscroll = debounce(() => {
console.log('触发了滚动事件');
},1000)
节流
节流的中心思想是:在某段时间内,不管你触发了多少次回调,我都只认第一次,并在计时结束时给予响应,也就是隔一段时间执行一次。
// fn是我们需要包装的事件回调, interval是时间间隔的阈值
function throttle(fn, interval) {
// last为上一次触发回调的时间
let last = 0;
// 将throttle处理结果当作函数返回
return function () {
// 保留调用时的this上下文
let context = this
// 保留调用时传入的参数
let args = arguments
// 记录本次触发回调的时间
let now = new Date()
// 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
if (now - last >= interval) {
// 如果时间间隔大于我们设定的时间间隔阈值,则执行回调
last = now;
fn.apply(context, args);
}
}
}
// 用throttle来包装scroll的回调
window.onscroll = throttle(() => {
console.log('触发了滚动事件')
},1000);
柯理化函数
函数柯里化,就是可以将一个接受多个参数的函数分解成多个接收单个参数的函数的技术,直到接收的参数满足了原来所需的数量后,才执行原函数
//求和
const sumFn = (...args) => {
return args.reduce((a,b) =>{
return a + b;
})
};
//排序
const sortFn = (...args) => {
return args.sort((a,b) => a - b);
}
let currying = function(fn){
const args = []; //接收所有传递的参数
return function result(...rest){ //剩下的参数
if(rest.length === 0){
return fn(...args);
}else{
args.push(...rest);
return result;
}
}
}
console.log(currying(sumFn)(1)(2)(3)()); //6
console.log(currying(sumFn)(1,2)(3,4)(5)()); //15
console.log(currying(sumFn)(1)(2,3,4,5)(6)()); //21
console.log(currying(sortFn)(1)(3)(2)(6,4)(5)());
继承
借用(构造函数)继承
构造继承特点:
子类实例共享父类引用属性的问题
创建子类实例时,可以向父类传递参数
可以实现多继承(call 或 apply 多个父类对象)
构造继承缺点:
实例并不是父类的实例,只是子类的实例
只能继承父类的实例属性和方法,不能继承原型属性和方法
无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
原型继承
原型的特点:共享一个空间!!!!
原型及原型链:
所有的函数都有一个属性prototype,这个属性可以找到该函数后面对应的原型对象。
所有的对象都有一个属性__proto__,通过这个属性可以找到该对象后面对应的原型对象,原型对象通过__proto__这个属性可以找到当前原型对象的父级原型对象,一直找到顶级原型Object.prototype,再找,则找到null,这个过程称为原型链
所有原型对象都有一个属性constructor,通过这个属性可以找到当前原型对象对应的构造函数。
特点:
非常纯粹的继承关系,实例是子类的实例,也 是父类的实例
父类新增原型方法/原型属性,子类都能访问到
简单,易于实现
缺点:
要想为子类新增原型属性和方法,必须要在new 父类()这样的语句之后执行。要想为子类新增实例属性和方法,必须要在构造函数中添加
无法实现多继承
来自原型对象的所有属性被所有实例共享
组合继承
特点:
可以继承实例属性和方法,也可以继承原型属性和方法
既是子类的实例,也是父类的实例
不存在引用属性的共享问题
可传参
函数可复用
缺点:
调用了两次父类构造函数,生成了两份实例
ES6 的继承语法
class 子类名 extends 父类名{
constructor([参数]){
//调用父类的构造函数
super([参数]);
}
}