单例模式
class SingleObject{
constructor(name) {
this.name = name
}
login() {
console.log('name: ', this.name)
}
}
SingleObject.getInstance = (() => {
let instance
return (name) => {
if (!instance) {
instance = new SingleObject(name)
}
return instance
}
})()
// 测试
const a = SingleObject.getInstance('yang')
const b = SingleObject.getInstance('guo')
console.log(a === b) // true
console.log(a.login(), b.login()) // yang
// getInstance是类的静态方法,不能使用new SingleObject()
// 创建出来的实例全等
观察者模式
// 目标对象
class Subject{
constructor() {
this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
notify() {
this.subs.map(sub => {
sub.update()
})
}
}
// 观察者
class Observer{
update() {
console.log('updating')
}
}
// 测试
const subject = new Subject()
const observer = new Observer()
subject.addSub(observer)
subject.notify()
// 首先是目标的构造函数,他有个数组,用于添加观察者
// 还有个广播方法notify,遍历观察者数组后调用观察者们的update方法
发布订阅模式
class EventEmitter{
constructor() {
this._eventPool = {}
}
on(event, cb) {
this._eventPool.event ?
this._eventPool.event.push(cb)
:
this._eventPool[event] = [cb]
}
off(event) {
delete this._eventPool[event]
}
emit(event, ...args) {
if (this._eventPool[event]) {
this._eventPool[event].map(cb => {
cb(...args)
})
}
}
once(event, cb) {
this.on(event, (...args) => {
cb(...args)
this.off(event)
})
}
}
// 测试
const emitter = new EventEmitter()
emitter.on('a', (aaa, bbb) => {
console.log('fffffff', aaa, bbb)
})
emitter.emit('a', 'windy', 'tom')
emitter.once('b', (aaa, bbb) => {
console.log('fffffff', aaa, bbb)
})
emitter.emit('b', 'windy', 'tom')
基于一个主题/事件通道,订阅者subscriber通过自定义事件订阅主题,发布者publisher通过发布主题事件的方式发布。
观察者模式和发布订阅模式区别
- 在观察者模式中,观察者需要直接订阅目标事件。在目标发出内容改变的事件后,直接接收事件并作出响应。
- 发布订阅模式相比观察者模式多了个主题/事件通道,订阅者和发布者不是直接关联的。
- 观察者模式两个对象之间有很强的依赖关系;发布/订阅模式两个对象之间的耦合度低。
函数柯里化
当我们没有重新定义toString与valueOf时,函数的隐式转换会调用默认的toString方法,它会将函数的定义内容作为字符串返回。
而当我们主动定义了toString/vauleOf方法时,那么隐式转换的返回结果则由我们自己控制了。其中valueOf的优先级会toString高一点。
柯里化好处:参数复用、延迟运行(返回函数,想什么时候运行什么时候运行)
function currying() {
const [fn, ..._args] = [...arguments]
const cb = function() {
if (!arguments.length) {
return fn.apply(this, _args)
}
_args.push(...arguments)
return cb
}
// 可根据需要添加
cb.toString = fn.apply(this, _args)
return cb
}
// 测试柯里化
function add() {
return [...arguments].reduce((a, b) => a + b)
}
const a = currying(add, 12, 24, 36)
console.log(a()) // 72
实现一个add方法,使结果满足如下预期
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;
function add() {
let _args = [...arguments]
const _adder = function() {
_args.push(...arguments)
return _adder
}
_adder.toString = function() {
return _adder.reduce((a, b) => a + b)
}
return _adder
}
实现call和apply
Function.prototype.myCall = function() {
let [context, ...args] = [...arguments]
if (!context) context = window
context.fn = this
const res = context.fn(...args)
delete context.fn
return res
}
Function.prototype.myApply = function() {
let [context, args] = [...arguments]
if (!context) context = window
context.fn = this
let res
if (args) {
res = context.fn(...args)
} else {
res = context.fn()
}
delete context.fn
return res
}
// 测试
const person = {
name: 'mike',
getName: function(a, b) {
console.log(this.name, a, b)
}
}
function printName(a, b) {
console.log(this.name, a, b)
}
printName.myCall(person, 'time', 'fly')
printName.myApply(person, ['time', 'fly'])
实现bind
Function.prototype.myBind = function() {
const [context, ...args] = [...arguments]
const _this = this
return function() {
return _this.apply(context, args.concat(...arguments))
}
}
const person = {
name: 'mike',
getName: function(a, b) {
console.log(this.name, a, b)
}
}
// 测试
const boy = {
name: 'boy'
}
const getName2 = person.getName.myBind(boy, 'hhhh', 'yyyy')
getName2() // boy hhhh yyyy
实现instanceof
function myInstanceof(left, right) {
let leftVal = left.__proto__
let rightVal = right.prototype
while (true) {
if (leftVal === null) return false
if (leftVal === rightVal) return true
leftVal = leftVal.__proto__
}
}
new的本质
function myNew(fun) {
return function() {
const obj = {
__proto__: fun.prototype
}
fun.call(obj, ...arguments)
return obj
}
}
// 测试
function Person(name, age) {
this.name = name
this.age = age
}
const obj = myNew(Person)('yang', 18)
// Person { name: 'yang', age: 18 }
Object.create的基本原理
function myCreate(obj) {
function F() {}
F.prototype = obj
return new F()
}
实现promise
class MyPromise{
constructor(process) {
this.status = 'pending'
this.msg = ''
process(this.resolve.bind(this), this.reject.bind(this))
return this
}
resolve(val) {
this.status = 'fulfilled'
this.msg = val
}
reject(val) {
this.status = 'rejected'
this.msg = val
}
then(fulfilled, reject) {
if (this.status === 'fulfilled') {
fulfilled(this.msg)
}
if (this.status === 'rejected'){
reject(this.msg)
}
}
}
// 测试
const mm = new MyPromise((resolve, reject) => {
resolve('123')
})
mm.then(res => {
console.log(res, 'success')
})
防抖和节流
所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。
区别:
- 函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。
- 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。
应用:进行窗口的resize、scroll,输入框内容校验或请求ajax时
// 防抖
function debounce(fn, time) {
let timeout
return function() {
const _this = this
const args = arguments
if (timeout) clearTimeout(timeout)
timeout = setTimeout(() => {
fn.apply(_this, args)
}, time || 500)
}
}
// 节流
function throttle(fn, time) {
let timeout
return function() {
const _this = this
const args = arguments
if (!timeout) {
timeout = setTimeout(() => {
fn.apply(_this, args)
timeout = null
}, time || 500)
}
}
}
function count() {
console.log('counting')
}
window.onscroll = debounce(count, 1000)
window.onscroll = throttle(count, 1000)
for循环和reduce实现map和filter
// for循环实现map
Array.prototype.map2 = function() {
const arr = this
const [fn, thisArg] = [...arguments]
let res = []
for ( let i = 0; i < arr.length; i++) {
res.push(fn.call(thisArg, arr[i], i, arr))
}
return res
}
// reduce实现map
Array.prototype.map3 = function() {
const arr = this
const [fn, thisArg] = [...arguments]
return arr.reduce((acc, cur, i) => {
acc.push(fn.call(thisArg, cur, i, arr))
return acc
}, [])
}
// 测试map
const m = [1,2,3,4,54].map2(item => item * item)
console.log(m) // [ 1, 4, 9, 16, 2916 ]
// for循环实现filter
Array.prototype.filter2 = function() {
const arr = this
const [fn, thisArg] = [...arguments]
let res = []
for ( let i = 0; i < arr.length; i++) {
if (fn.call(thisArg, arr[i], i, arr)) {
res.push(arr[i])
}
}
return res
}
// reduce实现filter
Array.prototype.filter3 = function() {
const arr = this
const [fn, thisArg] = [...arguments]
return arr.reduce((acc, cur, i) => {
fn.call(thisArg, cur, i, arr) && acc.push(cur)
return acc
}, [])
}
// 测试filter
const n = [0, 1, 2, 3, 4, 5]
const n2 = n.filter3(item => item % 2)
console.log(n2) // [ 1, 3, 5 ]
使用for循环打印1-10, 每个数字出现间隔500ms
错误方法:
// 结果虽然依次输出了1-10,但是每个数字之间没有间隔,是一次性输出的,所以不正确
for (var i = 1; i <= 10; i++) {
setTimeout((function(i) {
console.log(i);
})(i), 500);
}
正确方法:
// 使用闭包,注意setTimeout, 每隔500ms,因此每次传递的间隔时间要乘以i
for(var i=1;i<=10;i++){
(function(i){
setTimeout(function(){
console.log(i);
},500 * i);
})(i);
}
或者使用let,let本身就是块级作用域
for(let i=1;i<=10;i++){
setTimeout(function(){
console.log(i);
},500 * i);
}
使用setTimeout模拟setInterval
function fn() {
console.log('123')
}
setTimeout(function f() {
fn()
setTimeout(f, 500)
}, 500)
ES5实现继承
function Parent() {
this.name = 'parent'
this.play = [1, 2, 3]
}
function Child() {
Parent.call(this)
this.name = 'child'
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
// 测试
const ss = new Child()
console.log(ss instanceof Child, ss instanceof Parent) // true true
console.log(ss.constructor) // Child