Angular is good in the MVVM framework, but with such a large and comprehensive framework, it is not low to learn, and it will take at least one or two weeks to get started. knockoutjs focuses on data binding and can be put into use in just one or two days, so the learning cost should not be too low! In an era when front-end evolution is so rapid, learning cost is also a factor that has to be considered. Many times, our projects are not that complicated, and they do not need a universal framework. They need simple and easy tools.
Before Knockoutjs
Suppose we are building an order system and need to display the unit price of the product, and then we can calculate the total price based on the input quantity and display it. It is also easy to achieve with native code, the effect is:
The code is as follows:
<!--HTML code-->Price: <span id="price"></span><br />Account: <input type="text" id="account" value="" placeholder="Please enter the quantity" /><br />sum: <span id="sum"></span> //js codevar priceNode = document.getElementById('price'),accountNode = document.getElementById('account'),sumNode = document.getElementById('sum'),price = 100,account = 11,sum = price * account;//Initialize. priceNode.innerText = price;accountNode.value = account;sumNode.textContent = sum;// Monitor user input of View layer accountNode.addEventListener('keydown', function (e) {window.setTimeout(function () {account = accountNode.value;sum = price * account;sumNode.textContent = sum;},10);});Well, it's quite simple! Oh, by the way, we display 50 items at a time, and there are 10 types of displays, as well as various promotions such as buying 5 boxes of Okamoto and getting a fried dough stick...
So, you know the problem of native implementation:
• With the increase in UI and data interaction, the amount of code has grown rapidly and is difficult to maintain
•Names based on Dom queries, id or class are difficult to manage
•High code coupling, difficult to reuse
Introduction to Knockoutjs
Knockoutjs (hereinafter referred to as ko) appeared to solve the above problems. It is a lightweight MVVM library that focuses on implementing the binding of data and views. It does not provide UI classes and routing functions, and it is very fast to get started. At the same time, since Ko has been out for some years, it has become a relatively mature framework. When doing some pages that display more dynamically, ko is undoubtedly a better choice. I won’t say much about MVVM, just a picture to confuse:
ko is based on three core features (introduction to the official website):
1. Observables and dependency tracking: Use observables to set up an implicit relationship chain between model data for data transformation and binding.
2. Declarative bindings: Use simple and easy-to-read syntax to easily bind model data to DOM elements.
3. Templating: Built-in template engine, quickly write complex UI presentations for your model data.
Using ko is very simple. Just download it directly to the official website (http://knockoutjs.com/index.html) and introduce it with <script>.
Observable objects
Rewrite the above example using ko (custom price, which was one of my childhood wishes):
The code looks like this:
<!--HTML Code--><div id="one">Price: <input type="text" data-bind="value: price" placeholder="Please enter unit price" /><br />Account: <input type="text" data-bind="value: account" placeholder="Please enter number" /><br />sum: <span data-bind="text: sum"></span></div> // js Codevar ViewModel = function(p, a) {//Set as an observable object and initialize this.price = ko.observable(p);this.account = ko.observable(a);// When calling the ko function, this will be passed in. Otherwise, when executing the internal code of ko.pureComputed, this is ko and ko.price() reports an error. this.sum = ko.pureComputed(function() {//Because the observable object is a function object, you need to use price() to read the current value. //Set the value using price(NewValue), and supports chain writing: this.price(12).account(3)return this.price() * this.account();}, this);};var vm = new ViewModel(135, 10);//Apply this binding, and the binding begins to take effect ko.applyBindings(vm);1) Let’s look at the HTML code first:
You can see that a key-value pair like data-bind = "XX:OO" is added to each tag. This is the binding syntax of ko. What does XXOO represent? (XXOO? The author is still a child...) From the example, we can see that the attributes of XX are tags, which can be tag attributes such as text, value, class, checked, etc., and in fact, they can also be click, focus, load and other DOM events. OO looks like a variable, but it is not a variable, but a function object. Executing this function (with a()) can get the corresponding bound value. Through XXOO, the attributes or events of an element can be bound to the function objects in js (if XXOO, you have to be responsible for each other), this is ko's declarative binding. The definition of binding is actually an observer mode, but this is a two-way binding. The publisher and the subscriber subscribe to each other's messages. This is the two-way binding of MVVM. The result of ko bidirectional binding is that one party can automatically update the other party by changing, that is, the data and presentation layer are tightly bound together through ViewModel. The binding effect is similar to:
2) Let’s take a look at the js code:
You can see that a ViewModel object is defined in js, and the OO bound in HTML is operated in the object. There are two main operations here: ko.observable() and ko.pureComputed().
•ko.observable(p): See the name, this is the method of setting observable objects. The passed parameter p is the initialized value. The parameters here can be the basic data type or a json object. After being set as observable, it means that the system will observe this value all the time. Whether the p in the ViewModel or the p in the bound object changes will cause a refresh event, and all places that use this value will be updated to the latest state. Obviously, observable objects are relatively performance-consuming, so for values that do not require dynamic changes (such as prices), do not set as observable objects. Of course, it still needs to be put into ViewModel for centralized initialization.
•Note: The observable object returned by ko.observable(p) is a function object, so you need to use price() to read the observable object; similarly, setting the observable object requires price(newValue) to use price(newValue). What is more considerate is that when setting, it supports chain writing: ViewModel.price(100).account(10).
•ko.pureComputed() is the so-called dependency tracking. Here is the unit price * quantity equals the total price. Note that you cannot directly use this.sum = this.price() * this.account(); to specify sum. This writing method cannot dynamically refresh the bound object, but dynamically changes the sum variable, but other operations are required to refresh the bound object. Therefore, the binding values related to calculations must be set using ko's calculation function. Of course, the returned function object is also a function object. In addition, ko also has a computed function, which can also be set, but it is recommended to use pure to improve performance.
• Pay attention to the writing method here: ko.pureComputed(fn, this), that is, bind fn to the ViewModel scope, which is actually the call/apply in js. Because this is a ko object when executing ko internal functions, in order to obtain the scope of the ViewModel object, this needs to be passed into the above writing method. Of course, you can also use that to save the ViewModel object outside the ko function, and then use that inside the ko function to call the ViewModel object. Like this:
var that = this;this.sum = ko.pureComputed(function() {return that.price() * that.account();});After defining the ViewModel constructor, a ViewModel object is instantiated, and then the ko.applyBindings() method is used to make the binding take effect. Don't miss this step.
Simple page mode using ko:
<!--HTML Code--><span data-bind="text: bindtext"></span> // js Codevar viewModel = {bindtext: ko.observable('initValue')};ko.applyBindings(viewModel);To sum up, it is: use data-bind="XX: OO" to declare binding in HTML, create a ViewModel in js and set an observable object, and finally apply the binding.
Observable object array
Let’s take a look at the usage of observable object arrays. In ko, arrays and variables cannot be mixed like js. For array objects, you need to use ko.observableArray([…,…]) in the same way. Similarly, array elements can be of basic types or json objects. The observable object array in ko has a series of array operation methods, such as slice(), sort(), push(), etc. The effect is the same as the native js array operation methods. The only difference between changes made through the ko method will be notified to the subscriber to refresh the interface, but the js method will not refresh the interface. Here is a simple example:
<!--HTML Code--><select data-bind="options: list"></select> // js Codevar vm = {// list: ko.observableArray()list: ko.observableArray(['Luffy','Zoro','Sanji'])};ko.applyBindings(vm);Key point: ko monitors the state of the array, not the state of the element itself. That is to say, when the array state changes (adding elements), the ko event will be triggered to cause the bound object to refresh, but the changes in the elements inside the array (such as value changes) will not be monitored and cannot trigger the ko event. For example:
Using native methods to dynamically change Luffy to Lucy in the console will not refresh the UI page, while using ko's array to change the array will immediately refresh the page. It is worth noting that when refreshing, the previous changes will also be refreshed (Luffy > Lucy). In other words, the variables in js memory have actually changed, but there is still a lack of an action to refresh the DOM. As you can see here, the method of reading an array is vm.list()[0], because list is also a function object, and executing the return value is the list content we want. Similarly, you can reset the observable object array through vm.list(["Girl","Girl"]) and refresh the UI immediately.
If you need to dynamically react the changes of array elements to the UI, you need to set the array elements to observable objects, and then use ko's method to change the array element value. Note that it is to use ko's method list()[0]("Lucy")!
There are two types of methods for operating observable object arrays. One is the same name as the native js array method: pop, push, shift, unshift, reverse, sort, splice. This part is the same as the usage and effects of the native js method, so I will not repeat it again.
Some other methods are not available in js, mainly as follows:
•remove(someItem) -- Delete all element items with equal values to someItem and return them in an array form. What this means here is that you cannot directly delete the first item list.remove(0) but use the form list.remove(list()[0]) to delete it. In short, the passed parameter must be the value of the element item. It can be used in the form of list()[0], or you can directly enter the value string (such as "Luffy").
•remove(function(item) { return item.age < 18;}) -- Delete all element items with age attributes less than 18 and return them as an array. This usage is no different from ordinary array higher-order functions. Item is passed in as a parameter of a higher-order function. When iterating through the array, the item is deleted when the return value of the higher-order function is the true value, otherwise it will go to the next item.
•removeAll(['Chad', 132, undefined]) -- Remove all element items with values equal to 'Chad' or 123 or undefined and return them as an array.
•removeAll() -- Removes all items and returns as an array.
Tips: When dealing with observable objects, if there are many objects and frequent interactions, refreshing immediately with each change will consume performance. At this time, you can use the extension myObservableArray.extend({ rateLimit: 1000 }) to set delayed refresh. For example, when continuously inserting elements into the observable array, you can set a cycle time of 1000ms to concentrate all operations within 1000ms into a refresh to avoid performance deterioration caused by frequent DOM operations.
Summarize
This article mainly introduces the most important concept in knockoutjs: observable objects (arrays). An observable object is essentially a function object. When operating observable objects through the ko method, the UI can be dynamically refreshed. This is the recommended method. At the same time, you can also operate observable objects through the native js method, but the native method will not refresh the UI display, and you will need to wait until the next refresh event to be refreshed to the UI.