16

One, singleton mode

1. What is the singleton pattern

The definition of the singleton pattern is to ensure that there is only one instance of a class and provide a global access point to access it.

window object in the thread pool/global cache/browser, etc., we only need one instance.

The following will be introduced based on actual scenarios.

2. Actual scene

1. Login floating window

When we click the login button, a login floating window will appear on the page, and this login floating window is unique, no matter how many times the login button is clicked, this floating window will only be created once, then this login floating window It is suitable to be created in singleton mode.

1.1 Traditional approach

The traditional method is to create a login floating window when the page is loaded. When the user clicks the login button, the login floating window is displayed. The implementation code is as follows:

<button id="loginBtn">登录</button>
var loginLayer = (() => {
    let div = document.createElement('div')
    div.innerHTML = '我是登录弹窗'
    div.style.display = 'none'

    document.body.appendChild(div)

    return div
})()

document.getElementById('loginBtn').onclick = () => {
    loginLayer.style.display = 'block'
}

The above code has the following disadvantages:

  1. In the case of no login, the DOM node of the login floating window will be added, which wastes performance.

Now optimize it and change the code to add the DOM node of the login floating window after the user clicks the login button.

code show as below:

var createLoginLayer = () => {
    let div = document.createElement('div')
    div.innerHTML = '我是登录弹窗'
    div.style.display = 'none'

    document.body.appendChild(div)

    return div
}

document.getElementById('loginBtn').onclick = () => {
    const loginLayer = createLoginLayer()
    loginLayer.style.display = 'block'
}

The above code also has defects, as follows:

  1. Every time you click the login button, a login floating window will be created. Frequent creation of DOM nodes is a waste of performance.

In fact, we only need to create a login floating window once.

1.2 Singleton mode

Refactor the above code through singleton mode.

const createLoginLayer = () => {
    const div = document.createElement('div')
    div.innerHTML = '我是登录弹窗'
    div.style.display = 'none'
    console.log(123)

    document.body.appendChild(div)
    return div
}

const createSingle = (function () {
    var instance = {}
    return function (fn) {
        if (!instance[fn.name]) {
            instance[fn.name] = fn.apply(this, arguments)
        }
        return instance[fn.name]
    }
})()

const createIframe = function () {
    const iframe = document.createElement('iframe')
    document.body.appendChild(iframe)
    iframe.style.display = 'none'
    return iframe
}

const createSingleLoginLayer = createSingle(createLoginLayer)
const createSingleIframe = createSingle(createIframe)

document.getElementById('loginBtn').onclick = () => {
    const loginLayer = createSingleLoginLayer
    const iframe = createSingleIframe
    loginLayer.style.display = 'block'
    iframe.style.display = 'block'
}

After refactoring, the code has been optimized as follows:

  1. createIframe the responsibilities of creating instance objects createLoginLayer / 060cc2e26b59de and managing singleton objects createSingle , which conforms to the principle of single responsibility;
  2. Store the instance through the closure and make a judgment. No matter how many times the login button is clicked, only creates one login floating window instance ;
  3. It is easy to extend. When you need to create the only iframe / script and other tags in the page next time, you can reuse the logic directly.

3. Summary

The singleton pattern is a simple but very practical pattern, especially the lazy singleton technique. Objects are created when appropriate, and only one is created. What's more amazing is that the responsibilities of creating objects and managing singletons are distributed in two different methods, and the combination of these two methods has the power of the singleton pattern.

Second, the strategy model

1. What is the strategy mode

When we plan to go out on the National Day, in terms of transportation, we can choose the expensive and fast airplane , the medium-priced but slower motor train , the cheap but super slow train , according to different people. This is the strategy model .

The definition of the strategy pattern is to define a series of algorithms, encapsulate them one by one, and make them interchangeable.

2. Actual scene

1. Calculate the year-end bonus

1.1 Traditional approach

There is a need to calculate the employee's year-end bonus. Assume that the year-end bonus for employees with performance of S is 4 times the salary, the year-end bonus for employees A 3 times the salary, and the B for employees 2 . Calculate the employee’s year-end bonus.

var calculateBonus = function(performanceLevel, salary) {
    if (performanceLevel === 'S') {
        return salary * 4;
    }
    if (performanceLevel === 'A') {
        return salary * 3;
    }
    if (performanceLevel === 'B') {
        return salary * 2;
    }
};

calculateBonus('B', 20000); // 输出:40000 
calculateBonus( 'S', 6000 ); // 输出:24000

The above code has the following disadvantages:

  1. Use if-else statement to describe the logic, the code is huge;
  2. S , if you need to modify the bonus coefficient of performance 060cc2e26b5c30, you must modify the calculateBonus function, which violates the open-closed principle;
  3. It cannot be reused again. When this logic is needed in other places, it can only be copied again.
1.2 Strategy mode approach

After using the strategy model improved

const strategies = {
    S: salary => {
        return salary * 4
    },
    A: salary => {
        return salary * 3
    },
    B: salary => {
        return salary * 2
    }
}

const calculateBonus = (level, salary) => {
    return strtegies[level](salary)
}

console.log(calculateBonus('s', 20000))
console.log(calculateBonus('a', 10000))

You can see that the above code has made the following changes:

  1. The strategy class strategies encapsulates specific algorithms and calculation processes (calculation rules for each performance);
  2. The environmental category calculateBonus accepts the request and delegates the request to the strategy category strategies (employee's performance and salary;
  3. Separate the use of the algorithm from the realization of the algorithm, the code is clear, and the responsibilities are clear;
  4. Eliminate a large number of if-else sentences.
1.3 Summary

The strategy mode makes the code more readable and easy to expand more strategy algorithms. When the performance coefficient changes or the performance level increases, we only need to strategies , which conforms to the open-closed principle.

2. Form validation

When the form on the webpage needs to check the rules of input box/checkbox, etc., how to realize it?

Now there is a form requirement for registered users. Before submitting the form, the following rules need to be verified:

  1. Username can not be empty
  2. Password length cannot be less than 6 digits
  3. Mobile phone number must conform to the format
2.1 Traditional approach

Use the if-else statement to determine whether the form input complies with the corresponding rules, if not, the reason for the error will be prompted.

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>
    <form id='registerForm' action="xxx" method="post">
        用户名:<input type="text" name="userName">
        密码:<input type="text" name="password">
        手机号:<input type="text" name="phone">
        <button>提交</button>
    </form>
    <script type="text/javascript">
        let registerForm = document.getElementById('registerForm')

        registerForm.onsubmit = () => {
                if (registerForm.userName.value) {
                        alert('用户名不能为空')
                        return false
                }

                if (registerForm.password.value.length < 6) {
                        alert('密码长度不能少于6')
                        return false
                }

                if (!/(^1[3|5|8][0-9]$)/.test(registerForm.phone.value)) {
                        alert('手机号码格式不正确')
                        return false
                }
        }
        </script>
</body>
</html>

image.png

The above code has the following disadvantages:

  • onsubmit function is huge, including a large number of if-else statements;
  • onsubmit lacks flexibility. When there are rules that need to be adjusted or new rules need to be added, the onsubmit needs to be modified, which violates the open-closed principle;
  • The algorithm has poor reusability and can only be reused in other forms by duplication.
2.2 Strategy mode approach

Use the strategy pattern to refactor the above code.

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>
    
    <form action="http://xxx.com/register" id="registerForm" method="post">
         请输入用户名:
        <input type="text" name="userName" />
         请输入密码:
        <input type="text" name="password" />
         请输入手机号码:
        <input type="text" name="phoneNumber" />
        <button>
            提交
        </button>
    </form>
    <script type="text/javascript" src="index.js">
        
    </script>            
</body>  
</html>
// 表单dom
const registerForm = document.getElementById('registerForm')

// 表单规则
const rules = {
    userName: [
        {
            strategy: 'isNonEmpty',
            errorMsg: '用户名不能为空'
        },
        {
            strategy: 'minLength:10',
            errorMsg: '用户名长度不能小于10位'
        }    
    ],
    password: [
        {
            strategy: 'minLength:6',
            errorMsg: '密码长度不能小于6位'
        }
    ],
    phoneNumber: [
        {
            strategy: 'isMobile',
            errorMsg: '手机号码格式不正确'
        }
    ]
}

// 策略类
var strategies = {
    isNonEmpty: function(value, errorMsg) {
        if (value === '') {
            return errorMsg;
        }
    },
     minLength: function(value, errorMsg, length) {
        console.log(length)
        if (value.length < length) {
            return errorMsg;
        }
    },
     isMobile: function(value, errorMsg) {
        if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
            return errorMsg;
        }
    }
};

// 验证类
const Validator = function () {
    this.cache = []
}

// 添加验证方法
Validator.prototype.add = function ({ dom, rules}) {
    rules.forEach(rule => {
        const { strategy, errorMsg } = rule
        console.log(rule)
        const [ strategyName, strategyCondition ] = strategy.split(':')
        console.log(strategyName)
        const { value } = dom
        this.cache.push(strategies[strategyName].bind(dom, value, errorMsg, strategyCondition))
    })
}

// 开始验证
Validator.prototype.start = function () {
    let errorMsg
    this.cache.some(cacheItem => {
            const _errorMsg = cacheItem()
            if (_errorMsg) {
                    errorMsg = _errorMsg
                    return true
            } else {
                    return false
            }
    })

    return errorMsg
}

// 验证函数
const validatorFn = () => {
    const validator = new Validator()
    console.log(validator.add)

    Object.keys(rules).forEach(key => {
        console.log(2222222, rules[key])
        validator.add({
            dom: registerForm[key],
            rules: rules[key]
        })
    })

    const errorMsg = validator.start()
    return errorMsg
}


// 表单提交
registerForm.onsubmit = () => {
    const errorMsg = validatorFn()
    if (errorMsg) {
        alert(errorMsg)
        return false
    }
    return false
}

The code by strategies defining rules algorithm, Validator define the authentication algorithm, the separation of rules and algorithms, we simply arranged by the check can be completed form, validation rules which may be multiplexed anywhere in the program, but also As a form of plug-in, it can be easily transplanted to other projects.

3. Summary

The strategy pattern is a commonly used and effective design pattern. Through the above examples, some advantages of the strategy pattern can be summarized:

  • The strategy mode uses technology and ideas such as combination/delegation and polymorphism, which can effectively avoid multiple conditional selection sentences;
  • The strategy mode provides perfect support for the open-closed principle, encapsulating algorithms in independent strategy classes, making them easy to switch/understand/expand;
  • Combination and delegation are used in strategy mode to allow Context have the ability to execute algorithms, which is also a lighter alternative to inheritance.

Third, the agency model

1. What is the agency model

The proxy mode is to provide a substitute or placeholder for an object in order to control access to it.

The key to the proxy model is that when the client is inconvenient to directly access an object or does not meet the needs, a stand-in object is provided to control access to this object. The client actually accesses the stand-in object.

2. Simulated scene

1. Xiao Ming sends flowers to Xiao Bai

1.1 Traditional approach

The traditional method is that Xiao Ming directly sends the flowers to Xiao Bai, and Xiao Bai receives the flowers. The code is as follows:

const Flower = function () {
    return '玫瑰🌹'
}

const xiaoming = {
    sendFlower: target => {
        const flower = new Flower()
        target.receiveFlower(flower)
    }
}

const xiaobai = {
    receiveFlower: flower => {
        console.log('收到花', flower)
    }
}

xiaoming.sendFlower(xiaobai)
1.2 Proxy mode

However, Xiao Ming didn't know Xiao Bai. He wanted to use Xiao Dai to help him inquire about Xiao Bai's situation and send flowers when Xiao Bai was in a good mood, so that the success rate would be higher. code show as below:

const Flower = function () {
    return '玫瑰🌹'
}

const xiaoming = {
    sendFlower: target => {
        const flower = new Flower()
        target.receiveFlower(flower)
    }
}

const xiaodai = {
    receiveFlower: flower => {
        xiaobai.listenGoodMood().then(() => {
            xiaobai.receiveFlower(flower)
        })
    }
}

const xiaobai = {
    receiveFlower: flower => {
        console.log('收到花', flower)
    },
    listenGoodMood: fn => {
        return new Promise((reslove, reject) => {
            // 10秒后,心情变好
            reslove()
        })
    }
}

xiaoming.sendFlower(xiaodai)

Above, Xiao Ming monitored Xiao Bai's mood changes through Xiao Dai, and chose to send flowers to Xiao Bai when Xiao Bai was in a good mood. Not only that, but Xiaodai can also do the following:

  1. Help Xiaobai filter out some flower requests, which is called a protection agent;
  2. Help Xiaoming, when Xiaobai is in a good mood, perform flower buying operations, which is called a virtual agent. The virtual agent delays the creation of some expensive objects until they are really needed.

3. Actual scene

1. Picture preloading

A common technique for image preloading. If you directly set the src attribute to the img tag node, the position of the image is often blank for a period of time because the image is too large or the network is poor.

1.1 Traditional approach
const myImage = (() => {
    const imgNode = document.createElement('img')
    document.body.appendChild(imgNode)

    return {
        setSrc: src => {
            imgNode.src = src
        }
    }
})()

myImage.setSrc('https://img30.360buyimg.com/ling/jfs/t1/187775/5/8271/435193/60c8117eE7d79ef41/1d21db2c4dca9a90.png')

When you set the network speed to 5kb/s through the developer tools, you will find that the picture position is blank for a long time.

image.png

1.2 Virtual Agent

Next, use the virtual proxy to optimize this function, and hand over the operation of loading the image to the proxy function. When the image is loaded, first use a loading image to occupy the place. When the image is successfully loaded, it will be filled into the img node.

code show as below:

const myImage = (() => {
    const imgNode = document.createElement('img')
    document.body.appendChild(imgNode)

    return {
        setSrc: src => {
            imgNode.src = src
        }
    }
})()

const loadingSrc = '../../../../img/loading.gif'
const imgSrc = 'https://img30.360buyimg.com/ling/jfs/t1/187775/5/8271/435193/60c8117eE7d79ef41/1d21db2c4dca9a90.png'

const proxyImage = (function () {
    const img = new Image()
    img.onload = () => {
        myImage.setSrc(img.src)
    }

    return {
        setSrc: src => {
            myImage.setSrc(loadingSrc)
            img.src = src
        }
    }
})()

proxyImage.setSrc(imgSrc)

The above code has the following advantages:

  1. By proxyImage control of MyImage access in MyImage not previously loaded successfully, using loading FIG placeholder;
  2. Practicing the principle of single responsibility, to img node set src function MyImage , preloaded picture function proxyImage , only one duty;
  3. Practicing the open-closed principle, set src img node, which are isolated in two objects, and they can change separately without affecting each other.

2. Merge HTTP requests

Suppose we want to implement a file synchronization function, through the check box, when the check box is selected, the id corresponding to the check box is sent to the server, telling the server that the file corresponding to the id needs to be synchronized.

If you think about it, you will find that if every checkbox is checked, the interface is requested once. Assuming that 10 checkboxes are checked within 1s, then 10 requests will be sent.

2.1 Virtual Agent

The above approach can be optimized through a virtual proxy. A new proxy can be added to help checkboxes to initiate file synchronization requests, collect the requests within 1s, and then send these file ids to the server together after 1s.

code show as below:

<!DOCTYPE html>
<html>
<meta charset="utf-8" />
<head>
    <title></title>
</head>
<body>
  a <input type="checkbox" value="a" />
  b <input type="checkbox" value="b" />
  c <input type="checkbox" value="c" />
  d <input type="checkbox" value="d" />
    <script type="text/javascript" src="index.js">
    </script>
</body> 
</html>
const synchronousFile = cache => {
  console.log('开始同步文件,id为:'+ cache.join('/'))
}

const proxySynchronousFile = (() => {
  const cache = []

  let timer

  return id => {
    console.log(id)
    cache.push(id)

    if (timer) {
      return
    }

    timer = setTimeout(() => {
      synchronousFile(cache)
      clearTimeout(timer)
      timer = null
      cache.length = 0
    }, 2000)
  }
})()

const checkbox = document.getElementsByTagName('input')

Array.from(checkbox).forEach(i => {
  console.log(i)
  i.onclick = () => {
    if (i.checked) {
      proxySynchronousFile(i.value)
    }
  }
})

3. Ajax asynchronous request data

When the list needs to be paged, the data on the same page theoretically only needs to be pulled once in the background, and the pulled data can be cached, and the cached data can be used directly in the next request.

3.1 Caching Proxy

Use the cache proxy to achieve the above functions, the code is as follows:

(async function () {
  function getArticle (currentPage, pageSize) {
    console.log('getArticle', currentPage, pageSize)
    // 模拟一个ajax请求
    return new Promise((resolve, reject) => {
      resolve({
        ok: true,
        data: {
          list: [],
          total: 10,
          params: {
            currentPage, 
            pageSize
          }
        }
      })
    })
  }
  
  const proxyGetArticle = (() => {
    const caches = []
  
    return async (currentPage, pageSize) => {
  
      const cache = Array.prototype.join.call([currentPage, pageSize],',')
  
      if (cache in caches) {
        return caches[cache]
      }
      const { data, ok } = await getArticle(currentPage, pageSize)
  
      if (ok) {
        caches[cache] = data
      }
  
      return caches[cache]
    }
  })()

  // 搜索第一页
  await proxyGetArticle(1, 10)
  
  // 搜索第二页
  await proxyGetArticle(2, 10)

  // 再次搜索第一页
  await proxyGetArticle(1, 10)
  
})()

Through the cache proxy, when the data of the first page is requested for the second time, it is directly pulled from the cached data, without the need to request data from the server again.

4. Summary

The above describes the practice of virtual proxy and caching proxy based on actual scenarios.

When it is not convenient for us to directly access an object, find a proxy method to help us access the object. This is the proxy mode.

Practical exercises can be carried out through github source code

Hope this article can be helpful to you, thanks for reading ❤️~


Welcome to follow the blog of Bump Lab: aotu.io

Or follow the AOTULabs official account (AOTULabs) and push articles from time to time.


凹凸实验室
2.3k 声望5.5k 粉丝

凹凸实验室(Aotu.io,英文简称O2) 始建于2015年10月,是一个年轻基情的技术团队。