JS高级编程(三)-算法和设计模式

摩森特沃 2021年03月10日 358次浏览

数据结构和算法

斐波那契数列

迭代版本

  1. function fibonacciIterative(n) {
  2. if (n < 1) return 0;
  3. if (n <= 2) return 1;
  4. let fibNMinus2 = 0;
  5. let fibNMinus1 = 1;
  6. let fibN = n;
  7. for (let i = 2; i <= n; i++) { // n >= 2
  8. fibN = fibNMinus1 + fibNMinus2; // f(n-1) + f(n-2)
  9. fibNMinus2 = fibNMinus1;
  10. fibNMinus1 = fibN;
  11. }
  12. return fibN;
  13. }

递归版本

  1. function fibonacci(n){
  2. if (n < 1) return 0;
  3. if (n <= 2) return 1;
  4. return fibonacci(n - 1) + fibonacci(n - 2);
  5. }

递归性能优化版本

在以上递归算法中,某些值会经过多次计算,以下是使用闭包将已经算过的值进行缓存的方式来优化的代码

  1. function fibonacciMemoization() {
  2. const memo = [0, 1];
  3. const fibonacci = (n) => {
  4. if (memo[n] != null) return memo[n];
  5. return memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo);
  6. };
  7. return fibonacci;
  8. }
  9. const f = fibonacciMemoization()
  10. f(8) // 21

验证传入的二叉树是不是二叉查找树

二叉查找树(binary search tree, BST)的定义

  • 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值
  • 若任意节点的右子树不空,则右子树上所有节点的值均大于或等于它的根节点的值
  • 任意节点的左、右子树也分别为二叉查找树
  1. function TreeNode(val) {
  2. this.val = val;
  3. this.left = this.right = null;
  4. }

示例

  1. const isValidBST = function(root) {
  2. if (!root) {
  3. return true;
  4. }
  5. function helper(root, min, max) {
  6. if (!root) {
  7. // 当递归至叶子节点的子节点时,递归退出
  8. return true;
  9. }

  10. if ((min !== null && root.val <= min) || (max !== null && root.val >= max)) {
  11. // 若不符合BST的定义规则,则返回false
  12. return false;
  13. }
  14. // 缩小问题范围
  15. return helper(root.left, min, root.val) && helper(root.right, root.val, max);
  16. }
  17. return helper(root, null, null);
  18. };

排序算法

设计模式

发布-订阅模式

实现一个简易版的 Node.js 中的EventEmitter,用法如下

  1. const emitter = new EventEmitter()
  2. emitter.on('log', (param) => {
  3. console.log(param)
  4. })
  5. emitter.emit('log', 'Event Fire')

示例

  1. class EventEmitter {
  2. constructor() {
  3. this._events = {};
  4. }
  5. // 订阅type类型的事件,listener为处理时间的监听器
  6. on(type, listener) {
  7. this._events[type] = listener;
  8. }
  9. // 发布信息
  10. emit(type, ...args) {
  11. if(this._events[type]) {
  12. this._events[type].apply(this, ...args);
  13. }
  14. }
  15. }

观察者模式

使用示例

  1. const ob1 = new Observer();
  2. const ob2 = new Observer();
  3. const sub = new Subject();
  4. sub.add(ob1);
  5. sub.add(ob2);
  6. sub.notify('Event Fire');

实现示例

  1. class Subject {
  2. constructor(){
  3. this.observers = [];
  4. }
  5. // 增加观察者
  6. add(observer) {
  7. this.observers.push(observer);
  8. }
  9. // 通知所有观察者
  10. notify(...args) {
  11. this.observers.forEach(abserver => abserver.log(...args))
  12. }
  13. }

  14. class Observer {
  15. log(...args){
  16. console.log(args);
  17. }
  18. }

在观察者模式中,Subject 和 Observer 是互相耦合的 (Subject 要直接addObserver),而在 发布-订阅 模式中,由于 Event Channel 扮演了一个数据通道的角色,Publisher 和 Subscriber 是解耦的,这也使得 发布-订阅 模式 相对于观察者模式更加灵活。可以说,发布-订阅 模式是一种特殊的 观察者模式

装饰器模式

装饰模式的特点是不需要改变 被装饰对象 本身,而只是在外面套一个外壳接口来对其功能进行增强
react的高阶组件即使用了装饰器模式

  1. function withWindowWidth(BaseComponent) {
  2. class DerivedClass extends React.Component {
  3. state = {
  4. windowWidth: window.innerWidth,
  5. }

  6. onResize = () => {
  7. this.setState({
  8. windowWidth: window.innerWidth,
  9. })
  10. }

  11. componentDidMount() {
  12. window.addEventListener('resize', this.onResize)
  13. }

  14. componentWillUnmount() {
  15. window.removeEventListener('resize', this.onResize);
  16. }

  17. render() {
  18. return <BaseComponent {...this.props} {...this.state}/>
  19. }
  20. }
  21. return DerivedClass;
  22. }

  23. // 原始的组件仅能做简单的信息展示
  24. const MyComponent = (props) => {
  25. return <div>Window width is: {props.windowWidth}</div>
  26. };

  27. // 在使用了高阶组件后,高阶组件内部对原始的基础组件做了增强处理,使得新的组件可以自动根据窗口大小的变更在页面上显示最新的值
  28. // 在本示例中,原始组件始终没有做任何改变,只是通过装饰使其功能得到了增强
  29. export default withWindowWidth(MyComponent);

单例模式

在软件开发中,单体模式是指限制类只有一个实例,这样这个实例就可以被用在整个系统之中

在 JavaScript 中,我们只需要简单地声明:var g = {}就创建了一个单例

在前端开发中,单例模式是一种常用的模式,无论是window对象还是document对象,都是单例的

策略模式

在策略模式中,需要创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象,策略对象改变 context 对象的执行算法。做法是:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换

使用策略模式优化如下 React 代码

  1. function getEle(compName, value) {
  2. if (compName === 'input') {
  3. return <Input value={value}/>;
  4. } else if (compName === 'pic') {
  5. return <Image source={require(value)}/>;
  6. } else {
  7. return <View/>;
  8. }
  9. }
  10. const E = getEle('input', 123);

示例

  1. const eleMap = {
  2. input: value => <Input value={value} />,
  3. pic: value => <Image source={require(value)} />,
  4. default: () => <View />,
  5. };
  6. const E = eleMap['input'](123);

代理模式

代理模式在真正执行逻辑的时候会调用被代理的对象的逻辑,在调用前后可以加入自己的逻辑来对功能进行增强

使用代理模式,实现图片懒加载,在图片加载完成之前先展示loading.gif

  1. var myImage = (function () {
  2. var imgNode = document.createElement('img')
  3. document.body.appendChild(imgNode)

  4. return {
  5. setSrc: function (src) {
  6. imgNode.src = src
  7. }
  8. }
  9. })()

示例

  1. var proxyImage = (fucntion() {
  2. var img = new Image();
  3. img.onload = function() {
  4. myImage.setSrc(img.src);
  5. }

  6. return {
  7. setSrc: function(src) {
  8. myImage.setSrc('***.gif')
  9. img.src = src;
  10. }
  11. }
  12. })()

  13. proxyImage.setSrc('http://image.com')