In the 2020 You Don’t Know Svelte series, we will cover the basic uses, features, pros and cons, fundamentals and source code analysis of the Svelte framework.
directory
- First article 👉 It’s 2020 and you Still Don’t Know Svelte (1) — First Introduction to Svelte
- Second article 👉 It’s 2020 and you don’t know Svelte (2) — Updated Rendering Principles
- The third article 👉 It’s 2020 and you don’t know Svelte (3) — Dirty Value Detection, DOM Update (to be continued)
- 4 👉 It’s 2020 and You Don’t Know Svelte (4) — Long List Processing (to be continued)
- Article 5 👉 It’s 2020 and you Don’t Know Svelte (5) — A Preliminary Study of Compilation Principles (to be continued)
- Article 6 👉 It’s 2020 and You Don’t Know Svelte (6) — Implementing a Svelte Compiler (to be continued)
Compared to Vue and React, the Svelte update rendering process is very simple, so simple that you only need to read this article to understand it thoroughly.
Update the overall rendering process
How does Svelte update the data and render the results? What does Svelte’s overall update rendering process look like?
Next, we’ll compile a simple example that dives into each line of Svelte’s source code.
Example code:
<main>
<div>
{name}
</div>
<button on:click={onClick}>click</button>
</main>
<script>
let name = 'hello'
function onClick() {
name = name + 's'
}
</script>
Copy the code
Above: When the button is clicked, the name variable is updated and rendered to the DOM node, so what happens behind the scenes? What stages did they go through?
I combed the overall process of packaged Svelte code and found that it was relatively simple, which was illustrated as follows:
It’s important to note that just because Svelte claims to have no run-time code, it doesn’t mean that the packaged product doesn’t include other code at all. Svelte will still pack some tool code, which is much smaller than React and Vue.
-
After the click event occurs, two things happen: 1. The name variable is modified: name=name + s 2. Call? Invalidate method.
-
? The invalidate method in turn calls the make_dirty method, which remembers dirty data and does two complicated things that we’ll cover later.
-
Instead of updating immediately after the data is corrupted, the schedule_update() method pushes the flush callback to the end of the 16ms frame.
-
Flush, when executed, iterates through all components in diry_Components, calling their.p() method. The.p() method is compiled and contains a magic if judgment. If you walk into the judgment body, the setData method is called to update the DOM node
Looking confused? It doesn’t matter, just get a general idea, each process will be explained in detail below
After the Click event occurs
First of all, what we execute in the browser is not the code we wrote, but the Svelte compiled and modified.
Added an instance method as follows:
function instance(? self, ? props, ? invalidate) {
let name = "hello";
let handleClick = (a)= > {
?invalidate(0, name = name + "s");
};
return [name, handleClick];
}
Copy the code
The logic in the handleClick method has been rewritten to reassign name (name = name + “s”) and then add? A call to the invalidate () method.
? invalidate
? The invalidate method is also compiled, leaving only the following logic after the other irrelevant logic is removed:
function ? invalidate(i, value) {
make_dirty(component, i);
}
Copy the code
That is, call the make_dirty() method
make_dirty
The make_dirty method does two things to make the current component dirty: 1. Push the current compoent into the dirty_components array 2. Dirty data is recorded by binary values in order to save memory.
function make_dirty(component, i) {
/ / if the component? .dirty[0]
if(component.? .dirty[0= = =- 1) {
// dirty_components records dirty componentsdirty_components.push(component); schedule_update(); component.? .dirty.fill(0);
}
// The principle of the following code is to record dirty data through binary values
// Don't try to understand, as explained belowcomponent.? .dirty[(i /31) | 0] | = (1 << (i % 31));
}
Copy the code
Schedule_update () is then called
schedule_update
Schedule_update puts a flush method into a promise. then:
function schedule_update() {
resolved_promise.then(flush);
}
Copy the code
The underlying idea is to have the flush method executed while the microtask is executed in this frame
The sequence of tasks that occur within 16ms of a frame
-
Respond to user input events (Scroll/click/key)
-
Javascript execution
-
requestAnimation
/Intersection Observer cb
-
Layout of the Layout
-
Draw the Paint
-
If 16ms is surplus, call requestIdleCallback. If not, it will starve to death
-
Macro task (setTimeout/messagechannel.onMessage)
-
Micro tasks (Promise. Then ())
flush
Flush iterates through all components of the diry_components method, calls the update method, and finally calls the component’s.p() method. The.p() method is compiled,
function flush() {
// If flushing is flushing, exit
if (flushing) {
return;
}
flushing = true;
do {
for (let i = 0; i < dirty_components.length; i += 1) {
constcomponent = dirty_components[i]; update(component.?) ; } flushing =false;
}
function update(?) {
// Suppose? .fragment is not null
if(? .fragment ! = =null{? .update();/ / ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ before_update lifecycle ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~run_all(? .before_update);constdirty = ? .dirty;// All necessary updates must be updated, call p? .fragment && ?.fragment.p(?.ctx, dirty);/ / ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ after_update lifecycle ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
?.after_update.forEach(add_render_callback);
}
}
Copy the code
.p
methods
The.p method is used to update the DOM node with the latest data.
p(ctx, [dirty]) {
// ampersand is a bit operation
if (dirty & 1) {
// set_data updates the dom node's data value
set_data(t1, ctx[0])};if (dirty & 1) {
set_data(t3, ctx[0])}; },Copy the code
Set_data is a native method that encapsulates the DOM (such as innerHtml) and updates the DOM node.
The magic if judgment above is to determine whether the dirty data will affect the corresponding DOM node, and if so, precisely update that DOM node. The reason why method P knows which DOM node to update is that this method records the corresponding relationship between DATA and DOM node through AST and other means during compilation, which will be detailed in the next section.
Overall Flow Section
You may still be confused, but the biggest puzzle is how Svelte updates DOM nodes based on dirty data. To fully understand this logic, be sure to look at the following section.
Svelte dirty data update DOM principle
Any modern front-end framework needs to remember what data is updated, treat the updated data as dirty data, and then calculate the latest DOM state from that dirty data.
Svelte uses a technique called bitMask to track which values are dirty, which data has changed which since the component was last updated.
Bitmask is a technique for storing multiple Boolean values in a single integer, one bit for each data change, generally1
Represents dirty data,0
Indicates clean data.
In plain English, you have four values A, B, C, and D, so binary 0000 0001 means the first value A has changed, 0000 0010 means the second value B has changed, 0000 0100 means the third value C has changed, 0000 1000 means that the fourth D has changed.
In this notation, we can maximize the use of space. Why do you say that?
For example, the decimal number 3 can indicate that A and B are dirty data. First, convert the decimal number 3 to the binary number 0000, 0011.
The first and second values from the left are 1, which means that the first and second values A and B are dirty data. The rest of the bits are 0, which means the rest of the data is clean.
JS restrictions
However, the binary of JS is limited to 31 bits (32 bits, minus 1 bit for symbols).
That is, if Svelte uses the binary storage method, only 31 data can be stored in a Svelte component.
But you can’t do that, can you?
Svelte is stored in an array consisting of 31 bits in binary. If there are more than 31 numbers, the excess goes to the next item in the array.
This array is component.? . Dirty array: 1 bit in binary indicates that the corresponding data has changed and is dirty and needs to be updated. The 0 bits of binary indicate that the corresponding data has not changed and is clean.
To find outcomponent.? .dirty
Above, we talked about component.? Dirty is an array, what is the length of the number?
We simulate a Svelte component that modifies 33 data.
We print it out every timemake_dirty
After thecomponent.? .dirty
, as shown below:
WTF???? There seems to be no pattern.
Svelte uses component.? The random numbers in the dirty array indicate what data needs to be updated.
If we convert all of the values in the array above to binary, look again:
Each bit in each item of the array above, if 1, indicates whether the data is dirty. If it’s dirty, it means it’s updated.
-
The first row [” 0000000000000000000000000000001 “, “0000000000000000000000000000000”], said the first data is dirty, need to update the first data corresponding to the dom node
-
The second row [” 0000000000000000000000000000011 “, “0000000000000000000000000000000”], said the first and the second data are dirty, you need to update the first, second data corresponding to the dom node.
-
…
When the number of data in a component exceeds the limit of 31, an entry is added to the array.
How do I set it to dirty data
In principle, set to dirty data.? The dirty array binary bit is set to 1
If you don’t want to get into the code, skip the section below, “How to Set Up Dirty Data”.
Just know, set it to dirty, which is component.? The dirty array binary bit is set to 1
The Svelte authors do this through a series of bitwise operators
In fact, the source code for this logic is very concise and complex, just like the following line:
component.? .dirty[(i /31) | 0] | = (1 << (i % 31));
Copy the code
What does this code mean? Marks the data represented by the specified bit as dirty
I is the number of variables in CTX, 1 is the first variable, and 2 is the second variable.
(i/31)|0
What you mean?
I/said I to 31, 31 (I / 31) | 0 rather then the results of (31) I/integer.
Math.floor(I /31), which rounds the result of I /31.
component.? .dirty[(i/31)|0]
What you mean?
component.? . Dirty [(31) I / | 0] said component.? .Which of the dirty entries, the array corresponding to I, falls in Component.? . Which entry in the dirty array.
Remember our component.? What does dirty look like when it’s binary?
// component.? .dirty
["0000000000000000000000000000001"."0000000000000000000000000000000"]
Copy the code
If I exceeds the 31-digit limit, the component.? . Add an entry to the dirty array.
1 << (I % 31)
What you mean?
Let’s first discuss the calculation rules for <<. The rule for the left shift is that each bit converted to binary is moved one bit to the left, ending with a zero.
For example, 3 << 2 is calculated by moving all the digits to the left by 2.
The following is the specific calculation process of 3 << 2:
3The binary representation of is0000 0011All the digits move to the left2A into0000 1100In decimal form12(the equivalent of3 * 4So it gets bigger2 çš„2Power of times)Copy the code
It can be understood that the effect of moving all the digits to the left by n is the same as << the left digit, which will be magnified by 2 to the n
Assuming I is 3, the third data in the CTX is changed, 1 << (3%31) computs 8, converted to binary 1000.
| =
What you mean?
Operator: x |= y
Meaning: x = x | y
Copy the code
| = is the bitwise or assignment operator, operation process is to use two operands of a binary, perform bitwise or operation on them, and the results will be assigned to a variable.
Pay attention to oh.
Our initial code looks like this:
component.? .dirty[(i /31) | 0] | = (1 << (i % 31));
Copy the code
To make it easier to understand, we can rewrite it like this:
letitem = component.? .dirty[(i /31) | 0]
item = item | (1 << (i % 31));
Copy the code
As mentioned above, assuming I is 3, 1 << (I % 31) is calculated as 8, If the binary representation of 0000000000000000000000000001000 item binary representation of 0000000000000000000000000000001, | bitwise or after calculation, starting from the right fourth set to 1.
That is, the specified bit is set to 1, regardless of its previous value.
How to Set Dirty Data
All you need to know in this section is that the svelte author puts dirty data in the component.? The binary position of dirty is set to 1.
Which DOM nodes are updated
As we saw in the previous section, Svelte uses component.? .dirty The number of dirty data stored in the array.
So, with the dirty data, Svelte calls the method to update the DOM node in a 16ms frame of microtask. Svelte doesn’t know which DOM nodes to update until the p method is called.
The function of the p method is to determine the DOM node that needs to be updated and call the DOM native method to update it.
To test this, we simulated a Svelte component that modified 33 data points. The compiled product can be easily seen with the excellent online REPL tool provided by Svelte, as shown in the following figure:
The compiled p method looks something like this. Since we didn’t turn on code compression, it looks like there’s a lot of code:
p(ctx, dirty) {
if (dirty[0] & /*name1*/ 1) set_data(t1, /*name1*/ ctx[0]);
if (dirty[0] & /*name2*/ 2) set_data(t3, /*name2*/ ctx[1]);
if (dirty[0] & /*name3*/ 4) set_data(t5, /*name3*/ ctx[2]);
if (dirty[0] & /*name4*/ 8) set_data(t7, /*name4*/ ctx[3]);
if (dirty[0] & /*name5*/ 16) set_data(t9, /*name5*/ ctx[4]);
if (dirty[0] & /*name6*/ 32) set_data(t11, /*name6*/ ctx[5]);
if (dirty[0] & /*name7*/ 64) set_data(t13, /*name7*/ ctx[6]);
if (dirty[0] & /*name8*/ 128) set_data(t15, /*name8*/ ctx[7]);
if (dirty[0] & /*name9*/ 256) set_data(t17, /*name9*/ ctx[8]);
if (dirty[0] & /*name10*/ 512) set_data(t19, /*name10*/ ctx[9]);
if (dirty[0] & /*name11*/ 1024) set_data(t21, /*name11*/ ctx[10]);
if (dirty[0] & /*name12*/ 2048) set_data(t23, /*name12*/ ctx[11]);
if (dirty[0] & /*name13*/ 4096) set_data(t25, /*name13*/ ctx[12]);
if (dirty[0] & /*name14*/ 8192) set_data(t27, /*name14*/ ctx[13]);
if (dirty[0] & /*name15*/ 16384) set_data(t29, /*name15*/ ctx[14]);
if (dirty[0] & /*name16*/ 32768) set_data(t31, /*name16*/ ctx[15]);
if (dirty[0] & /*name17*/ 65536) set_data(t33, /*name17*/ ctx[16]);
if (dirty[0] & /*name18*/ 131072) set_data(t35, /*name18*/ ctx[17]);
if (dirty[0] & /*name19*/ 262144) set_data(t37, /*name19*/ ctx[18]);
if (dirty[0] & /*name20*/ 524288) set_data(t39, /*name20*/ ctx[19]);
if (dirty[0] & /*name21*/ 1048576) set_data(t41, /*name21*/ ctx[20]);
if (dirty[0] & /*name22*/ 2097152) set_data(t43, /*name22*/ ctx[21]);
if (dirty[0] & /*name23*/ 4194304) set_data(t45, /*name23*/ ctx[22]);
if (dirty[0] & /*name24*/ 8388608) set_data(t47, /*name24*/ ctx[23]);
if (dirty[0] & /*name25*/ 16777216) set_data(t49, /*name25*/ ctx[24]);
if (dirty[0] & /*name26*/ 33554432) set_data(t51, /*name26*/ ctx[25]);
if (dirty[0] & /*name27*/ 67108864) set_data(t53, /*name27*/ ctx[26]);
if (dirty[0] & /*name28*/ 134217728) set_data(t55, /*name28*/ ctx[27]);
if (dirty[0] & /*name29*/ 268435456) set_data(t57, /*name29*/ ctx[28]);
if (dirty[0] & /*name30*/ 536870912) set_data(t59, /*name30*/ ctx[29]);
if (dirty[0] & /*name31*/ 1073741824) set_data(t61, /*name31*/ ctx[30]);
if (dirty[1] & /*name32*/ 1) set_data(t63, /*name32*/ ctx[31]);
if (dirty[1] & /*name33*/ 2) set_data(t65, /*name33*/ ctx[32]);
}
Copy the code
Let’s look at it together, but it turns out that this whole mess of code is pretty straightforward: there are 33 if judgments, and if they’re true, setData is called.
The dirty variable in the code above is actually component.? .dirty arrays, as we described above, look back at binary arrays that might look like this:
// dirty === component.? .dirty
["0000000000000000000000000000001"."0000000000000000000000000000000"]
Copy the code
The CTX object in the code above holds data, and it is all up to date: CTX [0] represents the first data, CTX [1] represents the second data…
The set_data method in the above code encapsulates the native method of updating the DOM node by updating the latest data stored in CTX to the DOM node.
Don’t panic. Let’s take line 4 above as an example:
if (dirty[0] & /*name3*/ 4) set_data(t5, /*name3*/ ctx[2]);
Copy the code
The if judgment is dirty[0] &4. The number 4, which appears to be an irregular number, is converted to binary 00000100 (the third digit from left to right is 1), indicating the third dirty data.
Here,if
The judgment condition is: takedirty[0]
( 0000000000000000000000000000001
) and4
(4 converted to binary is0000, 0100,
) doBitwise and
Operation. So we can think about it a little bitBitwise and
When will the operation return1
?
A 1 is returned only if the third bit of the 4 to binary is 0000, and the third bit of the dirty[0] binary is also 1. In other words, the third bit of the dirty[0] binary is also a 1, meaning that the third bit of data is dirty. In simple terms, as long as the third data is dirty, we go to the if judgment and execute set_data(t5, /*name3*/ CTX [2]) to update the DOM node t5.
When we get to this point in our analysis, there are some things we can look at at a higher level: They actually save the corresponding relationship between the 33 variables and the real DOM nodes. If the variables are dirty, Svelte will go into different IF bodies and directly update the corresponding DOM nodes, without the need for complex Virtual DOM DIFF to figure out which DOM nodes to update.
These 30 lines of code are the result of Svelte compiling the Svelte component we wrote. When Svelte is compiling, the corresponding relationship between data and DOM nodes has been analyzed. When data changes, DOM nodes can be updated very efficiently
Update which DOM nodes
When a front-end framework, whether vue or React, updates data, it needs to consider which DOM node to update, i.e., the mapping between the dirty data and the real DOM to be updated.
React uses virtualDom to diff out which DOM nodes are more cost-effective to update.
Svelte DOM is the mapping relationship between data and real DOM, which is calculated by AST and stored in P function at compilation time.
As for Svelte’s dirty value detection, how to encapsulate the method of updating the DOM will be covered in the next section.
Finally, Bytedance is hiring everyone!!
Bytes to beat (hangzhou) | Beijing | Shanghai a lot of hiring. The benefits are great and the salary is second to BAT. No clock at work, afternoon tea every day, unlimited free snacks, three free meals (I read the menu, hairy crab, abalone, scallop, seafood, grilled fish fillet, beef with black pepper, curry, beef and spicy crayfish). Free gym, 16 inch roof with the latest MBP, monthly rental housing supplement. This is really a lot of opportunities, the number of research and development after the expansion of N times, good technical atmosphere, cattle, less overtime, still hesitate what? Send your resume to the email below, now!
Just a small JD, more welcome to add wechat ~
Front end JD
-
Advanced Front-end (Beijing) 👉 job.toutiao.com/s/WXjEDn
-
Front-end cross-end Applications (Beijing) 👉 job.toutiao.com/s/WXBG1s
-
Front-end Intern (Beijing) 👉 job.toutiao.com/s/WXBqne
-
Front-end Intern (Hangzhou) 👉 job.toutiao.com/s/WXM4oX
-
Advanced Front-end (Hangzhou) 👉 job.toutiao.com/s/WXfj3x
-
Advanced Front-end (Shanghai) 👉 job.toutiao.com/s/WX88rF
The JAVA part of JD
- JAVA (Beijing) 👉 job.toutiao.com/s/WX2wPC
Continue to recruit a large number of front-end, server, client, testing, products, internship recruitment are wide to
Add wechat dujuncheng1, you can chat about life, please indicate from nuggets and where to post