Preface
Front-end programmers are either logging or logging on the road almost every day. Recently, they encountered a console.log
in their real project. From this, record the knowledge you need to know in the process of console.log
Is console.log really asynchronous?
console.log
debug during the development process and were "lied" by it. When monitoring some complex objects, the value presented to us by log may not match our expectations, as follows:
At this time, someone directly defined: console.log
is asynchronous! In fact, this problem is mentioned in the asynchronous console section 1.1 of the second part of "JavaScript Central Volume You Don't Know":
There is no specification or set of requirements that specify how the console.* method family works-they are not an official part of JavaScript, but are added to JavaScript by the host environment (refer to the "Type and Syntax" section of this book). Therefore, different browsers and JavaScript environments can be implemented according to their own wishes, which can sometimes cause confusion.
In particular, it should be pointed out that under certain conditions, the console.log(..) of some browsers will not output the incoming content immediately. The main reason for this is that in many programs (not just JavaScript), I/O is a very slow blocking part. Therefore, (from the page/UI perspective) the browser asynchronously processes the console I/O in the background to improve performance, and the user may not even be aware of its occurrence at this time.
We have to know that the object in JS is a reference type, and every time you use an object, you just use the object's reference in the heap.
When we use a.b.c = 2
change the attribute value of the object, its value of c in the heap also becomes 2, and when we do not expand the object to see, console.log
prints the snapshot of the object at that time, so what we see The value of the c attribute is 1 before it has not changed. When the object is expanded, it actually reads the attribute value of the object from the memory again, so when we expand the object, the value of the c attribute is 2.
Conclusion 1
It can be seen that console.log
is not necessarily 100% credible content. Generally, the output of the basic types number
, string
, boolean
, null
undefined
But for reference types such as Object, the above abnormal printout will occur.
If it is changed to console.log(JSON.stringfy(obj))
a snapshot of the current object will be printed, and the ideal result similar to the synchronization can also be obtained. A better solution is to use breakpoints for debugging, where execution stops completely and you can check the current value of each point. Use logging only for serializable and immutable data.
Does console.log affect performance?
Through the above questions, you can reflect on: Why can you print a snapshot of program variables on the console? And when the value is a reference type, will the expanded object go to the memory to read the value of the object again? Shouldn't the variables be recovered by gc after the program has finished running?
The object passed to console.log
cannot be garbage collected ♻️, because the object information needs to be viewed in the development tool after the code is run. So it is best not to console.log any objects
Case number one
This can be confirmed in the console:
var Foo = function() {};
console.log(new Foo());
Open the console Memory debugging:
Case two
<script>
...
mounted () {
this.handleSignCallback({
type: SIGNATURE_TYPES.SIGNATURE_GET_SIGN_STATUS,
success: true,
result: {
longStr: new Array(1000000).join('*'),
someMethod: function () {
}
}
});
},
methods: {
handleSignCallback(data) {
const { type, result, success } = data;
// do something...
console.log(result) // 返回结果打印
if (true) { // 真实项目中用来判断是否满足条件
this.timer = setTimeout(() => {
this.handleSignCallback({
type: SIGNATURE_TYPES.SIGNATURE_GET_SIGN_STATUS,
success: true,
result: {
longStr: new Array(1000000).join('*'),
someMethod: function () {
}
}
})
clearTimeout(this.timer);
}, 1000)
}
}
}
</script>
The real project needs to judge certain logic in the polling. When the conditions are met, it will delay a few seconds to continue to request the background and process some data. Until the returned object does not meet the conditions, I will stop requesting the data. During the period, I want to print and return. So I added console.log(result)
, but if the amount of data is large, the program will crash after a while. Open the debug console -> Memory:
js Heap keeps rising, with no upper limit, until the browser crashes;
But when I commented console.log(result)
the line 060cd5dba2764f, I observed the heap memory again:
At this time, the heap memory usage is basically stable at around 150.
Conclusion 2
First, draw a conclusion to the above problem: console.log
does affect web page performance. The reason, please combine with the following unfolding questions to study in depth.
Development tips
Regarding the second conclusion, everyone should know that, try not to leave console.log
production mode. Maybe everyone is doing this in the company now, but do you really know why? The following is an example of Vue
project can uniformly remove the console.log
:
Install babel-plugin-transform-remove-console
npm i babel-plugin-transform-remove-console --dev
Configure
babel.config.js
plugin// babel.config.js文件 const plugins = [] // 生产环境移除console if(process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test') { plugins.push("transform-remove-console") } module.exports = { presets: [ '@vue/app' ], plugins: [...plugins] }
Perform performance debugging in combination with case two
Here are some analysis combined with the Devtools–>Performance Chrome. The operation steps are as follows:
- Turn on performance recording
- Execute the CG button to create a reference line
- Click the [click] button multiple times to create a new Leaker object
- Execute CG button
Stop recording. Here are some analysis combined with Chrome's Devtools–>Performance. The steps are as follows:
- Turn on performance recording
- Execute the CG button to create a reference line
- do something or create Object
- Execute CG button
- Stop recording
When log mode is turned on:
Note: The test code simulates the polling scenario, the timer is triggered frequently, and the time point js Heap of gc may be a little higher than the baseline, mainly depends on whether the last js Heap is almost the same as the line triggered for the first time
You can see that js Heap (the blue line, the green line is the number of nodes) has been increasing, and my manual garbage collection (gc) is not helpful. Obviously, a memory leak has occurred!
Turn off log mode:
After closing log, observe the situation of js heap memory as above: you can see that js Heap basically recovered to the same level at two points after manual GC.
Test in common scenarios, such as start recording -> gc -> click event, create object (N times) -> gc -> stop recording, and the two gc time points of js Heap basically coincide.
Memory leak troubleshooting method two
Heap Profiling can record a snapshot of the current heap memory (heap) and generate a description file of the object. The description file gives all the objects used by JS at the time, as well as the memory size occupied by these objects, the hierarchical relationship of references, etc. .
When JS is running, there will be stack memory (stack) and heap memory (heap). When we create a new class, the new object is stored in the heap, and the reference to this object is stored in the stack. The program finds this object through the stack reference. For example: var a = [1,2,3]
, a is a reference stored in the stack, and an Array object with content [1,2,3] is stored in the heap.
Open the debugging tool, click the Profiles tab in Memory, select "Take Heap Snapshot", and click the "start" button to take a snapshot of the current JS heap.
The list of objects in the heap is listed in the right view.
constructor: class name
Distance: Reference level distance from the object to the root
Objects Count: the number of objects of this class
Shallow Size: The memory occupied by the object (not including the memory occupied by other objects referenced internally)
Retained Size: The total memory occupied by the object (including ······································ ··············)
Click the black circle in the upper left corner of the above picture, and a second memory snapshot will appear
Switch the frame to 160cd5dba281c7 comparison (comparison) option, this view lists the object differences between the current view and the previous view
New: How many objects were created
Deleted: how many objects were recovered
Delta: the number of newly created objects minus the number of recovered objects
Focus on closures. If #Delta is a positive number, it means that a closure function has been created. If there are no negative numbers in multiple snapshots, it means that the closure has not been destroyed.
Interesting (boring) question
while (true)
console.log("hello... there");
Running the above code, the node consumed 1.5GB of memory in about 3 minutes.
Some people will say, can the infinite loop not cause the memory to soar?
Look at the following:
def hello():
while True:
print "hello world..."
hello()
Python
will always remain at 3.3MB.
Someone might think that console.log
will cause memory leaks, but I said that reference types will cause memory leaks. Here, the string is printed directly. Why is the performance so bad?
Short answer: console output is buffered and asynchronous. And apart from the global memory limit, the buffer is not subject to any restrictions. As a result, it fills up the buffer zone and dies.
More information is at #1741 .
"Buffer" refers to an implicit buffer that is actually an asynchronous operation queue.
- A connected checker client will cause
console.log()
buffer limited number of - When you print faster than the receiver (tty, pipe, file, etc.) can use them,
Libuv
can buffer messages. This is a problem that you can solve by adjusting the writing speed.
Libuv
: Libuv is a high-performance, event-driven asynchronous I/O library
Conclusion 3
During node development, when we write large log entries, writing to a file for logging is by far the best method. The service later reads the log file and aggregates it to the appropriate service.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。