Wang's blog Wang's blog
首页
  • 前端文章

    • HTML教程
    • CSS
    • JavaScript
  • 前端框架

    • Vue
    • React
    • VuePress
    • Electron
  • 后端技术

    • Npm
    • Node
    • TypeScript
  • 编程规范

    • 规范
  • 我的笔记
  • Git
  • GitHub
  • VSCode
  • Mac工具
  • 数据库
  • Google
  • 服务器
  • Python爬虫
  • 前端教程
更多
收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Wang Mings

跟随大神,成为大神!
首页
  • 前端文章

    • HTML教程
    • CSS
    • JavaScript
  • 前端框架

    • Vue
    • React
    • VuePress
    • Electron
  • 后端技术

    • Npm
    • Node
    • TypeScript
  • 编程规范

    • 规范
  • 我的笔记
  • Git
  • GitHub
  • VSCode
  • Mac工具
  • 数据库
  • Google
  • 服务器
  • Python爬虫
  • 前端教程
更多
收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Python爬虫

  • 前端教程

    • 团队规范

    • Project

    • JS

      • Canvas基础
      • 数据结构
      • 树的深度优先遍历与广度优先遍历
      • for in和for of区别
      • ES6-新增特性一览
      • ES6-解构赋值及原理
      • ES6-Object
      • ES6-模块详解
      • ES6-Class
      • ES6-ECMAScript特性汇总
      • 输入URL背后的技术步骤
      • JavaScript与浏览器 - 线程与引擎
      • HTTP跨域解决方案
      • Http 2与Http 1.x比较
      • JavaScript原型
      • JavaScript继承
      • JavaScript事件循环
      • 动手实现Promise
      • JS设计模式
      • JS 经典面试题
        • [#](#_1-call-apply) 1. call/apply
          • [#](#问题) 问题
          • [#](#思路) 思路
        • [#](#_2-bind) 2. bind
          • [#](#问题-2) 问题
          • [#](#思路-2) 思路
        • [#](#_3-new) 3. new
          • [#](#问题-3) 问题
          • [#](#思路-3) 思路
        • [#](#_4-curry) 4. curry
          • [#](#问题-4) 问题
          • [#](#思路-4) 思路
          • [#](#变种思考) 变种思考
        • [#](#_5-pipe-compose) 5. pipe/compose
          • [#](#pipe) pipe
          • [#](#compose) compose
        • [#](#_6-flatten) 6. flatten
          • [#](#深度为1的展平) 深度为1的展平
          • [#](#深度无限的展平) 深度无限的展平
          • [#](#指定深度的展平) 指定深度的展平
        • [#](#_7-去重) 7. 去重
        • [#](#_8-浅拷贝-深拷贝) 8. 浅拷贝/深拷贝
        • [#](#_9-防抖-节流) 9. 防抖/节流
        • [#](#_10-判断变量是-proxy-的实例) 10. 判断变量是 Proxy 的实例
          • [#](#动手实现判断proxy思路) 动手实现判断Proxy思路:
        • [#](#_11-手动实现模版引擎) 11. 手动实现模版引擎
          • [#](#问题-5) 问题
          • [#](#思路-5) 思路
        • [#](#_12-injector) 12. Injector
          • [#](#问题-6) 问题
          • [#](#思路-6) 思路
        • [#](#_13-实现ajax-get) 13. 实现ajax.get\(\)
          • [#](#_1-promise-prototype-catch) 1. Promise.prototype.catch
          • [#](#_2-promise-prototype-finally-cb) 2. Promise.prototype.finally\(cb\)
          • [#](#finally分析) finally分析
          • [#](#思路-7) 思路
          • [#](#_3-promise-all) 3. Promise.all
          • [#](#_4-promise-race) 4. Promise.race
        • [#](#_14-图片懒加载) 14. 图片懒加载
        • [#](#_15-手写发布订阅-依赖者模式) 15. 手写发布订阅/依赖者模式
        • [#](#参考文章) 参考文章
      • 排序算法
      • 正则表达式
      • MVC、MVP、MVVM区别
      • Array API与V8源码解析
      • 从V8 sort源码看插入排序
    • NodeJS

    • Vue

    • React

    • 效率工具

    • 读书笔记

  • 教程
  • 前端教程
  • JS
wangmings
2022-07-19
目录

JS 经典面试题

# # JS 经典面试题

重点考察JS原理及应用

# # 1. call/apply

# # 问题

    var foo = { value: 1 }
    function bar() { console.log(this.value) }
    bar.call(foo) // 期待打印:1
    bar.apply(foo) // 期待打印:1
    
1
2
3
4
5

# # 思路

call/apply立即执行函数,同时函数中的this改为指向context。类似等价于以下

    var foo = {
        value: 1,
        fn: function bar() { console.log(this.value) }
    }
    
1
2
3
4
5
    Function.prototype.call = function(context, ...args) {
        context = context || window
        context.fn = this // 这里的this代表函数
        context.fn(...args) // 给context添加属性fn,所以执行fn方法时,里面的this代表context
        delete context.fn
    }
    
    Function.prototype.apply = function(context, ...args) {
        context = context || window
        context.fn = this
        context.fn(args) // apply传递数组
        delete context.fn
    }
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# # 2. bind

# # 问题

    var foo = { value: 1 }
    function bar() { console.log(this.value) }
    let barBind = bar.bind(foo)
    barBind() // 期待打印:1
    
1
2
3
4
5

# # 思路

通过apply改变this,并且返回一个函数

    Function.prototype.bind = function (context, ...args) {
        var fn = this
        return function() {
            return fn.apply(context, args)
        }
    }
    
1
2
3
4
5
6
7

# # 3. new

# # 问题

    new Foo('name') = _new(Foo, 'name') // 模拟new
    
1
2

# # 思路

    _new() {
        var object = new Object() // 1. 类都是object类型
        var Constructor = [].shift.call(arguments)
        var args = arguments // 剩下的参数
        object.__proto__ = Constructor.prototype // 2. 设置原型链
        var ret = Constructor.apply(obj, args) // 3. 构造函数执行
        return typeof ret === 'object' ? ret : obj
    }
    _new(Foo, 'name')
    
    // es6
    _new(Constructor, ...args) {
       let object = Object.create(Constructor.prototype)
       let ret = Constructor.apply(object, args)
       return typeof ret === 'object' ? ret : obj
    }
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# # 4. curry

# # 问题

    let addFun = function(a, b, c) { return a + b + c }
    let curryFun = curry(addFun)
    curryFun(1)(2)(3) === 6 // true
    
1
2
3
4

# # 思路

递归,当执行的参数个数等于原本函数的个数,执行函数

    var curry = function(fn) {
        var limit = fn.length // fn函数参数个数
        return function judgeCurry (...args) {
            if (args.length >= limit) {
                return fn.apply(null, args)
            } else {
                return function(...args2) {
                    return judgeCurry.apply(null, args.concat(args2))
                }
            }
        }
    }
    
    // or es6
    var curry = function(fn, ...args) {
        if (args.length >= fn.length) {
            return fn(...args)
        }
    
        return function (...args2) {
            return curry(fn, ...args, ...args2)
        }
    }
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# # 变种思考

这道题也有很多变种,比如如下考察闭包:实现curry(1,3, 4)(1, 2)(1)() = 12

    const curry = (...arg) => {
        return (...arg2) => {
            if (arg2.length === 0) {
                return arg.reduce((sum, val) =>  sum += val, 0)
            } else {
                return curry(...arg, ...arg2)
            }
        }
    }
    
    console.log(curry(1,3, 4)(1, 2)(1)()) // 12
    
1
2
3
4
5
6
7
8
9
10
11
12

# # 5. pipe/compose

# # pipe

  • pipe(fn1,fn2,fn3,fn4)(args)等价于fn4(fn3(fn2(fn1(args)))
  • 第一个函数的结果,作为第二个函数的参数,以此类推...

# # compose

  • compose(fn1,fn2,fn3,fn4)(args)等价于fn1(fn2(fn3(fn4(args)))
  • 与pipe相反,先计算倒数第一个结果,作为倒数第二的参数,以此类推...
    let loopItem = (prevFn, nextFn) => (...args) => prevFn(nextFn(...args))
    
    const compose = (...fns) => fns.reduce(loopItem);
    const pipe = (...fns) => fns.reduceRight(loopItem)
    
    const example = pipe(
        (x, y) => x * y,
        x => x + 1
    );
    console.log(example(3, 4)) // 13
    
1
2
3
4
5
6
7
8
9
10
11

# # 6. flatten

# # 深度为1的展平

    // before:[1, 2, [3, 4, [5, 6]]]
    // after flat: [1, 2, 3, 4, [5, 6]]
    
    // 思路:使用reduce或map
    function flatSingle(arr) {
        return arr.reduce((pre, val) => pre.concat(val), [])
    }
    
    // or
    let flatSingle = arr => [].concat(...arr)
    
1
2
3
4
5
6
7
8
9
10
11

# # 深度无限的展平

    // before: [1,2,3,[1,2,3,4, [2,3,4]]]
    // after flatDeep: [1, 2, 3, 1, 2, 3, 4, 2, 3, 4]
    
    // 思路:深度优先递归,使用reduce连接起来
    // 深度优先算法 - 递归
    function flatDeep(arr) {
        return arr.reduce((pre, val) => pre.concat(Array.isArray(val) ? flatDeep(val) : val), [])
    }
    
    // 深度优先算法 - 堆栈
    function flatDeep(arr) {
        const stack = [...arr]
        const res = []
        while (stack.length) {
            const val = stack.pop() // 从尾部开始
            Array.isArray(val) ? stack.push(...val) : res.push(val)
        }
    
        return res.reverse()
    }
    
    // 取巧,利用Array.toString()
    function flatDeep(arr) {
        return arr.toString().split(',')
    }
    
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

# # 指定深度的展平

深度的含义是指每一项展平的次数

    // before: [1,2,3,[1, [2]], [1, [2, [3]]]]
    // after: [ 1, 2, 3, 1, 2, 1, 2, [ 3 ] ]
    
    function flatDeep(arr, depth = 1) {
        if (depth === 1) return arr.reduce((pre, val) => pre.concat(val), [])
        return arr.reduce((pre, val) => pre.concat(Array.isArray(val) ? flatDeep(val, depth - 1) : val), [])
    }
    
1
2
3
4
5
6
7
8

# # 7. 去重

数组去除重复

    // before: [2, 1, 3, 2]
    // after: [2, 1, 3]
    
    function removeRepeat(arr) {
        return arr.filter((item, index) => arr.indexOf(item) === index)
    }
    
    // or es6
    let removeRepeat = arr =>  Array.from(new Set(arr))
    let removeRepeat = arr =>  [...new Set(arr)]
    
1
2
3
4
5
6
7
8
9
10
11

# # 8. 浅拷贝/深拷贝

    // 浅拷贝
    function clone(source) {
        var target = {}
        for (var i in source) {
            source.hasOwnProperty(i) && target[i] = source[i]
        }
    
        return target
    }
    
    // or es6
    const clone = source => Object.assign({}, source)
    const clone = source => { ...source }
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
    // 深拷贝
    // 思路:递归赋值
    const deepClone = obj => {
        const isObj = obj => typeof obj === 'object' || typeof obj === 'function' && obj !== null
        if (!isObj(obj)) {
            throw new Error('不是对象')
        }
    
        // 区分array和object对象
        let target = Array.isArray(obj) ? [] : {}
        // https://stackoverflow.com/questions/34449045/what-is-the-difference-between-reflect-ownkeysobj-and-object-keysobj
        Reflect.ownKeys(obj).forEach(key => {
            target[key] = isObj(obj[key]) ? deepClone(obj[key]) : obj[key]
        })
    
        return target
    }
    
    // 优化:以上未考虑到对象循环引用
    const isObject = obj => obj !== null && (typeof obj === 'object' || typeof obj === 'function');
      const isFunction = obj => typeof obj === 'function'
      function deepClone (obj, hash = new WeakMap()) {
        if (hash.get(obj)) {
          // 环处理
          return hash.get(obj);
        }
        if (!isObject(obj)) {
          // 基本数据处理
          return obj;
        }
        if (isFunction(obj)) {
          // function返回原引用
          return obj;
        }
    
        let cloneObj;
        const Constructor = obj.constructor;
        switch (Constructor) {
          // 包装函数处理,可能是new Boolean(false)
          case Boolean:
          case Date:
            return new Date(+obj);
          case Number:
          case String:
          case RegExp:
            return new Constructor(obj);
          default:
            cloneObj = new Constructor(); // 重要:初始化cloneObj类型
            hash.set(obj, cloneObj);
        }
    
        [...Object.getOwnPropertyNames(obj), ...Object.getOwnPropertySymbols(obj)].forEach(k => {
          cloneObj[k] = deepClone(obj[k], hash);
        })
        return cloneObj;
      }
    
    // or 取巧方法
    // 注意这种取巧方法是有限制的
    // 1. 只能解析Number、String、Array等能够被json表示的数据结构
    // 2. 不能处理循环引用
    const deepClone = source => JSON.parse(JSON.stringify(source))
    
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

# # 9. 防抖/节流

  • 防抖:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。适合多次事件一次响应。
  • 节流:规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。适合大量事件按时间做平均分配触发。
    // 防抖案例:窗口resize停止才执行最终的函数
    function debounce(fn, wait, ...args) {
        var that = this
        fn.tId && clearTimeout(fn.tId)
        fn.tId = setTimeout(function() {
            fn.apply(that, args)
        }, wait)
    }
    
    function handle(e) {
        console.log('resize end event')
        console.log(e) // Event{}
    }
    // 缺点:handle不能写成匿名函数,因为把tId存储在handle函数对象上。所以间接导致传递参数e较为复杂
    window.onresize = function(e) { debounce(handle, 1000, e) }
    
    // 改进版
    // 思路: 用闭包把tId存储起来
    function debounce(fn, wait) {
        var tId
        return function() {
            var that = this
            var args = arguments
            tId && clearTimeout(tId)
            tId = setTimeout(function() {
                fn.apply(that, args)
            }, wait)
        }
    }
    function handle(e) {
        console.log('resize end event')
        console.log(e) // Event{}
    }
    window.onresize = debounce(handle, 1000)
    
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
    // 节流案例: 不停scroll时,滚动条每隔100ms固定频率执行函数
    function throttle(fn, wait) {
        var cur = new Date()
        fn.last = fn.last || 0
        if (cur - fn.last > wait) {
            fn.call()
            fn.last = cur
        }
    }
    
    function handle() {
        console.log('scroll event')
    }
    window.onscroll = function() { throttle(handle, 100) }
    
    // 或者
    function throttle(fn, wait) {
        var canRun = true // 标记是否可继续执行
        return function() {
            var that = this
            var args = arguments
            if (!canRun) return
            canRun = false
            setTimeout(function() {
                fn.apply(that, args)
                canRun = true
            }, wait)
        }
    }
    
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

# # 10. 判断变量是 Proxy 的实例

核心知识:Symbol.toStringTag (opens new window) (opens new window)

toString() 方法只能返回特定类型的标签。一些对象类型(如Map,Promise)能toString()识别是因为引擎为它们设置好toStringTag标签

    Object.prototype.toString.call(new Map());       // "[object Map]"
    Object.prototype.toString.call(function* () {}); // "[object GeneratorFunction]"
    Object.prototype.toString.call(Promise.resolve()); // "[object Promise]"
    
    // 自定义toStringTag
    ({[Symbol.toStringTag]: 'Foo'}.toString()) // [object Foo]
    
1
2
3
4
5
6
7

# # 动手实现判断Proxy思路:

  1. Symbol.toStringTag 属性来改写 Object.prototype.toString 方法
  2. Proxy代理construct()方法,设置 Symbol.toStringTag
    // 通过 Proxy 对 Proxy 本身做代理,然后赋值给 Proxy
    Proxy = new Proxy(Proxy, {
        construct: function(target, args) {
            let result = new target(...args) // target为Proxy
            const originTag = Object.prototype.toString.call(result).slice(1,-1).split(' ')[1]
            result[Symbol.toStringTag] = 'Proxy-' + originTag
            return result
        }
    })
    
    // 测试
    let testProxy = new Proxy([], {})
    Object.prototype.toString.call(testProxy) // [object Proxy-Array]
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# # 11. 手动实现模版引擎

# # 问题

手动实现模版引擎,类似underscore

    // 实现underscore模板引擎
    <script type="text/html" id="user_tmpl">
        <%for ( var i = 0; i < users.length; i++ ) { %>
            <li>
                <a href="<%=users[i].url%>">
                    <%=users[i].name%>
                </a>
            </li>
        <% } %>
    </script>
    
    var precompile = _.template(document.getElementById("user_tmpl").innerHTML);
    var html = precompile(data);
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# # 思路

  1. 利用正则替换字符。
    1. 将 %> 替换成 p.push('
    2. 将 <% 替换成 ');
    3. 将 <%=xxx%> 替换成 ');p.push(xxx);p.push('
  2. 利用eval/Function执行字符代码。
    // 按以上规则形成如下:
    ');for ( var i = 0; i < users.length; i++ ) { p.push('
        <li>
            <a href="');p.push(users[i].url);p.push('">
                ');p.push(users[i].name);p.push('
            </a>
        </li>
    '); } p.push('
    
1
2
3
4
5
6
7
8
9
    function tmpl(str, data) {
        var string = "var p = []; p.push('" +
        str
        .replace(/[\r\t\n]/g, "")
        .replace(/<%=(.*?)%>/g, "');p.push($1);p.push('")
        .replace(/<%/g, "');")
        .replace(/%>/g,"p.push('")
        + "');"
    
        eval(string)
    
        return p.join('');
    };
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# # 12. Injector

# # 问题

模拟RequireJS的模块。

    injector.register('service', function(name) { return name })
    injector.register('constant', 2)
    var func = injector.resolve(['service', 'constant'], function(service,constant, other) {
        console.log(service(constant), other)
    });
    func(3) // 期待打印:2, 3
    
1
2
3
4
5
6
7

# # 思路

通过闭包把register存储的值当作参数注入到func中。

笔者注:这种方式可以自己注册模块并且选择是否在流程中使用它,典型的依赖注入(也叫控制反转(IOC),因为本来func是用户依赖创建的,主动权在func上,但现在里面的逻辑是靠传入的service/serviceN参数确定的,相当于把主动权让渡给参数了,所以叫控制反转)。

    // 依赖注入(控制反转)
    var injector = {
        dependencies: {},
        register: function(key, value) {
            this.dependencies[key] = value;
        },
        resolve: function(deps, func, scope) {
            var registerArgs = deps.map(name => this.dependencies[name])
            return function(...args) {
                func.apply(scope || {}, [...registerArgs, ...args])
            }
        }
    }
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# # 13. 实现ajax.get()

说明:考查Promise能力

    ajax.get = function(url) {
        return new Promise(function(resolve, reject) {
            let xhr = new XMLHttpRequest()
            xhr.open('get', url, true)
            xhr.onreadystatechange = function() {
                if (this.readyState === 4) {
                    if (this.status === 200) {
                        resolve(this.response, this)
                    } else {
                        reject({ response: this.response, code: this.status})
                    }
                }
            }
            xhr.send()
        })
    }
    
    get('http://api.wwnight.cn').then((value, that)=>{
        console.log(value)
    })
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

更进一步,实现 Promise 的catch、finally、race、all实现,Promise更多细节内容可看笔者另外一篇文章:动手实现Promise (opens new window) (opens new window):

# # 1. Promise.prototype.catch

    Promise.prototype.catch = function(onRejected) {
        return this.then(null, onRejected)
    }
    
1
2
3
4

# # 2. Promise.prototype.finally(cb)

# # finally分析

  1. finally(cb)方法中的回调函数,都会执行
  2. finally后依然可以添加then方法,then参数值为上一个then的返回值
    let p1 = new Promise((resolve, reject) => {
        resolve('value')
    })
    
    p1.then(val => 'next-' + val)
        .finally(() => console.log('finally'))
        .then(val => console.log(val)) // 'next-value'
    
1
2
3
4
5
6
7
8

# # 思路

finally() 等同于 .then(onFinally, onFinally)

    Promise.prototype.finally = function (cb) {
        const Promise = this.constructor
        return this.then(
            val => Promise.resolve(cb()).then(() => value),
            err => Promise.resolve(cb()).then(() => throw err)
        )
    }
    
1
2
3
4
5
6
7
8

# # 3. Promise.all

Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值(失败优先)。

    Promise.all = (promises) => {
        return new Promise((onResolve, onReject) => {
            let result = [], ;
    
            // promise为空时,直接return
            if (!promises.length) {
                onResolve(result)
                return
            }
    
            let pending = i = promises.length
            let processPromise = (i) => {
                promises[i].then(value => {
                    result[i] = value // 收集结果
                    // 当收集完时,onResolve返回
                    if (!--pending) {
                        onResolve(result)
                    }
                }, onReject)
            }
    
            while(i--) {
                processPromise(i)
            }
        })
    }
    
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

# # 4. Promise.race

race就是赛跑的意思。哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。

    Promise.race = (promises) => {
        return new Promise((onResolve, onReject) => {
            for (p of promises) {
                // 谁快谁先onResolve输出
                Promise.resolve(p).then(onResolve, onReject)
            }
        })
    }
    
1
2
3
4
5
6
7
8
9

# # 14. 图片懒加载

    // 方法一
    function inSight(el) {
      const bound = el.getBoundingClientRect()
      const height = window.innerHeight
    
      return bound.top < height
    }
    
    const imgs = document.querySelectorAll('.my-photo')
    
    function checkImg() {
      console.log(1)
      imgs.forEach(img => {
        if (inSight(img)) {
          loadImg(img)
        }
      })
    }
    
    function loadImg(el) {
      if (!el.src) {
        const source = el.dataset.src
        el.src = source
      }
    }
    
    function throttle(fn, wait = 100) {
      let pre
      return function () {
        if (!pre) {
          pre = +new Date
        }
        let now = +new Date
        if (now - pre > wait) {
          pre = now
          fn()
        }
      }
    }
    
    window.onscroll = throttle(checkImg)
    
    // 方法二
    function checkImgs(){
        Array.from(document.querySelectorAll('.my-photo')).forEach(item => io.observe(item))
    }
    
    function loadImg(el){
        if(!el.src){
            const source = el.dataset.src
            el.src = source
        }
    }
    
    const io = new IntersectionObserver(ioes => {
        ioes.forEach((ioe)=>{
            const el = ioe.target
            const intersectionRatio = ioe.intersectionRatio
            if(intersectionRatio > 0 && intersectionRatio <= 1){
                loadImg(el)
            }
            el.onload = el.onerror = ()=>io.unobserve(el)
        })
    })
    
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

# # 15. 手写发布订阅/依赖者模式

前端必备设计模式案例

    // 发布订阅
    const event = {
        obj: {},
        on: function(name, fn) {
            (this.obj[name] || this.obj[name] = []).push(fn)
        },
        emit: function(name, ...args) {
            if (Array.isArray(this.obj[name])) {
                this.obj[name].forEach(fn => fn.call(this, ...args))
            }
        }
    }
    
    // 依赖者模式
    function Dep() {
        this.watchers = []
    }
    Dep.prototype.depend = watcher => this.watchers.push(watcher)
    Dep.prototype.notify = () => this.watchers.forEach(w => w.update())
    
    function Watcher(fn) {
        this.fn = fn
    }
    Watcher.prototype.update = function() {
        this.fn()
    }
    const dep = new Dep()
    dep.depend(new Watcher(function() {}))
    dep.notify()
    
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

# # 参考文章

  • 30-seconds-of-code (opens new window) (opens new window)
  • MDN Function.prototype.bind() (opens new window) (opens new window)
  • 一行写出javascript函数式编程中的curry (opens new window) (opens new window)
  • ES6 JavaScript Compose Function (opens new window) (opens new window)
  • JS函数防抖和函数节流 (opens new window) (opens new window)
  • Dependency injection in JavaScript (opens new window) (opens new window)
编辑 (opens new window)
JS设计模式
排序算法

← JS设计模式 排序算法→

最近更新
01
theme-vdoing-blog博客静态编译问题
09-16
02
搜索引擎
07-19
03
友情链接
07-19
更多文章>
Theme by Vdoing | Copyright © 2019-2022 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式