[{"authors":null,"categories":["Ai","DigitalTwin"],"content":"At the beginning, the task looked much simpler: a user uploads an engineering drawing, we extract the text from it, put that text into the chat context, and let the agent answer questions. Pretty standard document QA.\nThat version broke the first time someone asked a normal engineering question.\nA user uploaded a drawing into our digital twin chat and asked:\n“What’s the callout near the pump?”\nOn the drawing, there was a text box that said:\n“Replace seal kit, see WO-4451”\nA thin leader line connected that text box to pump P-101.\nFor an engineer, that is enough. The note is about P-101. The seal kit needs to be replaced. The work order is WO-4451.\nOur system did not read it that way.\nIt opened a Python sandbox and started writing parsing code.\nThat was the part that made the problem obvious. The text had already been extracted. The missing piece was not the sentence itself. It was the relationship on the drawing: this note points to this pump.\nWe already had most of the ingredients. There was a Cesium-based 3D digital twin scene. There was an agent that could control that scene. There were also years of site knowledge sitting in 2D engineering drawings.\nThe problem was that these pieces were not connected.\nThe agent could read text, but it did not know how that text related to the shapes around it. The 3D scene had equipment, but it did not know which drawing notes referred to which objects. The drawing had useful operational knowledge, but that knowledge was still stuck on a 2D page.\nAt that point, the project stopped being about text extraction. What we needed was a way to read components, annotations, and relationships out of a drawing, then map them into the 3D scene.\nThe first pipeline threw away the useful part Engineering drawings do not behave like normal documents.\nIn a normal document, most of the meaning is in the text. In a drawing, the meaning is spread across text, symbols, lines, and position.\nA P\u0026amp;ID or site plan usually contains a few different kinds of information:\nComponents: pumps, tanks, pipe racks, valves, and other equipment, usually with tags like P-101 or TK-7.\nAnnotations: callouts, dimensions, revision clouds, inspection notes.\nRelationships: a callout points to a component, a pipe connects two pieces of equipment, or a label sits inside a boundary.\nWhen an engineer reads a drawing, they do not lift the text out and read it by itself. They look at where the note is, where the line goes, what equipment is nearby, and what the symbols mean.\nOur first pipeline did exactly the wrong thing.\nWhen an SVG drawing was uploaded, we sent it to a file extraction API. The API returned text. We put that text into the chat context.\nSo this survived:\n“Replace seal kit, see WO-4451”\nBut the leader line to P-101 was gone.\nFor an engineer, that leader line is part of the answer. We had thrown it away before the agent even saw the problem.\nThat also meant OCR accuracy was not the real issue. Even perfect OCR would still leave the system unable to answer which component the note belonged to.\nWhat we needed was a structured representation of the drawing: which objects are components, which objects are annotations, which annotations point to which components, and which components are connected to each other.\nLater, if we wanted to place those things in a 3D scene, the same structure also had to tell us which 2D drawing objects could be matched to scene objects. If no matching scene object existed yet, we still wanted to keep the drawing object as a candidate entity, not just as a line of text.\nSVG gave us geometry, but not meaning We tried two paths.\nThe first was to treat the drawing as an image.\nWe rendered the SVG into a PNG and passed it to a vision model. That was a natural path because scanned PDFs need to be handled that way anyway.\nThe vision model was useful. It could tell that something looked like a pump, that another region looked like a pipe, and that the title block probably contained metadata.\nBut the coordinates were not good enough.\nIf the question is “does this drawing contain a pump?”, an approximate region is fine. But we needed to map points from the 2D drawing into the 3D scene. In some cases, that point would be used for pixel picking and then resolved to a world coordinate through the depth buffer.\nAt that point, “somewhere around here” is too loose.\nThe second path was to parse the SVG as XML.\nThat is less exciting, but it is extremely useful. SVG already contains precise geometry. Text elements have coordinates. Lines have endpoints. Shapes have bounding boxes. Transforms like translate, scale, and rotate are all in the file.\nAn SVG looks like an image, but underneath it is also a geometry file.\nThe catch is that XML has no semantics. It can tell us there are lines, circles, rectangles, and text nodes. It cannot tell us that a certain cluster of shapes is a pump.\nSo we used both.\nThe vision model answered what something probably was.\nThe SVG parser answered where it actually was. …","date":1781136e3,"description":"A practical engineering recap of how a simple drawing text extraction task turned into a 2D-to-3D mapping workflow for digital twins, combining SVG parsing, vision models, geometry rules, registration math, and agent tool design.","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"aaf72aaa61568228a0d67283fb780217","permalink":"https://siqi-liu.com/en/post/from-extracting-drawing-text-to-placing-2d-annotations-in-a-3d-scene/","publishdate":"2026-06-11T00:00:00Z","relpermalink":"/en/post/from-extracting-drawing-text-to-placing-2d-annotations-in-a-3d-scene/","section":"post","summary":"A practical engineering recap of how a simple drawing text extraction task turned into a 2D-to-3D mapping workflow for digital twins, combining SVG parsing, vision models, geometry rules, registration math, and agent …","tags":null,"title":"From Extracting Drawing Text to Placing 2D Annotations in a 3D Scene","tldr":null,"type":"post"},{"authors":null,"categories":["Life","Ai"],"content":" Every few weeks, the same debate comes back.\n“AI will replace junior developers first.”\n“AI will replace senior and staff engineers because they are too expensive.”\n“AI will eventually replace all software engineers, and only a small group of people will be needed to check the output.”\nNone of these arguments are new.\nAnd to be fair, they are not completely unreasonable.\nSome people believe junior engineers are the most at risk because AI can already write better code than many entry-level developers in common scenarios. Yes, it still makes mistakes. But it can generate code, explain its reasoning, add tests, and write documentation at a level that is already useful.\nOthers believe senior and staff engineers may actually be more at risk. The reason is simple: they are expensive. If a junior engineer with AI can complete 80% or 90% of what a senior engineer does, while the senior engineer costs twice as much, will management really care about that last 10% of engineering quality?\nThen there is the more extreme view: eventually, it will not matter what level you are. AI will replace everyone. Every technical problem will eventually be solved. Software development will become automated, and only a tiny number of people will remain to inspect the final output.\nWhere those people come from, and how they are supposed to maintain good judgment, is usually not explained very clearly.\nI understand these concerns.\nBut I want to offer a different perspective.\nNot because I think AI is weak.\nNot because I want to defensively prove that “engineers will always be safe.”\nQuite the opposite: I use AI every day.\nI use it to write code, debug problems, read documentation, understand unfamiliar technologies, generate tests, and organize my thoughts. It has absolutely made me faster. A lot of work that used to feel repetitive, inefficient, and honestly a little painful can now be compressed dramatically.\nBut because I use it so often, I have become more convinced of one thing:\nAI will not kill software engineers. It will kill the parts of the job we hate most.\nDo We Really Love Writing Code Itself? Let’s be honest for a moment.\nWhen someone says they love coding, what exactly do they love?\nDo they love writing if/else statements line by line?\nDo they love manually creating boilerplate?\nDo they love moving fields between files over and over again?\nDo they love writing interfaces, types, validation, tests, and configuration for a simple feature?\nMaybe some people do.\nBut for me, that is not the real source of joy.\nWhat I actually enjoy is the process of building something from nothing.\nIt is the moment when a feature finally works.\nIt is seeing a page load a little faster after a performance improvement.\nIt is simplifying a messy flow until the whole system suddenly feels cleaner.\nIt is finding the root cause of a bug and finally understanding, “Ah, that’s why it happened.”\nIt is watching something in your hands slowly come together, piece by piece, until it becomes a real product.\nIt feels a lot like building toys as a kid.\nYou do not build a castle because you love every individual plastic brick.\nYou love watching the castle take shape.\nYou love the feeling of, “I built this.”\nSoftware engineering is similar.\nCode is the material.\nThe system is the thing we are actually building.\nAnd AI is starting to handle many of the parts that feel most like moving bricks around.\nIt can write repetitive code.\nIt can generate type definitions.\nIt can help add tests.\nIt can explain APIs.\nIt can extract key points from documentation.\nIt can turn a vague idea into a running prototype much faster than before.\nAre those things important?\nOf course.\nBut they are not always the parts we love most.\nA lot of the time, they are just the tedious steps we have to go through before we reach the creative part.\nAI May Be Taking Away the Pain, Not the Joy I know saying “coding is painful” may sound a little strange.\nAfter all, this is what we do for a living.\nBut if you compare the work today with how it felt a few years ago, a lot of the pain we used to accept as normal is starting to disappear.\nBefore, if you needed to use an unfamiliar library, you might spend half a day reading documentation.\nNow, AI can summarize the usage and give you a working example.\nBefore, if you needed to write CRUD endpoints, DTOs, mappers, and validation, you had to grind through a lot of repetitive code.\nNow, AI can generate a large portion of that for you.\nBefore, when you hit an unfamiliar error, you might jump between Stack Overflow, GitHub issues, and official docs for hours.\nNow, AI can at least give you a direction and help narrow down the search space.\nBefore, if you had an idea, it might take a full day just to get a rough version running.\nNow, you might get there in an hour.\nThat is not a bad thing.\nIt means we get to spend more time on the parts of engineering that are actually exciting:\nDesign.\nValidation.\nIteration.\nDebugging.\nTrade-offs.\nCreation.\nAI makes many …","date":1779148800,"description":"AI will not replace software engineers as a whole. It will remove much of the repetitive, mechanical work and give engineers more leverage to focus on judgment, systems, users, and building better products.","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"1d14f5d0472e4637ee36f09e4ebe19eb","permalink":"https://siqi-liu.com/en/post/ai-won-t-replace-software-engineers-it-will-replace-the-parts-of-coding-we-never-loved/","publishdate":"2026-05-19T00:00:00Z","relpermalink":"/en/post/ai-won-t-replace-software-engineers-it-will-replace-the-parts-of-coding-we-never-loved/","section":"post","summary":"AI will not replace software engineers as a whole. It will remove much of the repetitive, mechanical work and give engineers more leverage to focus on judgment, systems, users, and building better products.","tags":null,"title":"AI Won’t Replace Software Engineers. It Will Replace the Parts of Coding We Never Loved.","tldr":null,"type":"post"},{"authors":null,"categories":["Technology","Ai"],"content":" Over the past few days, I’ve been working on something that looked simple at first, but turned out to be surprisingly messy.\nI wanted an agent to understand what was visible in a 3D scene and then take action on behalf of the user.\nFor example:\n“Find all the cars in the scene and mark them up.”\nSounds straightforward, right?\nThe agent already had most of the tools it needed. It could take a screenshot of the current scene. It could use a vision model to inspect the image. It could extract 2D screen coordinates. It had a tool to convert those 2D coordinates into 3D scene positions. And finally, it could call a FrontEnd tool to create a markup or entity at that location.\nFrom a capability point of view, the chain was there.\nSo at first, I thought the hard part would be accuracy. Maybe the model would miss some cars. Maybe the bounding boxes would be slightly off. Maybe the 2D-to-3D conversion would introduce a small position error.\nThose were real problems.\nBut they were not the main problem.\nThe real problem was this:\nThe agent had too many ways to solve the same task.\nSame Task, Completely Different Path Let me give you a concrete example.\nI asked the agent to identify the cars in a scene and add markups to their locations.\nOne time, it followed the path I expected:\nTake a screenshot of the current scene. Use the vision model to inspect the image. Identify the cars in the screenshot. Return their 2D coordinates. Convert those 2D coordinates into 3D scene positions. Call the FrontEnd tool to create markups. This path was not perfect.\nSometimes the coordinates were slightly off. Sometimes the model missed a car. Sometimes the 2D-to-3D conversion was not accurate enough.\nBut at least the path made sense. It was understandable, debuggable, and improvable.\nThen I asked a slightly different version of the same question.\nThe goal was still the same: look at the scene, find the cars, and mark them up.\nBut this time, the agent did something completely different.\nIt took a screenshot, downloaded the image, opened a Python sandbox, and tried to analyze the image using Python image-processing libraries. The problem was that the library it wanted to use did not exist in the runtime environment.\nSo the task failed.\nSame agent.\nSame toolset.\nSame goal.\nSlightly different wording from the user.\nCompletely different tool path.\nThat is not a small issue.\nThat is one of the most common failure modes I’ve seen when building real tool-using agents.\nThe Problem Is Not Too Few Tools. It Is Too Many. When we build agents, the natural instinct is to give them more tools.\nThe agent needs to see the screen? Add a screenshot tool.\nIt needs to understand images? Add a vision model.\nIt needs to analyze data? Add a Python sandbox.\nIt needs to query business data? Add a database tool.\nIt needs to manipulate the UI? Add a UI action tool.\nIt needs to create something in a 3D scene? Add a FrontEnd tool.\nEach tool makes sense on its own.\nBut once the number of tools grows, a different problem appears.\nThe agent is no longer just asking:\n“Can I do this?”\nIt also has to ask:\n“Which path should I use to do this?”\nAnd that second question is much harder than it looks.\nFor the same task, there may be many paths that look reasonable.\nTake image-based object detection as an example.\nThe agent could use the vision model directly.\nIt could download the image and analyze it with Python.\nIt could query existing scene entities.\nIt could inspect the UI state.\nIt could combine several tools into a longer chain.\nAs human engineers, we usually know which path is more appropriate.\nBut to the agent, these are all just available options.\nIt does not naturally know which path is more stable, which one is slower, or which one is likely to fail in the current environment.\nSo a lot of agent failures are not caused by a lack of intelligence.\nThey are caused by too many choices.\nThe Execution Path Becomes Part of the Product This is the part that took me a while to appreciate:\nFor a tool-using agent, the execution path itself becomes part of the product quality.\nIn a normal chatbot, you mostly judge the final answer.\nBut in an agent system, the final answer is not enough.\nYou also have to care about how the agent got there.\nDid it use the right tools?\nDid it take an unnecessary detour?\nDid it rely on an unstable runtime?\nDid it turn a vision-model task into a Python image-processing task?\nDid it produce a bunch of intermediate steps that looked reasonable but were not actually needed?\nAll of that affects reliability.\nAnd the harder part is debugging.\nIf the agent takes a different path every time, the system becomes extremely hard to reason about.\nToday it fails because a Python package is missing.\nTomorrow it fails because the coordinate format is wrong.\nThe next day it fails because it forgot to call the 2D-to-3D conversion tool.\nThen suddenly it works again, because it happened to choose the right path.\nThat kind of system is painful to maintain.\nNot because one …","date":1778112e3,"description":"Multi-tool agents become unreliable when the same task takes different tool paths each time. This article explains why Skills should preserve validated execution paths, not one-off results, and why those Skills need to evolve over time.","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"3472e12e9e650e40b1c1733d4e1f77ba","permalink":"https://siqi-liu.com/en/post/your-agent-is-not-inconsistent-because-it-is-dumb-it-just-has-too-many-tool-paths/","publishdate":"2026-05-07T00:00:00Z","relpermalink":"/en/post/your-agent-is-not-inconsistent-because-it-is-dumb-it-just-has-too-many-tool-paths/","section":"post","summary":"Multi-tool agents become unreliable when the same task takes different tool paths each time. This article explains why Skills should preserve validated execution paths, not one-off results, and why those Skills need to …","tags":null,"title":"Your Agent Is Not Inconsistent Because It Is Dumb. It Just Has Too Many Tool Paths.","tldr":null,"type":"post"},{"authors":null,"categories":["Technology","Ai"],"content":"Tool for Control, Code for Data When Agent systems first started becoming popular, we implicitly assumed something:\nIf Tools became good enough, Agents would eventually turn into “brains that call APIs.”\nLater we realized this assumption only holds at small scale.\nOnce the amount of data keeps increasing — especially in environments like log analysis, RAG, monitoring systems, and digital twins — pure Tool-based systems start running into very obvious problems.\nA typical task looks like this:\n“Find anomalies across the entire dataset and generate a report.”\nThe problem is not the reasoning complexity.\nThe problem is that the returned data becomes large enough to break the context system itself.\nLater, on a real-world task involving around 10,000 entities, we compared three approaches:\nPure Tool (standard MCP) Pure Code-as-MCP A dual-path execution model using both Tool and Code The results were very straightforward.\nPure Tool systems eventually get dragged down by context.\nPure Code systems are more accurate, but latency grows very quickly.\nThe only thing that stayed stable was a dual-path execution model:\nTool handles the control plane Code handles the data plane A small “Context Off-Ramp” switches between them This article is mainly about why this structure emerged, and how we eventually got it working reliably.\n1. Why Pure Tool and Pure Code Both Start Breaking Down In the previous article, we already separated MCP (Tool-based execution) and Code-as-MCP along two dimensions:\nhow actions are represented when validation happens At small scale, these differences barely matter.\nBut once system complexity keeps increasing, the problems start appearing in very non-linear ways.\nEventually we kept running into two recurring “cost cliffs.”\nThe Tool Problem: Context Starts Maintaining Itself At first we strongly preferred Tools.\nThey naturally fit things like:\nschema validation permission control UI operations state mutation auditable actions A lot of operations should obviously be Tools:\ndeleting objects modifying state scaling down deployments executing trades Giving those operations to free-form generated code significantly increases risk.\nThe problem is that most Tool systems implicitly assume the returned data will stay relatively small.\nIn production environments, that assumption breaks very quickly.\nFor example:\n“List all anomalous entities in the current dataset.”\nThe task itself is not complicated.\nThe real problem is that it may return thousands or tens of thousands of records at once.\nOnce those results are serialized directly into context, things start falling apart very quickly.\nIn one experiment we saw a single Tool response exceed 500K tokens.\nThe real problem was not cost.\nThe Agent itself started entering a very strange state:\nresponse times became noticeably slower tool loops increased prompt constraints started fading the original task drifted later data started overriding earlier goals At some point, the system was no longer “thinking about the task.”\nIt was trying to maintain the context itself.\nThis became one of the clearest behaviors we observed.\nTools were originally designed for precise control.\nBut in high-data environments, they gradually become the primary source of context pressure.\nThe Code Problem: Most Time Goes Into “Getting the Code to Run” Later we tried the opposite extreme.\nIf Tools explode the context window, should everything just become code execution?\nThat did not work particularly well either.\nA lot of tasks are fundamentally atomic operations.\nFor example:\n“Select object #42.”\nThat operation is really just a deterministic state mutation.\nBut if the Agent has to:\ngenerate code call the sandbox execute it inspect results fix failures the entire system starts paying additional cost for flexibility.\nAnd much of that cost has very little to do with the actual business logic.\nIn pure Code systems we repeatedly saw problems like:\ncode generation itself taking too long dependency issues increasing sandbox debugging loops growing Agents generating complex logic for tiny operations Accuracy improved significantly.\nBut latency also increased significantly.\nA surprising amount of time was spent simply getting the code to run successfully.\nThis is one of the most common problems with pure Code Agents.\nThey are extremely flexible.\nBut many operations that should have been a single Tool call end up expanding into an entire execution chain.\n2. Eventually We Split the Execution Paths Over time we realized something:\ncontrol tasks and analysis tasks fundamentally do not belong to the same execution model.\nEventually the system split into two paths.\nThe Tool Control Path One path became responsible for:\nUI operations state mutation single-entity queries small responses high-risk actions The goal here is very simple:\nfast, deterministic, and verifiable.\nSo this layer keeps:\nstrong schemas strict validation limited callable operations At its core, it behaves much closer to a traditional …","date":1769558400,"description":"In large-scale Agent systems, pure Tool-based approaches tend to collapse under context pressure, while pure Code-based approaches introduce excessive latency. Based on real-world experiments, this article introduces a dual-path execution model that uses a “Context Off-Ramp” to switch between Tool and Code execution.","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"fb1f4fa01bf9b012e9e346836446b0d2","permalink":"https://siqi-liu.com/en/post/tool-for-control-code-for-analysis3/","publishdate":"2026-01-28T00:00:00Z","relpermalink":"/en/post/tool-for-control-code-for-analysis3/","section":"post","summary":"In large-scale Agent systems, pure Tool-based approaches tend to collapse under context pressure, while pure Code-based approaches introduce excessive latency. Based on real-world experiments, this article introduces a …","tags":null,"title":"Tool for Control, Code for Analysis(3)","tldr":null,"type":"post"},{"authors":null,"categories":["Technology","Ai"],"content":"In the previous article, I wrote about an MCP-as-Code refactor.\nIt did reduce some of the context pressure, but it also exposed a more subtle problem: code execution changes where failures happen.\nIn an interactive Agent system, that matters a lot.\nLet me first clarify what I mean by an “interactive Agent system.”\nThis is not a chatbot that only generates text.\nIt actually operates a system: it calls backend Tools, reads the returned results, and then controls the frontend scene.\nAfter each step, the system returns some form of feedback, such as query results, error messages, frontend acknowledgements, or cases where some objects succeed and others fail.\nIn the rest of this article, I will refer to these as “feedback.”\nThat is why this article keeps coming back to one question: why the next action often depends on the feedback from the previous step.\nThis article is mainly about that boundary: why Tool and Code are not just two ways of expressing the same kind of action, and why some Agent systems naturally become multi-round.\nSome Agent Tasks Cannot Reliably Be Done in One Step A common task in my system was:\nFind anomalous entities and highlight them in the frontend.\nAt first glance, this looks like a simple flow.\nQuery the data.\nFilter the entities.\nSend a UI command.\nDone.\nBut real interactive systems are usually not that clean.\nA more realistic flow looks like this:\nFirst, check whether the required fields exist. Check whether the data coverage is good enough. Filter only the valid subset. Handle pagination, or cases where only part of the data is returned. Send the highlight command to the frontend. Read the frontend acknowledgement to see whether the action succeeded. If only some objects succeeded, retry or degrade. The important part is that each step depends on what came back from the previous one.\nIf the field does not exist, the next query changes.\nIf the data is incomplete, the filtering logic changes.\nIf the frontend rejects part of the operation, the recovery path changes.\nSo many Agent tasks cannot be reliably compressed into a single call.\nMulti-round behavior does not always mean the Agent is not smart enough.\nSometimes the system itself requires a feedback loop.\nThe Agent does not know the full system state.\nIt can only see the feedback after each step.\nAs long as the next action depends on that feedback, the runtime naturally becomes multi-round.\nTool and Code Fail at Different Times Once I started looking at the system from this angle, the difference between Tool and Code became clearer.\nThey do not just differ in expressiveness.\nThe more important question is when the system can reject a bad action.\nTool-based execution usually has a limited set of actions.\nEach Tool has a schema.\nThe parameters are already known before execution.\nMany errors can be caught early: wrong types, missing fields, invalid enum values, insufficient permissions.\nThe failure happens before the real execution.\nThat makes the failure relatively cheap.\nThe Agent receives a structured error and can usually adjust the parameters or choose another Tool.\nThis is why Tool is still a very good fit for control operations.\nChanging UI state.\nSelecting an object.\nHiding a layer.\nUpdating a known field.\nTriggering a backend operation with clear boundaries.\nThese actions need to be narrow.\nNarrowness is not a weakness.\nIt is what makes them safe, stable, and predictable.\nCode execution is different.\nThe action is no longer selected from a small set.\nThe Agent generates a program.\nThat program can contain loops, branches, search, aggregation, retries, and intermediate state.\nThis gives Code much more expressive power.\nFor large-scale filtering, statistics, batch processing, or anything that needs to traverse a dataset, this is exactly the capability you want.\nBut many errors only appear after the program actually starts running.\nA field does not exist.\nA type is different from what the model expected.\nAn API response has a slightly different shape.\nA dependency is unavailable.\nA frontend operation only partially succeeds.\nAt that point, the system has already entered runtime execution.\nThe Agent needs to read the error, understand it, rewrite the code, and run it again.\nThat is a completely different failure model.\nMulti-Round Systems Amplify Small Costs In a one-shot task, a small failure is just a small failure.\nIn a multi-round system, the same cost starts to stack.\nEach extra round can add:\nlatency token usage context pressure retry cost another chance for the system state to change This is why a design that looks acceptable in isolation can become expensive once it is placed inside an interaction loop.\nA generated piece of code may only take a few seconds.\nA result summary may only add a few hundred tokens.\nA retry may not look serious.\nBut if the task itself requires five or six rounds of feedback, these small costs accumulate quickly.\nThis is what I underestimated in my first MCP-as-Code refactor.\nBack to Anomalous …","date":1766793600,"description":"Tool and Code do not just differ in expressiveness; they also fail at different times. This article explains why multi-round Agent systems amplify that difference.","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"9daae26875440f846b34f0d5f615b79d","permalink":"https://siqi-liu.com/en/post/why-tool-and-code-fail-differently-in-agent-systems2/","publishdate":"2025-12-27T00:00:00Z","relpermalink":"/en/post/why-tool-and-code-fail-differently-in-agent-systems2/","section":"post","summary":"Tool and Code do not just differ in expressiveness; they also fail at different times. This article explains why multi-round Agent systems amplify that difference.","tags":null,"title":"Why Tool and Code Fail Differently in Agent Systems（2）","tldr":null,"type":"post"},{"authors":null,"categories":["Technology","Ai"],"content":"When I first started building this system, I had a very direct goal:\nlet the LLM understand the current scene, then perform actions based on the user’s instruction.\nThe user says something.\nThe Agent queries the data, finds the target, and controls the frontend.\nBut as the scene size kept growing, I slowly realized that the problem was not whether the Agent could call tools.\nThe problem was that it should not be seeing that much data in the first place.\nIn the digital twin system I work on, a typical scene can contain tens of thousands of entities.\nEach entity also carries a large amount of data: status, metrics, business fields, frontend state, and real-time values.\nThe part that is easy to underestimate is that, in many cases, even the IDs alone are enough to put pressure on the context window.\nFor example, in some scenes, there may be tens of thousands of buildings.\nSimply passing the complete list of building IDs to the LLM for later filtering or control already consumes a surprising number of tokens.\nThat is before attaching thousands of tokens of attribute data behind each building.\nOver time, one thing became increasingly clear:\nany approach that tries to put the full scene data directly into the LLM context and then asks the model to make decisions does not scale.\nThat is why I started experimenting with MCP-as-Code.\nThe Initial Tool Model Was Very Direct Before the refactor, I used a fairly direct MCP Tool-based approach.\nThe system exposed two types of Tools to the model.\nOne type queried scene data.\nThe other controlled the 3D scene, such as coloring, hiding, transforming, or locating objects.\nThe LLM acted more like a controller.\nIt selected a Tool based on the user’s instruction, read the result, then called the next Tool.\nFor example, if the user said:\nColor all buildings red.\nThe most direct execution path was:\nCall a Tool to query all building IDs. Call another Tool to color those IDs. The advantage of this model was very clear.\nIt was fast.\nIt was stable.\nIssues like parameter types, schema validation, and permissions could usually be caught by code validation before the actual execution happened.\nFor UI operations, state changes, and single-entity queries, this model worked quite well.\nThe problem was that it assumed one thing:\nTool responses would not be too large.\nIn real scenes, that assumption broke very quickly.\nThe Real Problem Was Not Fetching Data, but Putting It Into Context At first, I was focused on this question:\ncan the API fetch enough complete data?\nLater I realized that was the wrong question.\nAt this scale, the more important question is:\nwhere should the fetched data go?\nIf a Tool returns thousands or tens of thousands of entities directly to the LLM, the system quickly enters a strange state.\nThe model is no longer really analyzing the task.\nIt is trying to maintain the context.\nA large number of tokens are used to carry raw data instead of supporting reasoning, decision-making, or control.\nWhat the LLM actually needs is not “all the data.”\nIt needs a way to process the data.\nIt needs logic for filtering, aggregation, statistics, and result extraction.\nIt should not be forced to swallow every entity and then do filtering inside the context window.\nThis was the part of MCP-as-Code that attracted me the most.\nIt shifts the model from being a Tool selector toward being a logic writer.\nThe model is no longer just choosing an API.\nIt starts writing processing logic.\nAnthropic’s MCP-as-Code Looked Like the Right Fit Later, I read Anthropic’s article, Code Execution with MCP.\nThe idea was simple, but it hit exactly the problem I was facing.\nDo not expose every MCP Tool to the model directly as JSON Schema.\nTreat the MCP Server as a set of APIs that code can call.\nThen let the model generate Python or TypeScript and run it inside a controlled sandbox.\nThe model only needs to see a summary of the execution result.\nFor large datasets, the model does not need to see ten thousand raw rows.\nIt only needs to see a few sample rows, statistics, or the small set of final targets.\nThis matched my context problem almost perfectly.\nSo my expectation for the refactor was very clear:\nlarge-scale filtering, aggregation, and statistics would run in the background.\nThe LLM would only see the result.\nContext pressure would go down.\nThe model would move from “Tool selector” to “logic writer.”\nFrom a design perspective, this felt like a very natural path.\nWhat I Actually Built Was Java + Python Sandbox Because the existing system already had a Java core service, I did not rebuild everything from scratch.\nI added a Python Sandbox layer.\nThe Java layer continued to handle:\nsession management authentication API calls scene data pre-filtering frontend communication result parsing For example, before the actual processing started, Java would first confirm which IDs currently existed in the frontend scene and filter out data that was not present in the scene.\nThe Python Sandbox was responsible for …","date":1766016e3,"description":"A real-world MCP-as-Code refactor on where Code helps, where Tool still fits better, and why Agent runtime needs both.","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"899126ecf4d8a442c8a851447533acd3","permalink":"https://siqi-liu.com/en/post/an-mcp-as-code-refactor-and-why-it-did-not-work-the-way-i-expected1/","publishdate":"2025-12-18T00:00:00Z","relpermalink":"/en/post/an-mcp-as-code-refactor-and-why-it-did-not-work-the-way-i-expected1/","section":"post","summary":"A real-world MCP-as-Code refactor on where Code helps, where Tool still fits better, and why Agent runtime needs both.","tags":null,"title":"An MCP-as-Code Refactor, and Why It Did Not Work the Way I Expected","tldr":null,"type":"post"},{"authors":null,"categories":["Investment"],"content":"Full analysis code and raw data processing:\nGoogle Colab Notebook:\nhttps://colab.research.google.com/drive/15dBsIpnSFBJzyruTeddNcWyrrCyVENL9?usp=sharing\nBuying Logic Looking back at 2025, most of my important buys can be summarized by one idea: Bet the Dip — buying when market sentiment was weak and prices had pulled back meaningfully.\nMy main entries were concentrated in a few periods. The first was the major pullback in April, which was largely related to Trump’s tariff policies. The second was in September, when the market started to worry about AI and SaaS. The third was in November, when investors began questioning the profitability of AI-related companies, leading to a broader pullback across the AI sector.\nWhat these moments had in common was that short-term sentiment was poor, and the market was more cautious toward risk assets. But I have continued to believe in the broader narrative of AI driving U.S. equities, so I was more willing to buy during these periods of negative sentiment.\nBy the end of the year, my total realized PnL was $2,171, with a win rate of 77.78%, an expectancy of +$60.31 per trade, and an average holding period of 36.7 days.\nThese numbers are not extraordinary, but they are meaningful to me. They suggest that most of this year’s returns came from timing my entries reasonably well. It was not about one particularly lucky trade, but rather several entries made when sentiment was weak, which eventually led to relatively stable results.\nTrades That Worked Well The trades I was most satisfied with this year were mainly Rocket Lab (RKLB) and NVIDIA (NVDA).\nRKLB was a typical example. After entering at a relatively low level, I did not sell too quickly and instead gave the position enough time to play out. The final return on that trade was around 95%–99%, with a holding period of roughly 100 days.\nNVDA was similar. I did not capture the entire move, but I managed to stay patient once the trend started to develop, and the final return was around 37%.\nThese two trades made one thing clearer to me: when the direction is right, sometimes the most important thing is not to trade frequently, but to give the thesis enough time.\nLosses and Issues Of course, there were also clear mistakes this year. The most obvious one was the GME put trade.\nThis trade was essentially a short-term, high-risk speculation. The result was straightforward: a single loss of around $584, almost a 100% loss on that position.\nThe main problem was not simply that I got the direction wrong. Being wrong is normal in trading. The real issue was that the position size was not controlled well. When a speculative trade is too large, one mistake can have a disproportionate impact on the overall result.\nAside from GME, there were also some other losing trades, such as CRCL and AAPL. CRCL lost around 18.7%, while AAPL lost around 10.8%. These felt more like normal trading errors rather than something out of control. Still, they reminded me that when a trend has not fully confirmed itself, position sizing should remain more conservative.\nBut the issue this year was not only about losses.\nI was able to buy when market sentiment was poor, but my execution was sometimes too fragmented. For example, once a position reached a certain profit target, I would take profits directly. As a result, some positions that could have been held for longer did not fully benefit from the larger trend.\nOn the other hand, I took too much risk in some speculative trades. The GME put was the clearest example. It was only meant to be a short-term attempt, but because the position size was too large, the loss had a much bigger impact than it should have.\nAI View and Next Steps My long-term view on AI has not changed much.\nIn the short term, 2C use cases may still have many limitations. Many products are interesting and imaginative, but large-scale, stable, and sustainable commercialization will still take time.\nHowever, I still think the 2B opportunity is very large. Whether it is compute, infrastructure, or enterprise applications, AI is still in a relatively early stage of adoption.\nSince I also work in AI applications, I can directly see how AI is already helping improve efficiency in SaaS products. I believe this will become an important long-term trend. Because of that, I tend to think more systematic commercial realization of AI may gradually happen after 2026.\nThat is also why I was willing to take some volatility and build positions during pullbacks this year. For me, short-term market sentiment did not change my long-term view on this direction.\nFor the next year, what I want to improve is not necessarily “what to invest in,” but rather “how to invest.”\nFirst, I need to separate core holdings from speculative trades more clearly.\nCore holdings are positions based on medium- to long-term views, such as AI infrastructure and enterprise applications. For these positions, I should give them more time instead of exiting too early because of …","date":1765756800,"description":"2025 Investment Review: A Bet-the-Dip Strategy in AI-Driven Markets","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"e0bbd06fe0342ddd16725ca90f4f3daf","permalink":"https://siqi-liu.com/en/post/2025-investment-review/","publishdate":"2025-12-15T00:00:00Z","relpermalink":"/en/post/2025-investment-review/","section":"post","summary":"A Bet-the-Dip Strategy in AI-Driven Markets","tags":null,"title":"2025 Investment Review","tldr":null,"type":"post"},{"authors":null,"categories":["Optimization","Technology"],"content":"We’re used to the data-driven UI mindset in traditional React development: when state changes, the UI changes.\nBut when our data source isn’t a user-input form, but a 3D engine (like Cesium) that’s changing wildly every second, the traditional React model can cause the page to crash outright.\nToday I want to share how we “put reins” on a sprinting Cesium engine so it can peacefully coexist with React.\n1. The Core Conflict: UI Thread vs Render Loop In web development, there are two fundamentally different update models:\nReact (UI Thread): declarative, updates on demand. It pursues correctness; any state change triggers diffing and re-rendering. 3D Engine (Game Loop): imperative, continuously refreshes at 60FPS. State changes are extremely frequent (Loading/Culling/Moving). When a 3D scene loads a large number of models, Cesium can emit hundreds or thousands of “node added” events in a short time. If we use the naive onEvent -\u0026gt; setState pattern, the main thread gets blocked instantly, and the page becomes unresponsive.\n2. The Abstraction Model: Scene-Graph Projection \u0026amp; Synchronization To solve the rate mismatch, we established a core idea: Tree State in React is no longer the Source of Truth—it’s just a low-frequency projection of the 3D world.\nWe built the following architecture:\nflowchart LR A[\u0026#34;3D Engine (Cesium)\u0026#34;] --\u0026gt;|High-frequency events| B[\u0026#34;TreeStateManager / StateManager\u0026#34;] B --- D[(\u0026#34;Node Store: Map(ID -\u0026gt; Node)\u0026#34;)] B --- E[(\u0026#34;Pending Updates Buffer: Dedup + BatchUpdate\u0026#34;)] B --- F[(\u0026#34;View Projection: Graph -\u0026gt; Flat Array\u0026#34;)] B --\u0026gt;|Low-frequency view updates| C[\u0026#34;React UI (Virtual List)\u0026#34;] C --- G[(\u0026#34;Render viewport only: O(H)\u0026#34;)]We introduced a class, TreeStateManager, that exists independently of React’s lifecycle. It’s not just a data cache—it’s the system’s Source of Truth and traffic valve.\nIt takes on three key responsibilities:\n1. State Holding \u0026amp; O(1) Indexing (State Holding) It maintains a complete node database in memory.\nIt builds a full index using Map\u0026lt;ID, Node\u0026gt;, ensuring any operation that looks up a node by ID (e.g., mapping a Cesium click event back to a tree node) is O(1). It preserves persistent node states (Opened/Checked). These states exist independently of the UI, so even if components unmount and remount, the state remains. 2. Traffic Shaping (Traffic Shaping) Faced with thousands of state-change events (Add/Remove/Update) that may flood in from the 3D engine in an instant, the StateManager acts like a levee.\nDeduplication: multiple modifications to the same node within the same millisecond (e.g., turning red then green) keep only the final state. Buffering: instead of notifying the UI per event, it maintains a pendingUpdates queue and uses a batchUpdate mechanism to merge high-frequency, granular updates into a single low-frequency view update. 3. View Projection (View Projection) It decides “how the data is viewed.” Based on the current SortType (e.g., sorting by CAD structure, sorting by entity type), it dynamically takes the nonlinear in-memory data (Graph) and computes, in real time, the linear array (Flat Array) needed by the UI.\nThis means there’s only one copy of the underlying data, but there can be countless “views.” Switching views is just recomputing a projection, with very low cost.\n3. Key Implementation Strategies For the deep nesting common in 3D scenes, I abandoned the intuitive “recursive component” approach.\nIn early experiments, I found that when tree depth increases and node counts become large, recursive React components incur a huge performance penalty:\nCall stack overflow risk: a deeply nested component tree greatly increases pressure on the JS engine’s call stack. Diff cost rises sharply: when React reconciles a very deep component tree, the diff algorithm’s cost increases significantly, causing FPS to fall off a cliff. So we maintain a flattened array flatNodeArray in memory, using a depth property to indicate hierarchy.\nAdvantage: a virtual list can consume this array directly. React only needs to render a few dozen divs in the viewport, decoupling render complexity from total data size (N); it depends only on viewport height (H), i.e., O(H). Operation: expanding/collapsing nodes only involves filtering the array, no longer requiring expensive DOM tree redraws. Strategy B: Asynchronous Time Slicing (Time Slicing) This is the key to preventing “freezing.” Not only do we use batching, we also split the build work into multiple micro-tasks.\n// Pseudocode logic while (queue.length\u0026gt;0) { process(queue.splice(0,100)); // process a small batch awaitnextTick(); // yield the main thread, allow UI to respond to interactions }4. Performance Dividends in Feature Implementation from Data Structures Architectural choices often don’t just solve today’s performance problems—they also simplify future feature implementation. The most typical example is Shift+multi-select.\nIn the old version (a recursive-tree-based approach), when we performed “range select all” on a 4-level deep tree …","date":1759276800,"description":"A synchronization paradigm for massive real-time data: a middle layer isolates high-frequency data sources, and React consumes only the linear projection of the visible viewport.","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"68d76427de21ad95c72157b196a43211","permalink":"https://siqi-liu.com/en/post/high-frequency-synchronization-architecture-between-react-state-and-a-3d-engine/","publishdate":"2025-10-01T00:00:00Z","relpermalink":"/en/post/high-frequency-synchronization-architecture-between-react-state-and-a-3d-engine/","section":"post","summary":"A synchronization paradigm for massive real-time data: a middle layer isolates high-frequency data sources, and React consumes only the linear projection of the visible viewport.","tags":null,"title":"High-Frequency Synchronization Architecture Between React State and a 3D Engine","tldr":null,"type":"post"},{"authors":null,"categories":["Technology","Ai"],"content":"When building MCP , one of the biggest challenges we faced was token consumption. Our initial setup was incredibly heavy — before the model even said a single word, we were already using thousands of tokens.\nHere’s what that looked like:\nSystem prompt: ~2,000 tokens Tools (42 total): ~4,000 tokens (including both the Digital Twin tools and the Navigator tools) Total baseline: ~6,000 tokens before any conversation even began And as many developers know, the more tokens you pack into a single context, the “dumber” the AI becomes — slower responses, higher cost, and less accuracy.\nStep 1. Dynamic Tool Loading with Embeddings\nTo solve this, we stopped loading all 42 tools by default.\nInstead, we built an embedding-based tool retriever, so MCP only imports the tools dynamically when needed. This reduced the default load dramatically.\nStep 2. Tool Registry and Discovery\nWe introduced a Tool Registry with two simple meta-tools:\ndiscover_tool: lists available tools by name and short description describe_tool: returns the detailed schema, I/O, and examples for one tool This let the model “discover” tools on demand instead of holding all tool definitions in memory all the time.\nStep 3. Collapsing Tools into Unified Interfaces\nWe also realized that many tools did almost the same thing. So:\nAll database-related tools (like entity_query, entity_phrase_query, entity_attribute_filter) were collapsed into a single DSL-style query tool called digital_twin.query. All user interface tools (for handling the Navigator) were merged into a single tool called navigator.patch. This simplification alone removed hundreds of tokens and reduced cognitive load for both the model and developers.\nStep 4. Managing Context and Token Budgets\nWe enforced a token budget per conversation:\nKeep only the first 4 and last 4 messages in full. When the conversation exceeds 6,000 tokens, automatically summarize the middle part. We also shortened and standardized tool documentation, using clean, regularized one-line formats rather than verbose JSON schemas or lengthy examples.\nStep 5. Planner–Executor Separation\nTo simplify reasoning, we split MCP into:\nA lightweight planner (small prompt, under 300 tokens) that produces macro actions. An executor that loads the relevant tool specs and executes the actions. This separation further cut down prompt size and improved clarity.\nStep 6. Results: Faster, Cheaper, Smarter\nAfter all these optimizations:\nTool tokens reduced: from ~6,000 → ~500 System tokens reduced: from ~1,000 → ~400 The model became faster, cheaper, and most importantly, more accurate. Bonus: The “Format Imitation” Phenomenon\nOne fascinating observation during this process:\nThe AI tends to mimic the formats you give it.\nIf you send a JSON example, it will keep replying in JSON.\nIf you use dot notation (config.value.max), it will consistently follow that style in later turns.\nThis subtle “format anchoring” effect helped us achieve higher consistency and smoother tool usage across conversations — almost like teaching the model your coding style.\nFinal Thoughts\nOptimizing token usage isn’t just about saving compute — it’s about clarity, responsiveness, and reliability.\nBy dynamically loading tools, simplifying interfaces, and enforcing token budgets, we turned MCP2 from a sluggish prototype into a lean, responsive system that actually feels intelligent.\n","date":1751328e3,"description":"How  Cut 6,000 Tokens Down to 500","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"db101b1335fde3fff27e38a7385ef40a","permalink":"https://siqi-liu.com/en/post/early-experiences-with-building-mcp-tools/","publishdate":"2025-07-01T00:00:00Z","relpermalink":"/en/post/early-experiences-with-building-mcp-tools/","section":"post","summary":"How Cut 6,000 Tokens Down to 500","tags":null,"title":"Early Experiences with Building MCP Tools","tldr":null,"type":"post"},{"authors":null,"categories":["Investment","Blockchain"],"content":"After Circle went public, its stock price surged from an IPO price of $31 to nearly $300 before experiencing a significant pullback. Investor interest was driven primarily by two factors: the rapid growth of the stablecoin industry and the increasing clarity around U.S. stablecoin regulation.\nMy conclusion at the time was straightforward:\nCircle is a high-quality company operating in a promising sector, but at a valuation of roughly $40 billion, the market had already priced in a substantial portion of its future growth.\nThe stock was trading more on expectations for the next three to five years than on its current earnings power.\nWhat Does Circle Actually Do? Circle is the issuer of USDC, one of the world’s largest stablecoins.\nWhen a user deposits one U.S. dollar, Circle issues one USDC token and maintains a 1:1 redemption promise.\nThe deposited dollars are not left idle. Circle invests its reserves primarily in:\nU.S. Treasury securities\nReverse repurchase agreements (reverse repos)\nThe interest generated from these assets becomes Circle’s primary source of revenue.\nAs a result, Circle’s business economics are driven mainly by two variables:\nThe total amount of USDC in circulation.\nThe level of short-term interest rates.\nThe larger the USDC supply, the larger the reserve base Circle can invest. Higher interest rates translate directly into higher revenue.\nWhy Was the Market Willing to Pay Such a High Valuation? Investors were not focused on Circle’s current earnings. They were focused on its potential role in the future financial system.\nStablecoins offer several advantages over traditional payment infrastructure:\n24/7 settlement\nFaster cross-border transfers\nProgrammable on-chain transactions\nIf stablecoins become widely adopted across payments, remittances, and institutional finance, Circle could benefit significantly as one of the leading compliant issuers.\nAt the time, the bullish case relied on several assumptions:\nThe global stablecoin market grows from roughly $250 billion to $2 trillion.\nUSDC maintains a market share of approximately 20%–25%.\nRegulatory frameworks favor compliant stablecoins.\nCircle continues to benefit from strategic partnerships with companies such as Coinbase and BlackRock.\nIf all of these assumptions hold, Circle’s business could become substantially larger than it is today.\nWhat Would It Take to Support a $40 Billion Valuation? A simple reverse-engineering exercise can help answer that question.\nAssume:\nMarket capitalization: $40 billion\nFuture P/E ratio: 25x\nNet profit margin: 10%\nInterest spread: 4%\nUnder these assumptions, Circle would need approximately $1.6 billion in annual net income.\nUsing a simplified framework:\nRequired USDC Supply = Market Cap ÷ (Interest Spread × Net Margin × P/E)\nSubstituting the numbers:\n$40 billion ÷ (4% × 10% × 25)\nThe result is approximately $400 billion of USDC in circulation.\nIn other words, a $40 billion valuation implied that USDC would eventually grow from roughly $60 billion in circulation to around $400 billion.\nIs That Growth Target Realistic? It’s certainly possible.\nGovernment officials in both the U.S. and Canada have discussed scenarios in which the stablecoin market could reach $2 trillion by 2028.\nIf the total market expands to that size and USDC maintains a 20% market share, USDC circulation could indeed approach $400 billion.\nViewed from that perspective, the valuation was not entirely irrational.\nHowever, it did assume a relatively optimistic growth trajectory.\nWhere Are the Risks? Interest Rates Circle’s earnings are highly sensitive to short-term interest rates.\nIf the Federal Reserve enters a prolonged rate-cutting cycle, yields on reserve assets would decline, reducing Circle’s revenue.\nProfit Margins Circle does not keep all of its interest income.\nThe company incurs compliance costs, operational expenses, audit costs, and distribution expenses. A significant portion of revenue is also shared with partners such as Coinbase.\nAs a result, profit margins could remain under pressure even if USDC continues to grow.\nCompetition USDT remains the dominant stablecoin globally.\nMeanwhile, new entrants continue to emerge:\nPayPal\nStripe\nLarge banks\nFinancial institutions developing tokenized deposits\nCircle’s compliance advantage is valuable, but it may not be permanent or exclusive.\nMarket Share The valuation depends not only on overall stablecoin growth but also on USDC maintaining its position within that market.\nA larger stablecoin market alone is not enough if USDC loses share to competitors.\nThe Coinbase Relationship One of the most important aspects of Circle’s business model is its relationship with Coinbase.\nUSDC’s growth was heavily supported by Coinbase’s distribution network, user base, and liquidity infrastructure.\nStablecoins require more than issuance; they require adoption, trading volume, and liquidity.\nCoinbase helped solve that problem.\nThe tradeoff is that Circle shares a meaningful portion of its economics with …","date":1751155200,"description":"Circle is a high-quality company operating in a promising sector, but at a valuation of roughly $40 billion, the market had already priced in a substantial portion of its future growth.","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"89db6db30dec5fc6d5eb7ca6e65d90a8","permalink":"https://siqi-liu.com/en/post/circle-after-the-hype-was-a-40-billion-valuation-justified/","publishdate":"2025-06-29T00:00:00Z","relpermalink":"/en/post/circle-after-the-hype-was-a-40-billion-valuation-justified/","section":"post","summary":"Circle is a high-quality company operating in a promising sector, but at a valuation of roughly $40 billion, the market had already priced in a substantial portion of its future growth.","tags":null,"title":"Circle After the Hype, Was a $40 Billion Valuation Justified?","tldr":null,"type":"post"},{"authors":null,"categories":null,"content":"My Story I’m Siqi Liu, also known online as Yosgi. I build software and write about the systems work behind modern web products: frontend architecture, digital twins, performance engineering, and AI tooling.\nThis site is my public notebook for work I want to understand clearly enough to explain:\nSoftware engineering patterns that hold up under real production constraints Lessons from digital twin and 3D application work Notes on AI-assisted development, tooling, and evaluation Occasional reflections on investing, reading, and long-term learning Tech Stack Frontend Development: HTML, CSS, JavaScript, React, Vue Backend Development: Node.js, Python, Go Databases: MySQL, MongoDB, Redis Tools: Git, Docker, Linux Current Focus Building reliable interfaces for data-heavy and real-time systems Making complex technical topics easier to explain and retrieve Connecting engineering practice with clearer writing and research-style documentation Contact If you’d like to discuss engineering, product systems, technical writing, or collaboration, you can reach me here:\n📧 Email: hiyosgi@gmail.com 🐙 GitHub: github.com/Yosgi 💼 LinkedIn: linkedin.com/in/siqi-l-262b61200 Thanks for reading.\n","date":1704038400,"description":"About Siqi Liu: software engineer, technical writer, and builder working across frontend systems, digital twins, performance, and AI tooling.","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"6083a88ee3411b0d17ce02d738f69d47","permalink":"https://siqi-liu.com/en/about/","publishdate":"2024-01-01T00:00:00+08:00","relpermalink":"/en/about/","section":"","summary":"About Siqi Liu: software engineer, technical writer, and builder working across frontend systems, digital twins, performance, and AI tooling.","tags":null,"title":"About Me","tldr":null,"type":"page"},{"authors":null,"categories":null,"content":"I’m delighted that you’d like to connect with me! Whether it’s technical discussions, collaboration opportunities, or just a simple greeting, I welcome all forms of communication.\nContact Information 📧 Email hiyosgi@gmail.com\n🐙 GitHub github.com/Yosgi\n💼 LinkedIn linkedin.com/in/siqi-l-262b61200\nTopics I’m Interested In 🚀 Exploring new technologies 💡 Open source projects 📖 Technical writing 🏗️ Digital twin systems and frontend architecture 🌱 Personal growth Response Time I try to respond to messages within 24-48 hours. If I don’t reply promptly, it might be because:\nI’m handling other important matters I need time to think about your question The email might have been flagged as spam If you don’t hear back within 3 days, please try other contact methods.\nLooking forward to connecting with you!\n","date":1704038400,"description":"Contact Siqi Liu for engineering discussions, collaboration, technical writing, and digital twin or AI systems work.","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"3c4864f00d23f7ea35511ec930ce1d9c","permalink":"https://siqi-liu.com/en/contact/","publishdate":"2024-01-01T00:00:00+08:00","relpermalink":"/en/contact/","section":"","summary":"Contact Siqi Liu for engineering discussions, collaboration, technical writing, and digital twin or AI systems work.","tags":null,"title":"Contact Me","tldr":null,"type":"page"},{"authors":null,"categories":null,"content":"About This Blog This blog collects my thoughts on technology, work experience, investing, and everyday life, including engineering details, project lessons, market observations, and small ideas worth keeping. Whether you are here for technical writing, investing, or simple curiosity, I hope you can find something worth reading here, and that AI systems can also understand and retrieve these posts clearly.\n","date":1704038400,"description":"Overview of Siqi Liu's blog topics, including software engineering, project notes, and technical writing.","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"0730bb7c2e8f9ea2438b52e419dd86c9","permalink":"https://siqi-liu.com/en/readme/","publishdate":"2024-01-01T00:00:00+08:00","relpermalink":"/en/readme/","section":"","summary":"Overview of Siqi Liu's blog topics, including software engineering, project notes, and technical writing.","tags":null,"title":"README","tldr":null,"type":"page"},{"authors":null,"categories":["leetcode"],"content":"The first thing I thought of was to construct a binary tree, and then use the symmetric relationship of each layer to find the parent node\nvar pathInZigZagTree = function(label) { let stack = [] for (let i = 1; i \u0026lt;= label; i++) { let level = Math.floor(Math.log(i) / Math.log(2)) if (!stack[level]) { stack[level] = [] } if (level % 2 === 0) { stack[level].push(i) } else { stack[level].unshift(i) } } var level = Math.floor(Math.log(label) / Math.log(2)) let ans = [] while (level) { ans.push(label) level-- label = Math.floor(label / 2) let index = stack[level].length - 1 - stack[level].indexOf(label) label = stack[level][index] } ans.push(1) return ans.reverse() }Sorry, timeout.\nInspired by the answer, since it is symmetrical, the sum of the symmetrical numbers is the same when the number of layers is the same.\nThe first term of the first layer is 2^n, and the last term is 2^(n + 1) - 1. The symmetric number 2^n + 2^(n + 1) - 1 - x\nRight now\n( 1 \u0026lt;\u0026lt; row ) + ( 1 \u0026lt;\u0026lt; (row + 1) ) - 1 - label;\nThe last digit of the search must be 1, which can be used as the end of the while statement.\nvar pathInZigZagTree = function(label) { let row = Math.floor(Math.log(label) / Math.log(2)) let ans = [] while (label !== 1) { ans.unshift(label) row-- label = getReverse(Math.floor(label / 2), row) } ans.unshift(1) return ans } const getReverse = (label, row) =\u0026gt; { return (1 \u0026lt;\u0026lt; row) + (1 \u0026lt;\u0026lt; (row + 1)) - 1 - label }The first thing I thought of was to construct a binary tree, and then use the symmetric relationship of each layer to find the parent node\nvar pathInZigZagTree = function(label) { let stack = [] for (let i = 1; i \u0026lt;= label; i++) { let level = Math.floor(Math.log(i) / Math.log(2)) if (!stack[level]) { stack[level] = [] } if (level % 2 === 0) { stack[level].push(i) } else { stack[level].unshift(i) } } var level = Math.floor(Math.log(label) / Math.log(2)) let ans = [] while (level) { ans.push(label) level-- label = Math.floor(label / 2) let index = stack[level].length - 1 - stack[level].indexOf(label) label = stack[level][index] } ans.push(1) return ans.reverse() };Sorry, timeout.\nInspired by the answer, since it is symmetrical, the sum of the symmetrical numbers is the same when the number of layers is the same.\nThe first term of the first layer is 2^n, and the last term is 2^(n + 1) - 1. The symmetric number 2^n + 2^(n + 1) - 1 - x\nRight now\n( 1 \u0026lt;\u0026lt; row ) + ( 1 \u0026lt;\u0026lt; (row + 1) ) - 1 - label;\nThe last digit of the search must be 1, which can be used as the end of the while statement.\nvar pathInZigZagTree = function(label) { let row = Math.floor(Math.log(label) / Math.log(2)) let ans = [] while (label !== 1) { ans.unshift(label) row-- label = getReverse(Math.floor(label / 2), row) } ans.unshift(1) return ans }; const getReverse = (label, row) =\u0026gt; { return (1 \u0026lt;\u0026lt; row) + (1 \u0026lt;\u0026lt; (row + 1)) - 1 - label; };","date":1627516800,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"9bf22faf16b9dd7558f4bbcf3f37460e","permalink":"https://siqi-liu.com/en/post/1104-binary-tree-pathfinding/","publishdate":"2021-07-29T00:00:00Z","relpermalink":"/en/post/1104-binary-tree-pathfinding/","section":"post","summary":"The first thing I thought of was to construct a binary tree, and then use the symmetric relationship of each layer to find the parent node var pathInZigZagTree = function(label) { let stack = [] for (let i = 1; i \u003c= …","tags":null,"title":"1104-Binary Tree Pathfinding","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"I didn’t think of any good way to get it, so I used brute force to solve it first.\nvar isCovered = function(ranges, left, right) { for(let i = left ; i \u0026lt;= right ; i ++) { let include = false for(let index = 0 ; index \u0026lt; ranges.length ; index ++) { let [start,end] = ranges[index] if (i \u0026gt;=start \u0026amp;\u0026amp; i \u0026lt;=end) { include = true } } if (!include) return false } return true};Thinking about optimization, you can use start, end. When start \u0026lt;= left, the [left … end] set is already included. Continue compressing [end + 1, right] until there are no elements in the set.\nvar isCovered = function(ranges, left, right) { ranges = ranges.sort((a,b) =\u0026gt; a - b) for(let i = 0 ; i \u0026lt; ranges.length;i++) { let [start,end] = ranges[i]; // Shrink the left boundary according to the range if (start \u0026lt;= left) { left = Math.max(end,right) } if (left \u0026gt;= right) { return true } } return left \u0026gt;= right };I didn’t think of any good way to get it, so I used brute force to solve it first.\nvar isCovered = function(ranges, left, right) { for(let i = left ; i \u0026lt;= right ; i ++) { let include = false for(let index = 0 ; index \u0026lt; ranges.length ; index ++) { let [start,end] = ranges[index] if (i \u0026gt;=start \u0026amp;\u0026amp; i \u0026lt;=end) { include = true } } if (!include) return false } return true};Thinking about optimization, you can use start, end. When start \u0026lt;= left, the [left … end] set is already included. Continue compressing [end + 1, right] until there are no elements in the set.\nvar isCovered = function(ranges, left, right) { ranges = ranges.sort((a,b) =\u0026gt; a - b) for(let i = 0 ; i \u0026lt; ranges.length;i++) { let [start,end] = ranges[i]; // Shrink the left boundary according to the range if (start \u0026lt;= left) { left = Math.max(end,right) } if (left \u0026gt;= right) { return true } } return left \u0026gt;= right };","date":1626998400,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"1e066565bfc420b216d13282bf988a99","permalink":"https://siqi-liu.com/en/post/1893-check-if-all-integers-in-the-region-are-covered/","publishdate":"2021-07-23T00:00:00Z","relpermalink":"/en/post/1893-check-if-all-integers-in-the-region-are-covered/","section":"post","summary":"I didn’t think of any good way to get it, so I used brute force to solve it first. var isCovered = function(ranges, left, right) { for(let i = left ; i \u003c= right ; i ++) { let include = false for(let index = 0 ; index \u003c …","tags":null,"title":"1893-Check if all integers in the region are covered","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Considering that the head may also be reversed, a dummy head is needed.\nDuring the traversal process, the tail node after the reversal and the previous node are recorded.\nFinally, the node link processing is performed.\nvar reverseBetween = function(head, m, n) { let noob = new ListNode(0) noob.next = head let cur = noob let index = 0 let A,B while (index \u0026lt; m) { A = cur // A records the previous node of the reverse subchain cur = cur.next index++ } B = cur // B records the tail node after reversal let pre = cur while (index \u0026lt;= n) { let next = cur.next cur.next = pre pre = cur cur = next index++ } // After execution, the cur pointer is at the next node of the reverse subchain B.next = cur A.next = pre return noob.next };","date":1626998400,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"276af19fa750105259e2b4e2bacf6526","permalink":"https://siqi-liu.com/en/post/reverse-linked-list-2/","publishdate":"2021-07-23T00:00:00Z","relpermalink":"/en/post/reverse-linked-list-2/","section":"post","summary":"Considering that the head may also be reversed, a dummy head is needed. During the traversal process, the tail node after the reversal and the previous node are recorded. Finally, the node link processing is performed. …","tags":null,"title":"Reverse Linked List 2","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"The idea behind the question is to use double pointers to point to the old and new nodes, and then add a map to map the old and new nodes.\nrecursion\nvar listMap = new Map() var copyRandomList = function(head) { if(head === null) return head if (listMap.get(head)) { return listMap.get(head) } let newNode = new Node(head.val,null,null) listMap.set(head, newNode) newNode.next = copyRandomList(head.next) newNode.random = copyRandomList(head.random) return newNode }; ```Iteration ```javascript var copyRandomList = function(head) { if(head == null) return head var listMap = new Map() listMap.set(null,null) var cur = head while (cur!= null) { listMap.set(cur,new Node(cur.val,null,null)) cur = cur.next } cur = head while (cur!= null) { listMap.get(cur).next = listMap.get(cur.next) listMap.get(cur).random = listMap.get(cur.random) cur = cur.next } return listMap.get(head) };The idea behind the question is to use double pointers to point to the old and new nodes, and then add a map to map the old and new nodes.\nrecursion\nvar listMap = new Map() var copyRandomList = function(head) { if(head === null) return head if (listMap.get(head)) { return listMap.get(head) } let newNode = new Node(head.val,null,null) listMap.set(head, newNode) newNode.next = copyRandomList(head.next) newNode.random = copyRandomList(head.random) return newNode }; ```Iteration ```javascript var copyRandomList = function(head) { if(head == null) return head var listMap = new Map() listMap.set(null,null) var cur = head while (cur!= null) { listMap.set(cur,new Node(cur.val,null,null)) cur = cur.next } cur = head while (cur!= null) { listMap.get(cur).next = listMap.get(cur.next) listMap.get(cur).random = listMap.get(cur.random) cur = cur.next } return listMap.get(head) };","date":1626912e3,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"f65d62c542bd6091b1dcc5841f6b1a84","permalink":"https://siqi-liu.com/en/post/138-copying-a-linked-list-with-random-pointers/","publishdate":"2021-07-22T00:00:00Z","relpermalink":"/en/post/138-copying-a-linked-list-with-random-pointers/","section":"post","summary":"The idea behind the question is to use double pointers to point to the old and new nodes, and then add a map to map the old and new nodes. recursion var listMap = new Map() var copyRandomList = function(head) { if(head …","tags":null,"title":"138-Copying a linked list with random pointers","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: 15 minutes\nUse the union-find template\nvar findRedundantConnection = function(edges) { var n = edges.length var fa = new Array(n + 1) var find = function (x) { if (x != fa[x]) { fa[x] = find(fa[x]) } return fa[x] } for(let i = 1 ; i \u0026lt;= n ; i ++) { fa[i] = i } for(let i = 0 ; i \u0026lt; n ; i ++) { var [x,y] = edges[i] if (find(x) === find(y)) { return [x,y] } else { fa[find(x)] = find(y) } } };","date":1625616e3,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"cdd076cd0f53eb15a81ceab391dcba3e","permalink":"https://siqi-liu.com/en/post/684-redundant-connection/","publishdate":"2021-07-07T00:00:00Z","relpermalink":"/en/post/684-redundant-connection/","section":"post","summary":"Time: 15 minutes Use the union-find template var findRedundantConnection = function(edges) { var n = edges.length var fa = new Array(n + 1) var find = function (x) { if (x != fa[x]) { fa[x] = find(fa[x]) } return fa[x] } …","tags":null,"title":"684-Redundant Connection","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"It is mainly used for learning and checking ideas, and you will start only after looking at the answers.\nThe idea is to group the interchangeable strings first, sort the groups, and then combine them\nUnion-find is to use recursion or while loop to implement find, and then use arrays and subscripts to implement union\nvar smallestStringWithSwaps = function(s, pairs) { var fa = [] var find = function (x) { if (x === fa[x]) { return x } else { return fa[x] = find(fa[x]) } } for (let i = 0; i \u0026lt; s.length; i++) { fa[i] = i } for(let i = 0 ; i \u0026lt; pairs.length ; i ++) { const [x,y] = pairs[i] fa[find(x)] = find(y) } var n = s.length // Calculate the grouped map const vec = new Array(n).fill(0).map(() =\u0026gt; new Array()); for (let i = 0; i \u0026lt; n; i++) { fa[i] = find(i); vec[fa[i]].push(s[i]); } console.log(fa) // Sort the grouped map for (let i = 0; i \u0026lt; n; ++i) { if (vec[i].length \u0026gt; 0) { vec[i].sort((a, b) =\u0026gt; a.charCodeAt() - b.charCodeAt()); } } // Combined into ans const ans = new Array(n).fill(0); for (let i = 0; i \u0026lt; n; ++i) { var group = fa[i] if ( group!= undefined \u0026amp;\u0026amp; vec[group].length) { // Take out the smallest number in each group ans[i] = vec[fa[i]].shift() } else { ans[i] = s[i] } } return ans.join(\u0026#39;\u0026#39;) };It is mainly used to learn and check the ideas, and you will start only after looking at the answers.\nThe idea is to group the interchangeable strings first, sort the groups, and then combine them\nUnion-find is to use recursion or while loop to implement find, and then use arrays and subscripts to implement union\nvar smallestStringWithSwaps = function(s, pairs) { var fa = [] var find = function (x) { if (x === fa[x]) { return x } else { return fa[x] = find(fa[x]) } } for (let i = 0; i \u0026lt; s.length; i++) { fa[i] = i } for(let i = 0 ; i \u0026lt; pairs.length ; i ++) { const [x,y] = pairs[i] fa[find(x)] = find(y) } var n = s.length // Calculate the grouped map const vec = new Array(n).fill(0).map(() =\u0026gt; new Array()); for (let i = 0; i \u0026lt; n; i++) { fa[i] = find(i); vec[fa[i]].push(s[i]); } console.log(fa) // Sort the grouped map for (let i = 0; i \u0026lt; n; ++i) { if (vec[i].length \u0026gt; 0) { vec[i].sort((a, b) =\u0026gt; a.charCodeAt() - b.charCodeAt()); } } // Combined into ans const ans = new Array(n).fill(0); for (let i = 0; i \u0026lt; n; ++i) { var group = fa[i] if ( group!= undefined \u0026amp;\u0026amp; vec[group].length) { // Take out the smallest number in each group ans[i] = vec[fa[i]].shift() } else { ans[i] = s[i] } } return ans.join(\u0026#39;\u0026#39;) };","date":1625529600,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"28d64c9fd3816c20096d67f8f7e481d9","permalink":"https://siqi-liu.com/en/post/1202-swap-elements-in-a-string/","publishdate":"2021-07-06T00:00:00Z","relpermalink":"/en/post/1202-swap-elements-in-a-string/","section":"post","summary":"It is mainly used for learning and checking ideas, and you will start only after looking at the answers. The idea is to group the interchangeable strings first, sort the groups, and then combine them Union-find is to use …","tags":null,"title":"1202-Swap elements in a string","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: 20 minutes\nFirst of all, it can be seen that this is a question to examine DFS\nvar swimInWater = function(grid) { let row = grid.length; let col = grid[0].length; var step = 0 while(true) { for(let i = 0 ; i \u0026lt; row ; i ++) { for(let j = 0 ; j \u0026lt; col ; j ++){ grid[i][j] -- } } var visited = [] for(let i = 0 ; i \u0026lt; row ; i ++) { let arr = [] for(let j = 0 ; j \u0026lt; col ; j ++) { arr.push(false) } visited.push(arr) } var dfs = function (i,j) { if ( i\u0026lt; 0 || j \u0026lt; 0 || i \u0026gt;= row || j \u0026gt;= col) return false if (visited[i][j]) { return false } else { visited[i][j] = true } if (grid[i][j] \u0026gt; 0) return false if (i === row - 1 \u0026amp;\u0026amp; j === col - 1 \u0026amp;\u0026amp; grid[i][j] \u0026lt;= 0) return true return dfs(i+1,j) || dfs(i-1,j) || dfs(i,j-1)|| dfs(i,j + 1) } step++ if (dfs(0,0) === true) break } return step };We don’t have to start from 1, we can use the binary search to find the leftmost insertion point.\nvar swimInWater = function(grid) { let row = grid.length; let col = grid[0].length; let right = -Infinity let left = Infinity for(let i = 0 ; i \u0026lt; row ; i ++) { for(let j = 0 ; j \u0026lt; col ; j ++ ){ let num = grid[i][j] right = Math.max(right,num) left = Math.min(left,num) } } let mid = left + Math.floor( (right - left) / 2 ) while(left \u0026lt;= right) { mid = left + Math.floor( (right - left) / 2 ) var visited = [] var new_grid = [] for(let i = 0 ; i \u0026lt; row ; i ++) { let arr = [] let _grid = [] for(let j = 0 ; j \u0026lt; col ; j ++) { arr.push(false) _grid.push(grid[i][j] - mid) } visited.push(arr) new_grid.push(_grid) } var dfs = function (i,j) { if ( i \u0026lt; 0 || j \u0026lt; 0 || i \u0026gt;= row || j \u0026gt;= col) return false if (visited[i][j]) { return false } else { visited[i][j] = true } if (new_grid[i][j] \u0026gt; 0) return false if (i === row - 1 \u0026amp;\u0026amp; j === col - 1 \u0026amp;\u0026amp; new_grid[i][j] \u0026lt;= 0) return true return dfs(i+1,j) || dfs(i-1,j) || dfs(i,j-1)|| dfs(i,j + 1) } if(dfs(0,0)) { // Found the spare tire, continue to see if it can be smaller right = mid - 1 } else { left = mid + 1 } } return left };","date":1625443200,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"4ee9cb9d8a04efac3d76b4e45e0eab88","permalink":"https://siqi-liu.com/en/post/778-swimming-in-a-rising-pool/","publishdate":"2021-07-05T00:00:00Z","relpermalink":"/en/post/778-swimming-in-a-rising-pool/","section":"post","summary":"Time: 20 minutes First of all, it can be seen that this is a question to examine DFS var swimInWater = function(grid) { let row = grid.length; let col = grid[0].length; var step = 0 while(true) { for(let i = 0 ; i \u003c row …","tags":null,"title":"778-Swimming in a rising pool","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"The first thing that comes to mind is to find the distance r of all heaters that are the shortest distance from the house, and then return the largest r\nvar findRadius = function(houses, heaters) { var max = 0 for(let i = 0 ; i \u0026lt; houses.length ; i ++) { var house_position = houses[i] var r = Infinity for(let j = 0 ; j \u0026lt; heaters.length ; j ++) { var heat_position = heaters[j] var reduce = Math.abs(house_position - heat_position) r = Math.min(r,reduce) } max = Math.max(r,max) } return max };In this case, we can also use the dichotomy method to find the heater\nvar findRadius = function(houses, heaters) { var r = 0 for(let i = 0 ; i \u0026lt; houses.length ; i ++) { var house_position = houses[i] let left = 0 , right = heaters.length - 1 // Find the heat_position closest to house_position // That is, find the rightmost insertion point of house_position while(left \u0026lt;= right \u0026amp;\u0026amp; right \u0026lt; heaters.length) { let mid = left + Math.floor((right - left) / 2) var heat_position = heaters[mid] // If the one found is not greater than, go to the right if(heat_position \u0026lt;= house_position) { left = mid + 1 } else { right = mid - 1 } } // The insertion point found at this time is not necessarily the closest one. The one on the left is smaller than it, so we need to compare it var R = Math.abs(heaters[right] - house_position) if(right \u0026gt; 0) { R = Math.min( Math.abs( heaters[right - 1] - house_position) , R) } r = Math.max(r,R) } return r };","date":1623283200,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"babccf33b212cb72efba8ed6835c4d14","permalink":"https://siqi-liu.com/en/post/475-heater/","publishdate":"2021-06-10T00:00:00Z","relpermalink":"/en/post/475-heater/","section":"post","summary":"The first thing that comes to mind is to find the distance r of all heaters that are the shortest distance from the house, and then return the largest r var findRadius = function(houses, heaters) { var max = 0 for(let i …","tags":null,"title":"475-Heater","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: 60 minutes\nThe quickest way of thinking when getting the question is to enumerate all the speeds at which bananas can be eaten and find the minimum speed at which all bananas can be eaten.\nThe enumeration speed range is [1… the largest pile of bananas], because the speed will not be less than 1, and there is no need for the speed to be faster than the largest pile of bananas.\nvar minEatingSpeed = function(piles, h) { // Enumerate all possible speeds to eat bananas and find the smallest one. piles = piles.sort((a,b) =\u0026gt; a - b) var maxSpeed = piles[piles.length - 1] var min = maxSpeed for(let i = 1; i \u0026lt;= maxSpeed; i ++) { var H = 0 for(let j = 0 ; j \u0026lt; piles.length; j ++) { H += Math.ceil(piles[j] / i) } if(H \u0026lt;= h) { min = Math.min(min,i) } } return min };It can be thought that [1…maximum speed] is a monotonic interval, which can be solved by bisection.\nvar minEatingSpeed = function(piles, h) { // The minimum speed is to find the lower bound l piles = piles.sort((a,b) =\u0026gt; a - b) var maxSpeed = piles[piles.length - 1] var min = maxSpeed // Binary the speed range [1...maxSpeed] var l = 1 , r = maxSpeed while(l \u0026lt;= r) { var mid = l + Math.floor ((r - l) / 2) var H = 0 for(let j = 0 ; j \u0026lt; piles.length ; j ++) { H += Math.ceil(piles[j] / mid) } if ( H \u0026lt;= h ) { // If yes, try to see if the speed can be smaller r = mid - 1 } else { // Not applicable, increase the speed l = mid + 1 } } return l };","date":1619568e3,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"d85e9e1e9ce0657d523db885a9b29a61","permalink":"https://siqi-liu.com/en/post/875-keke-who-loves-bananas/","publishdate":"2021-04-28T00:00:00Z","relpermalink":"/en/post/875-keke-who-loves-bananas/","section":"post","summary":"Time: 60 minutes The quickest way of thinking when getting the question is to enumerate all the speeds at which bananas can be eaten and find the minimum speed at which all bananas can be eaten. The enumeration speed …","tags":null,"title":"875-Keke who loves bananas","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: 15 minutes\nThere is nothing much to say about conventional heap sort\nclass Heap { constructor(list, compare = (a, b) =\u0026gt; a - b) { this.left = index =\u0026gt; 2 * index + 1 this.right = index =\u0026gt; 2 * index + 2 this.parent = index =\u0026gt; Math.floor((index - 1) / 2) this.heapify = (index = 0) =\u0026gt; { const { list } = this const leftIndex = this.left(index) const rightIndex = this.right(index) let maxIndex = index if (list[leftIndex] !== undefined \u0026amp;\u0026amp; this.compare(list[maxIndex], list[leftIndex]) \u0026gt; 0) { maxIndex = leftIndex } if (list[rightIndex] !== undefined \u0026amp;\u0026amp; this.compare(list[maxIndex], list[rightIndex]) \u0026gt; 0) { maxIndex = rightIndex } if (index !== maxIndex) { const temp = list[index] list[index] = list[maxIndex] list[maxIndex] = temp this.heapify(maxIndex) } } this.buildHeap = () =\u0026gt; { for (let i = Math.floor(this.list.length / 2); i \u0026gt;= 0; i--) { this.heapify(i) } return this.list } this.extract = () =\u0026gt; { const temp = this.list[0] this.list[0] = this.list[this.list.length - 1] this.list[this.list.length - 1] = temp const result = this.list.pop() this.heapify(0) return result } this.top = () =\u0026gt; { return this.list[0] } this.insert = (item) =\u0026gt; { const { list } = this list.push(item) let index = list.length - 1 let parentIndex = this.parent(index) while (list[parentIndex] !== undefined \u0026amp;\u0026amp; this.compare(list[parentIndex], list[index]) \u0026gt; 0) { const temp = list[index] list[index] = list[parentIndex] list[parentIndex] = temp index = parentIndex parentIndex = this.parent(index) } } this.list = list this.compare = compare this.buildHeap() } } var kClosest = function(points, k) { // To find the nearest point, a max heap should be used to store the k smallest var compare = function (a,b) { var A = a[0] * a[0] + a[1] * a[1] var B = b[1] * b[1] + b[0] * b[0] return B - A } var heap = new Heap([],(a,b) =\u0026gt; { return compare(a,b) }) for(let i = 0 ; i \u0026lt; points.length ; i ++) { if (i \u0026lt; k) { heap.insert(points[i]) } else { var top = heap.top() // top is greater than the current value, exit top if(compare(points[i],top) \u0026gt; 0) { heap.extract() heap.insert(points[i]) } } } return heap.list};","date":1618012800,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"5fefc3136e9d2bcadc7cb8907a28d57d","permalink":"https://siqi-liu.com/en/post/973-k-points-closest-to-the-origin/","publishdate":"2021-04-10T00:00:00Z","relpermalink":"/en/post/973-k-points-closest-to-the-origin/","section":"post","summary":"Time: 15 minutes There is nothing much to say about conventional heap sort class Heap { constructor(list, compare = (a, b) =\u003e a - b) { this.left = index =\u003e 2 * index + 1 this.right = index =\u003e 2 * index + 2 this.parent = …","tags":null,"title":"973-K points closest to the origin","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: 30 minutes\nThe idea is to first use the time complexity of On to count, and then solve it using the minimum heap method\nclass Heap { constructor(list, compare = (a, b) =\u0026gt; a - b) { this.left = index =\u0026gt; 2 * index + 1 this.right = index =\u0026gt; 2 * index + 2 this.parent = index =\u0026gt; Math.floor((index - 1) / 2) this.heapify = (index = 0) =\u0026gt; { const { list } = this const leftIndex = this.left(index) const rightIndex = this.right(index) let maxIndex = index if (list[leftIndex] !== undefined \u0026amp;\u0026amp; this.compare(list[maxIndex], list[leftIndex]) \u0026gt; 0) { maxIndex = leftIndex } if (list[rightIndex] !== undefined \u0026amp;\u0026amp; this.compare(list[maxIndex], list[rightIndex]) \u0026gt; 0) { maxIndex = rightIndex } if (index !== maxIndex) { const temp = list[index] list[index] = list[maxIndex] list[maxIndex] = temp this.heapify(maxIndex) } } this.buildHeap = () =\u0026gt; { for (let i = Math.floor(this.list.length / 2); i \u0026gt;= 0; i--) { this.heapify(i) } return this.list } this.extract = () =\u0026gt; { const temp = this.list[0] this.list[0] = this.list[this.list.length - 1] this.list[this.list.length - 1] = temp const result = this.list.pop() this.heapify(0) return result } this.top = () =\u0026gt; { return this.list[0] } this.insert = (item) =\u0026gt; { const { list } = this list.push(item) let index = list.length - 1 let parentIndex = this.parent(index) while (list[parentIndex] !== undefined \u0026amp;\u0026amp; this.compare(list[parentIndex], list[index]) \u0026gt; 0) { const temp = list[index] list[index] = list[parentIndex] list[parentIndex] = temp index = parentIndex parentIndex = this.parent(index) } } this.list = list this.compare = compare this.buildHeap() } } var topKFrequent = function(nums, k) { var map = {} for(let i = 0 ; i \u0026lt; nums.length ; i ++) { var num = nums[i] if (map[num] == undefined) { map[num] = 1 } else { map[num] ++ } } // Use a minimum heap to maintain the K highest elements var heap = new Heap([],(a,b) =\u0026gt; a[1] - b[1]) var K = 0 // Because the api is a maximum heap, we need to take the opposite number Object.entries(map).forEach(([key,val]) =\u0026gt; { if (K \u0026lt; k) { heap.insert([key,-val]) } else { var top = heap.top() if (val \u0026gt; - top[1]) { heap.extract() heap.insert([key,-val]) } } }) return heap.list.map((v) =\u0026gt; v[0]).slice(0,k) };","date":161784e4,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"246512c4d9025ba45147d32313eeac0b","permalink":"https://siqi-liu.com/en/post/347-first-k-high-frequency-elements/","publishdate":"2021-04-08T00:00:00Z","relpermalink":"/en/post/347-first-k-high-frequency-elements/","section":"post","summary":"Time: 30 minutes The idea is to first use the time complexity of On to count, and then solve it using the minimum heap method class Heap { constructor(list, compare = (a, b) =\u003e a - b) { this.left = index =\u003e 2 * index + 1 …","tags":null,"title":"347-first-K-high-frequency elements","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: 10 minutes\nconst swap = function (arr,i,j) { [arr[i],arr[j]] = [arr[j],arr[i]] } class MaxHeap { constructor() { this.count = 0 this.data = new Array(this.count + 1) } shiftUp(k) { // Put the new elements up while(k\u0026gt;=1) { let father = Math.floor(k / 2) if (this.data[k] \u0026gt; this.data[father]) { swap(this.data,k,father) k = father } else { break } } } shiftDown(k) { while( k * 2 \u0026lt;= this.count) { // Indicates that k has children let j = k if (k * 2 + 1 \u0026lt;= this.count \u0026amp;\u0026amp; this.data[k * 2 + 1] \u0026gt; this.data[k] \u0026amp;\u0026amp; this.data[k * 2 + 1] \u0026gt; this.data[k * 2]) { j = k * 2 + 1 } else if (this.data[k * 2] \u0026gt; this.data[k]) { j = k * 2 } else { break } swap(this.data,j,k) k = j } } size() { return this.count } isEmpty() { return this.count === 0 } insert(item) { this.data[++this.count] = item this.shiftUp(this.count) } extractMax() { if (this.count \u0026lt; 0) return let ret = this.data[1] swap(this.data,1,this.count--) this.shiftDown(1) return ret } top() { return this.data[1] } } var smallestK = function(arr, k) { var heap = new MaxHeap() var K = 0 while(K \u0026lt; k) { heap.insert(arr[K]) K++ } // Maintain the heap at size K while(K \u0026lt; arr.length) { var max = heap.top() if (arr[K] \u0026lt; max) { heap.extractMax() heap.insert(arr[K]) } K++ } var size = heap.size() return heap.data.slice(1, 1 + size ) };","date":161784e4,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"c315c2c1c5cca6e1f685eb0076ebb0c4","permalink":"https://siqi-liu.com/en/post/interview-question-17-14-minimum-number-of-k/","publishdate":"2021-04-08T00:00:00Z","relpermalink":"/en/post/interview-question-17-14-minimum-number-of-k/","section":"post","summary":"Time: 10 minutes const swap = function (arr,i,j) { [arr[i],arr[j]] = [arr[j],arr[i]] } class MaxHeap { constructor() { this.count = 0 this.data = new Array(this.count + 1) } shiftUp(k) { // Put the new elements up …","tags":null,"title":"Interview Question-17-14-Minimum Number of K","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: Read the answer\nSince it is a pile exercise, it must be closer to the idea of pile\nSince we only want the median, there is no need to sort all the numbers.\nYou can use two heaps, a maximum heap and a minimum heap\nWhen the total number is odd (i.e., the number of maximum heaps - minimum heaps = 1), get the maximum value of the maximum heap\nWhen the total number is an even number (i.e. the number of maximum heaps = minimum heap), get the maximum value of the maximum heap and the minimum value of the minimum heap\nconst swap = function (arr,i,j) { [arr[i],arr[j]] = [arr[j],arr[i]] } class Heap { constructor() { this.count = 0 this.data = new Array(this.count + 1) } shiftUp(k) { // Put the new elements up while(k\u0026gt;=1) { let father = Math.floor(k / 2) if (this.data[k] \u0026gt; this.data[father]) { swap(this.data,k,father) k = father } else { break } } } shiftDown(k) { while( k * 2 \u0026lt;= this.count) { // Indicates that k has children let j = k if (k * 2 + 1 \u0026lt;= this.count \u0026amp;\u0026amp; this.data[k * 2 + 1] \u0026gt; this.data[k] \u0026amp;\u0026amp; this.data[k * 2 + 1] \u0026gt; this.data[k * 2]) { j = k * 2 + 1 } else if (this.data[k * 2] \u0026gt; this.data[k]) { j = k * 2 } else { break } swap(this.data,j,k) k = j } } size() { return this.count } isEmpty() { return this.count === 0 } insert(item) { this.data[++this.count] = item this.shiftUp(this.count) } extractMax() { if (this.count \u0026lt;= 0) return let ret = this.data[1] swap(this.data,1,this.count--) this.shiftDown(1) return ret } } var MedianFinder = function() { this.maxHeap = new Heap(); this.minHeap = new Heap();};MedianFinder.prototype.addNum = function(num) { //[1] [] // [1,2] [] -\u0026gt; [1] [2] // [1,3] [2] -\u0026gt; [1,2] [3] // [1,2,4] [3] -\u0026gt; [1,2] [3,4] // [1,2,5] [3,4] -\u0026gt; [1,2,4] [4,5] var maxHeapsize = this.maxHeap.size() var minHeapsize = this.minHeap.size() this.maxHeap.insert(num) var max = this.maxHeap.extractMax() this.minHeap.insert(-max ) if (maxHeapsize === minHeapsize) { var min = Math.abs(this.minHeap.extractMax()) this.maxHeap.insert(min) } };MedianFinder.prototype.findMedian = function() { if (this.maxHeap.size() === this.minHeap.size()) { var max = this.maxHeap.extractMax() var min = Math.abs(this.minHeap.extractMax()) return (max + min) / 2 } else { return this.maxHeap.extractMax() } };Points to note\nTo ensure that the number of items in the max heap minus the number of items in the min heap is 0 to 1, and that the maximum value in the max heap is less than the minimum value in the min heap, each item is first entered into the max heap, which then passes the maximum value to the min heap. The number of items in the max heap is then used to determine whether to pass the minimum value of the min heap to the max heap. Only the max heap is implemented, but negative numbers can be used to create a min heap. Don’t forget to include negative numbers when exiting. ","date":1616544e3,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"210d700605c46a259ff4f489d6367a72","permalink":"https://siqi-liu.com/en/post/295-median-of-data-streams/","publishdate":"2021-03-24T00:00:00Z","relpermalink":"/en/post/295-median-of-data-streams/","section":"post","summary":"Time: Read the answer Since it is a pile exercise, it must be closer to the idea of pile Since we only want the median, there is no need to sort all the numbers. You can use two heaps, a maximum heap and a minimum heap …","tags":null,"title":"295-Median of data streams","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: Refer to the answer\nAfter reading the title, the first thing that comes to mind is that dp cannot escape\nBut writing the conversion equation requires some skill\nObserve that dp[1][1] = matrix[0][0] ^ matrix[0][1] ^ matrix[1][0] ^ matrix[1][1]\nThen use the property of XOR\nThe same value is XORed to 0, and any value XORed with 0 is itself\nCan be written as\ndp[1][1] = matrix[0][0] ^ (matrix[0][1] ^ matrix[0][0]) ^ (matrix[1][0] ^ matrix[1][0]) ^ matrix[1][1]\nThen get\ndp[i][j] = dp[i - 1][j - 1] ^ dp[i][j - 1] ^ dp[i - 1][j] ^ matrix[i][j]\n(This is a technique I didn’t think of. If you can draw a picture, you can figure it out.\nAfter the XOR operation of the region (1, 2) and (2, 1), the overlapping parts are eliminated.\nThe overlapping part is exactly the area of (1, 1), so we can XOR (1, 1) to compensate\nconst swap = function (arr,i,j) { [arr[i],arr[j]] = [arr[j],arr[i]] } class MaxHeap { constructor() { this.count = 0 this.data = new Array(this.count + 1) } shiftUp(k) { // Put the new elements up while(k\u0026gt;=1) { let father = Math.floor(k / 2) if (this.data[k] \u0026gt; this.data[father]) { swap(this.data,k,father) k = father } else { break } } } shiftDown(k) { while( k * 2 \u0026lt;= this.count) { // Indicates that k has children let j = k if (k * 2 + 1 \u0026lt;= this.count \u0026amp;\u0026amp; this.data[k * 2 + 1] \u0026gt; this.data[k] \u0026amp;\u0026amp; this.data[k * 2 + 1] \u0026gt; this.data[k * 2]) { j = k * 2 + 1 } else if (this.data[k * 2] \u0026gt; this.data[k]) { j = k * 2 } else { break } swap(this.data,j,k) k = j } } size() { return this.count } isEmpty() { return this.count === 0 } insert(item) { this.data[++this.count] = item this.shiftUp(this.count) } extractMax() { if (this.count \u0026lt; 0) return let ret = this.data[1] swap(this.data,1,this.count--) this.shiftDown(1) return ret } } var kthLargestValue = function(matrix, k) { var maxheap = new MaxHeap() var row = matrix.length var col = matrix[0].length var dp = [[matrix[0][0]]] maxheap.insert(matrix[0][0]) for(let j = 1; j \u0026lt; col ; j ++) { dp[0][j] = dp[0][j - 1] ^ matrix[0][j] maxheap.insert(dp[0][j]) } for(let i = 1 ; i \u0026lt; row ; i ++) { for(let j = 1 ; j \u0026lt; col; j ++) { if (!dp[i]) { dp[i] = [] dp[i][0] = dp[i - 1][0] ^ matrix[i][0] maxheap.insert(dp[i][0]) } dp[i][j] = dp[i - 1][j - 1] ^ dp[i][j - 1] ^ dp[i - 1][j] ^ matrix[i][j] maxheap.insert(dp[i][j]) } } var res = \u0026#39;\u0026#39; while(k \u0026gt; 0) { res = maxheap.extractMax() k-- } return res };Then K is large, so we should use the minimum heap. Here we use the maximum heap, but it is not a big problem.\nTime: Refer to the answer\nAfter reading the title, the first thing that comes to mind is that dp cannot escape\nBut writing the conversion equation requires some skill\nObserve that dp[1][1] = matrix[0][0] ^ matrix[0][1] ^ matrix[1][0] ^ matrix[1][1]\nThen use the property of XOR\nThe same value is XORed to 0, and any value XORed with 0 is itself\nCan be written as\ndp[1][1] = matrix[0][0] ^ (matrix[0][1] ^ matrix[0][0]) ^ (matrix[1][0] ^ matrix[1][0]) ^ matrix[1][1]\nThen get\ndp[i][j] = dp[i - 1][j - 1] ^ dp[i][j - 1] ^ dp[i - 1][j] ^ matrix[i][j]\n(This is a technique I didn’t think of. If you can draw a picture, you can figure it out.\nAfter the XOR operation of the region (1, 2) and (2, 1), the overlapping parts are eliminated.\nThe overlapping part is exactly the area of (1, 1), so we can XOR (1, 1) to compensate\nconst swap = function (arr,i,j) { [arr[i],arr[j]] = [arr[j],arr[i]] } class MaxHeap { constructor() { this.count = 0 this.data = new Array(this.count + 1) } shiftUp(k) { // Put the new elements up while(k\u0026gt;=1) { let father = Math.floor(k / 2) if (this.data[k] \u0026gt; this.data[father]) { swap(this.data,k,father) k = father } else { break } } } shiftDown(k) { while( k * 2 \u0026lt;= this.count) { // Indicates that k has children let j = k if (k * 2 + 1 \u0026lt;= this.count \u0026amp;\u0026amp; this.data[k * 2 + 1] \u0026gt; this.data[k] \u0026amp;\u0026amp; this.data[k * 2 + 1] \u0026gt; this.data[k * 2]) { j = k * 2 + 1 } else if (this.data[k * 2] \u0026gt; this.data[k]) { j = k * 2 } else { break } swap(this.data,j,k) k = j } } size() { return this.count } isEmpty() { return this.count === 0 } insert(item) { this.data[++this.count] = item this.shiftUp(this.count) } extractMax() { if (this.count \u0026lt; 0) return let ret = this.data[1] swap(this.data,1,this.count--) this.shiftDown(1) return ret } } var kthLargestValue = function(matrix, k) { var maxheap = new MaxHeap() var row = matrix.length var col = matrix[0].length var dp = [[matrix[0][0]]] maxheap.insert(matrix[0][0]) for(let j = 1; j \u0026lt; col ; j ++) { dp[0][j] = dp[0][j - 1] ^ matrix[0][j] maxheap.insert(dp[0][j]) } for(let i = 1 ; i \u0026lt; row ; i ++) { for(let j = 1 ; j \u0026lt; col; j ++) { if (!dp[i]) { dp[i] = [] dp[i][0] = dp[i - 1][0] ^ matrix[i][0] maxheap.insert(dp[i][0]) } dp[i][j] = dp[i - 1][j - 1] ^ dp[i][j - 1] ^ dp[i - 1][j] ^ matrix[i][j] maxheap.insert(dp[i][j]) } } var res = \u0026#39;\u0026#39; while(k \u0026gt; 0) { res = maxheap.extractMax() k-- } return res };If K is large, a min-heap should be used. Here, a max-heap is used, but it’s not a big deal.\n","date":1616371200,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"0523716feae7ce88fd1f6740201b3d44","permalink":"https://siqi-liu.com/en/post/1738-find-the-k-th-largest-xor-coordinate-value/","publishdate":"2021-03-22T00:00:00Z","relpermalink":"/en/post/1738-find-the-k-th-largest-xor-coordinate-value/","section":"post","summary":"Time: Refer to the answer After reading the title, the first thing that comes to mind is that dp cannot escape But writing the conversion equation requires some skill Observe that dp[1][1] = matrix[0][0] ^ matrix[0][1] ^ …","tags":null,"title":"1738-Find the K-th largest XOR coordinate value","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: 10 minutes\nA simple question shouldn’t ask me to write the maximum heap by hand.\nconst swap = function (arr,i,j) { [arr[i],arr[j]] = [arr[j],arr[i]] } class MaxHeap { constructor() { this.count = 0 this.data = new Array(this.count + 1) } shiftUp(k) { // Put the new elements up while(k\u0026gt;=1) { let father = Math.floor(k / 2) if (this.data[k] \u0026gt; this.data[father]) { swap(this.data,k,father) k = father } else { break } } } shiftDown(k) { while( k * 2 \u0026lt;= this.count) { // Indicates that k has children let j = k if (k * 2 + 1 \u0026lt;= this.count \u0026amp;\u0026amp; this.data[k * 2 + 1] \u0026gt; this.data[k] \u0026amp;\u0026amp; this.data[k * 2 + 1] \u0026gt; this.data[k * 2]) { j = k * 2 + 1 } else if (this.data[k * 2] \u0026gt; this.data[k]) { j = k * 2 } else { break } swap(this.data,j,k) k = j } } size() { return this.count } isEmpty() { return this.count === 0 } insert(item) { this.data[++this.count] = item this.shiftUp(this.count) } extractMax() { if (this.count \u0026lt; 0) return let ret = this.data[1] swap(this.data,1,this.count--) this.shiftDown(1) return ret } } var lastStoneWeight = function(stones) { var heap = new MaxHeap() for(let i = 0 ; i \u0026lt; stones.length ; i++) { heap.insert(stones[i]) } while(heap.size() \u0026gt; 1) { var s1 = heap.extractMax() var s2 = heap.extractMax() var reduce = Math.abs(s1 - s2) if (reduce) { heap.insert(reduce) } } if (heap.size() === 0) { return 0 } return heap.extractMax() };Time: 10 minutes\nA simple question shouldn’t ask me to write the maximum heap by hand.\nconst swap = function (arr,i,j) { [arr[i],arr[j]] = [arr[j],arr[i]] } class MaxHeap { constructor() { this.count = 0 this.data = new Array(this.count + 1) } shiftUp(k) { // Put the new elements up while(k\u0026gt;=1) { let father = Math.floor(k / 2) if (this.data[k] \u0026gt; this.data[father]) { swap(this.data,k,father) k = father } else { break } } } shiftDown(k) { while( k * 2 \u0026lt;= this.count) { // Indicates that k has children let j = k if (k * 2 + 1 \u0026lt;= this.count \u0026amp;\u0026amp; this.data[k * 2 + 1] \u0026gt; this.data[k] \u0026amp;\u0026amp; this.data[k * 2 + 1] \u0026gt; this.data[k * 2]) { j = k * 2 + 1 } else if (this.data[k * 2] \u0026gt; this.data[k]) { j = k * 2 } else { break } swap(this.data,j,k) k = j } } size() { return this.count } isEmpty() { return this.count === 0 } insert(item) { this.data[++this.count] = item this.shiftUp(this.count) } extractMax() { if (this.count \u0026lt; 0) return let ret = this.data[1] swap(this.data,1,this.count--) this.shiftDown(1) return ret } } var lastStoneWeight = function(stones) { var heap = new MaxHeap() for(let i = 0 ; i \u0026lt; stones.length ; i++) { heap.insert(stones[i]) } while(heap.size() \u0026gt; 1) { var s1 = heap.extractMax() var s2 = heap.extractMax() var reduce = Math.abs(s1 - s2) if (reduce) { heap.insert(reduce) } } if (heap.size() === 0) { return 0 } return heap.extractMax() };","date":1616284800,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"3233ce943f5d5ecb407da67567460ccc","permalink":"https://siqi-liu.com/en/post/1046-the-weight-of-the-last-stone/","publishdate":"2021-03-21T00:00:00Z","relpermalink":"/en/post/1046-the-weight-of-the-last-stone/","section":"post","summary":"Time: 10 minutes A simple question shouldn’t ask me to write the maximum heap by hand. const swap = function (arr,i,j) { [arr[i],arr[j]] = [arr[j],arr[i]] } class MaxHeap { constructor() { this.count = 0 this.data = new …","tags":null,"title":"1046-The Weight of the Last Stone","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: 60 minutes\nI was misled by the depth of the question and thought that the calculation depth should be done from top to bottom. In fact, it can be done from bottom to top.\nIf the left and right subtrees have the same height, return the node itself and its depth.\nIf the left subtree is deeper, it means that the smallest and deepest part is in the left subtree. Return the left subtree and its own depth.\nIf the right subtree is deeper, it means the minimum depth is on the right, and the right subtree and its own depth are returned.\nvar subtreeWithAllDeepest = function(root) { var dfs = function (root) { if (!root) return [root,0] var [left,ld] = dfs(root.left) var [right,rd] = dfs(root.right) if (ld \u0026gt; rd) return [left, ld + 1] if (rd \u0026gt; ld) return [right,rd + 1] return [root,ld + 1] } var res = dfs(root) return res[0] };","date":1616284800,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"5e08dd9aef87d9b9ecae3af892b0a317","permalink":"https://siqi-liu.com/en/post/865-minimum-subtree-with-all-deepest-nodes/","publishdate":"2021-03-21T00:00:00Z","relpermalink":"/en/post/865-minimum-subtree-with-all-deepest-nodes/","section":"post","summary":"Time: 60 minutes I was misled by the depth of the question and thought that the calculation depth should be done from top to bottom. In fact, it can be done from bottom to top. If the left and right subtrees have the …","tags":null,"title":"865 - Minimum subtree with all deepest nodes","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: 10 minutes\nSolve by using the property of pre-order traversal of binary tree\nvar minDiffInBST = function(root) { var min = Infinity var pre = null var dfs = function (root) { if (!root) return if (root.left) { dfs(root.left) } if (pre == null) { pre = root.val } else { console.log(pre,root.val) var reduce = Math.abs(pre - root.val) min = Math.min(min,reduce) pre = root.val } if (root.right) { dfs(root.right) } } dfs(root) return min };","date":1616025600,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"7f76c1bc046edec2a1f046496adccb0e","permalink":"https://siqi-liu.com/en/post/783-binary-search-tree-node-minimum-distance/","publishdate":"2021-03-18T00:00:00Z","relpermalink":"/en/post/783-binary-search-tree-node-minimum-distance/","section":"post","summary":"Time: 10 minutes Solve by using the property of pre-order traversal of binary tree var minDiffInBST = function(root) { var min = Infinity var pre = null var dfs = function (root) { if (!root) return if (root.left) { …","tags":null,"title":"783-Binary Search Tree Node Minimum Distance","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: 10 minutes\nThe preorder is calculated from top to bottom, there is nothing much to say\nConverting binary to decimal is quite difficult, so I just parseInt(path, 2)\nvar sumRootToLeaf = function(root) { var ans = 0 var dfs = function (root,path) { if (!root) return path += root.val if (!root.left \u0026amp;\u0026amp; !root.right) { ans += parseInt(path, 2) } root.left \u0026amp;\u0026amp; dfs(root.left,path) root.right \u0026amp;\u0026amp; dfs(root.right,path) } dfs(root,\u0026#39;\u0026#39;) return ans };Time: 10 minutes\nThe preorder is calculated from top to bottom, there is nothing much to say\nConverting binary to decimal is quite difficult, so I just parseInt(path, 2)\nvar sumRootToLeaf = function(root) { var ans = 0 var dfs = function (root,path) { if (!root) return path += root.val if (!root.left \u0026amp;\u0026amp; !root.right) { ans += parseInt(path, 2) } root.left \u0026amp;\u0026amp; dfs(root.left,path) root.right \u0026amp;\u0026amp; dfs(root.right,path) } dfs(root,\u0026#39;\u0026#39;) return ans };","date":1615939200,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"06d83d1948269fc1b4eccb7b8fd165d4","permalink":"https://siqi-liu.com/en/post/1022-the-sum-of-the-binary-numbers-from-root-to-leaf/","publishdate":"2021-03-17T00:00:00Z","relpermalink":"/en/post/1022-the-sum-of-the-binary-numbers-from-root-to-leaf/","section":"post","summary":"Time: 10 minutes The preorder is calculated from top to bottom, there is nothing much to say Converting binary to decimal is quite difficult, so I just parseInt(path, 2) var sumRootToLeaf = function(root) { var ans = 0 …","tags":null,"title":"1022 - the sum of the binary numbers from root to leaf","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: 5 minutes\nVirtual node + post-order traversal can be done happily in seconds\nvar removeLeafNodes = function(root, target) { var dfs = function (root) { if (root.left) { root.left = dfs(root.left) } if (root.right) { root.right = dfs(root.right) } if (!root.left \u0026amp;\u0026amp; !root.right) { if (root.val === target) { root = null } } return root } var dummy = new TreeNode(0) dummy.left = root dfs(dummy) return dummy.left};Time: 5 minutes\nVirtual node + post-order traversal can be done happily in seconds\nvar removeLeafNodes = function(root, target) { var dfs = function (root) { if (root.left) { root.left = dfs(root.left) } if (root.right) { root.right = dfs(root.right) } if (!root.left \u0026amp;\u0026amp; !root.right) { if (root.val === target) { root = null } } return root } var dummy = new TreeNode(0) dummy.left = root dfs(dummy) return dummy.left};","date":1615939200,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"4049c65b1c284aa7894b310531cacbd9","permalink":"https://siqi-liu.com/en/post/1325-delete-the-leaf-node-of-the-given-value/","publishdate":"2021-03-17T00:00:00Z","relpermalink":"/en/post/1325-delete-the-leaf-node-of-the-given-value/","section":"post","summary":"Post-order traversal approach to delete target-valued leaf nodes using a dummy root.","tags":null,"title":"1325-Delete the leaf node of the given value","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: I secretly looked at the answer\nI originally thought that the problem was complicated. I thought about maintaining a maximum heap, passing it from top to bottom and then judging whether it was greater than the maximum value each time.\nAfter reading the answer, I found that I only need to pass a max to determine whether it is greater than max, then get the values of the left and right nodes from bottom to top, and return the left and right nodes plus themselves to see if they match.\nvar goodNodes = function(root,max = root.val) { if (!root) return 0 var left = 0 max = Math.max(max,root.val) if (root.left) { left = goodNodes(root.left,max) } var right = 0 if (root.right) { right = goodNodes(root.right,max) } console.log(left,right,root.val) return (left + right) + (root.val \u0026gt;= max ? 1 : 0) };Time: I secretly looked at the answer\nI originally thought that the problem was complicated. I thought about maintaining a maximum heap, passing it from top to bottom and then judging whether it was greater than the maximum value each time.\nAfter reading the answer, I found that I only need to pass a max to determine whether it is greater than max, then get the values of the left and right nodes from bottom to top, and return the left and right nodes plus themselves to see if they match.\nvar goodNodes = function(root,max = root.val) { if (!root) return 0 var left = 0 max = Math.max(max,root.val) if (root.left) { left = goodNodes(root.left,max) } var right = 0 if (root.right) { right = goodNodes(root.right,max) } console.log(left,right,root.val) return (left + right) + (root.val \u0026gt;= max ? 1 : 0) };","date":1615939200,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"ee4113234f1f9058a56e5808d9aebfc3","permalink":"https://siqi-liu.com/en/post/1448-count-the-number-of-good-nodes-in-a-binary-tree/","publishdate":"2021-03-17T00:00:00Z","relpermalink":"/en/post/1448-count-the-number-of-good-nodes-in-a-binary-tree/","section":"post","summary":"Notes on counting good nodes in a binary tree using DFS with a running max.","tags":null,"title":"1448-Count the number of good nodes in a binary tree","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: 6 minutes\nIt’s very simple. In-order traversal, calculate whether the value 1 exists in the subtrees on both sides, and remove it if not. If the left or right subtree or the tree itself contains 1, return true. Otherwise, return false.\nvar pruneTree = function(root) { var DFS = function (root) { if (!root) return false var left = DFS(root.left) var right = DFS(root.right) if (!left) { root.left = null } if (!right) { root.right = null } if (left) { return true } if (right) { return true } if( root.val === 1) { return true } } var dummp = new TreeNode(1) dummp.left = root DFS(dummp) return dummp.left};One point to note is that the root itself may also need to be pruned, so a virtual node is added as the parent node\n","date":1615939200,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"314a5980484d268f49a0228a97c43d06","permalink":"https://siqi-liu.com/en/post/814-binary-tree-pruning/","publishdate":"2021-03-17T00:00:00Z","relpermalink":"/en/post/814-binary-tree-pruning/","section":"post","summary":"Time: 6 minutes It’s very simple. In-order traversal, calculate whether the value 1 exists in the subtrees on both sides, and remove it if not. If the left or right subtree or the tree itself contains 1, return true. …","tags":null,"title":"814-Binary Tree Pruning","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"The question is about starting from the root node. The first thing that comes to mind is top-down DFS, passing parameters downward, and the end condition has no left or right children.\nStart with the pre-order traversal, and you can assume that all the previous nodes have been processed.\nvar sumNumbers = function(root) { var ans = 0 var DFS = function (root,path) { if (!root) return path += root.val if (!root.left \u0026amp;\u0026amp; !root.right) { ans += Number(path) } root.left \u0026amp;\u0026amp; DFS(root.left,path) DFS(root.right, path) } DFS(root,\u0026#39;\u0026#39;) return ans };The question is about starting from the root node. The first thing that comes to mind is top-down DFS, passing parameters downward, and the end condition has no left or right children.\nStart with the pre-order traversal, and you can assume that all the previous nodes have been processed.\nvar sumNumbers = function(root) { var ans = 0 var DFS = function (root,path) { if (!root) return path += root.val if (!root.left \u0026amp;\u0026amp; !root.right) { ans += Number(path) } root.left \u0026amp;\u0026amp; DFS(root.left,path) DFS(root.right, path) } DFS(root,\u0026#39;\u0026#39;) return ans };","date":1615852800,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"d46c5da43e9101c9e5fcd5945a9ad606","permalink":"https://siqi-liu.com/en/post/129-find-the-sum-of-numbers-from-the-root-node-to-the-leaf-node/","publishdate":"2021-03-16T00:00:00Z","relpermalink":"/en/post/129-find-the-sum-of-numbers-from-the-root-node-to-the-leaf-node/","section":"post","summary":"The question is about starting from the root node. The first thing that comes to mind is top-down DFS, passing parameters downward, and the end condition has no left or right children. Start with the pre-order traversal, …","tags":null,"title":"129-Find the sum of numbers from the root node to the leaf node","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: 15 minutes\nTime: 15 minutes\nMy first reaction when I got the question was to use recursion, but after I started writing it, I found that\nThe condition that recursion needs to meet is that the problem can be split into sub-problems, but according to the meaning of the question, what we need to find is the difference between the sum of the left and right nodes of each node. This “sum of the left and right nodes” is a different problem for each node.\nSo it’s still double recursion\nvar findTilt = function(root) { var total = 0 var innerDFS = function (root) { if (!root) return 0 var left = 0 ,right = 0 if (root.left) { left = innerDFS(root.left) } if (root.right) { right = innerDFS(root.right) } return root.val + left + right } var outerDFS = function (root) { if (!root) return root.right \u0026amp;\u0026amp; outerDFS(root.right) total += Math.abs(innerDFS(root.left) - innerDFS(root.right)) root.left \u0026amp;\u0026amp; outerDFS(root.left) } outerDFS(root) return total };But!!!!!!!!\nAfter looking at the answer, I think I should just take the clothes.\nIt turns out that calculating the sum of the left and right nodes is not inconsistent with accumulating the difference between the left and right nodes. A recursive operation can solve the problem. When performing DFS, we can do both at the same time. The accumulation of the left and right nodes is return root.val + left + right, and the difference is total += Math.abs(left-right)\nvar findTilt = function(root) { var total = 0 var DFS = function (root) { if (!root) return 0 var left = 0 ,right = 0 if (root.left) { left = DFS(root.left) } if (root.right) { right = DFS(root.right) } total += Math.abs(left-right) return root.val + left + right } DFS(root) return total };I am still young, I have done similar recursion\n","date":1615852800,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"30df4c272493db69bad58bd81a9baa98","permalink":"https://siqi-liu.com/en/post/563-slope-of-binary-tree/","publishdate":"2021-03-16T00:00:00Z","relpermalink":"/en/post/563-slope-of-binary-tree/","section":"post","summary":"Time: 15 minutes Time: 15 minutes My first reaction when I got the question was to use recursion, but after I started writing it, I found that The condition that recursion needs to meet is that the problem can be split …","tags":null,"title":"563-Slope of Binary Tree","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: 20 minutes\nBecause we are looking for “the path starting from each node is sum”\nObviously, backtracking comes to mind. Since it is a binary tree, double recursion is used.\nThe outer recursion is used to find all nodes, and the inner recursion is used to calculate the path of the nodes.\nvar pathSum = function(root, sum) { var res = 0 var dfs = function (root,total,visited) { if (!root) return if (total + root.val == sum) { res++ } else if (total + root.val \u0026gt; sum) { return } dfs(root.left,total + root.val,[...visited,root.val]) dfs(root.right,total + root.val,[...visited,root.val]) } var dfs_outer = function (root) { if (!root )return root.left \u0026amp;\u0026amp; dfs_outer(root.left) dfs(root,0,[]) root.right \u0026amp;\u0026amp; dfs_outer(root.right) } dfs_outer(root) return res };","date":1615852800,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"feeca0ccbfaef603c8aae27d0fa0eb91","permalink":"https://siqi-liu.com/en/post/interview-question-04-12-sum-path/","publishdate":"2021-03-16T00:00:00Z","relpermalink":"/en/post/interview-question-04-12-sum-path/","section":"post","summary":"Time: 20 minutes Because we are looking for “the path starting from each node is sum” Obviously, backtracking comes to mind. Since it is a binary tree, double recursion is used. The outer recursion is used to find all …","tags":null,"title":"Interview Question-04-12-Sum Path","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: 30 minutes\nOnce you understand the question, you can actually do it quickly.\nThe question is about the value of any node. We can find the value of each node and choose the largest one.\nSince the path cannot return, we know that the path of the child node of the root node we start from can only choose left or right, or neither, and the result is x\nWhen finding max, compare x with the case where both sides are selected.\nvar maxPathSum = function(root) { var max = -Infinity var DFS = function (root) { if (!root) return 0 var leftSum = DFS(root.left) var rightSum = DFS(root.right) var res = Math.max(leftSum + root.val ,rightSum + root.val ,root.val) max = Math.max(max,res,root.val + leftSum + rightSum) return res } DFS(root) return max };Time: 30 minutes\nOnce you understand the question, you can actually do it quickly.\nThe question is about the value of any node. We can find the value of each node and choose the largest one.\nSince the path cannot return, we know that the path of the child node of the root node we start from can only choose left or right, or neither, and the result is x\nWhen finding max, compare x with the case where both sides are selected.\nvar maxPathSum = function(root) { var max = -Infinity var DFS = function (root) { if (!root) return 0 var leftSum = DFS(root.left) var rightSum = DFS(root.right) var res = Math.max(leftSum + root.val ,rightSum + root.val ,root.val) max = Math.max(max,res,root.val + leftSum + rightSum) return res } DFS(root) return max };","date":1615766400,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"a0e9f09ec93202bc397a9fa266bf4740","permalink":"https://siqi-liu.com/en/post/124-maximum-path-sum-in-a-binary-tree/","publishdate":"2021-03-15T00:00:00Z","relpermalink":"/en/post/124-maximum-path-sum-in-a-binary-tree/","section":"post","summary":"Time: 30 minutes Once you understand the question, you can actually do it quickly. The question is about the value of any node. We can find the value of each node and choose the largest one. Since the path cannot return, …","tags":null,"title":"124-Maximum Path Sum in a Binary Tree","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: 80 minutes\nThe first reaction when I got the question was of course to solve it with DFS.\nBut this is a complete binary tree, so we should use the properties of a complete binary tree.\nYou can examine the depth of the left and right subtrees of the binary tree\nIf the left depth is greater than the right depth, it means that the right side is full. The number of full nodes is 2 to the power of n - 1, plus the root is 2 to the power of n, and continue recursively on the left side.\nIf the right depth is greater than the left, it means the left is full. Similarly, continue recursively on the right.\nAs for how to find the depth of the binary tree, you can recursively start from the bottom up.\nvar count = function(root) { if (root === null) return 0 return Math.max(count(root.left),count(root.right)) + 1}Then the overall code is\nvar countNodes = function(root) { if (!root) { return 0 } var count = function(root) { if (root === null) return 0 return Math.max(count(root.left),count(root.right)) + 1 } var leftLevel = count(root.left) var rightLevel = count(root.right) if (leftLevel === rightLevel) { return Math.pow(2,leftLevel) + countNodes(root.right) } else { return Math.pow(2,rightLevel ) + countNodes(root.left) } };Time: 80 minutes\nThe first reaction when I got the question was of course to solve it with DFS.\nBut this is a complete binary tree, so we should use the properties of a complete binary tree.\nYou can examine the depth of the left and right subtrees of the binary tree\nIf the left depth is greater than the right depth, it means that the right side is full. The number of full nodes is 2 to the power of n - 1, plus the root is 2 to the power of n, and continue recursively on the left side.\nIf the right depth is greater than the left, it means the left is full. Similarly, continue recursively on the right.\nAs for how to find the depth of the binary tree, you can recursively start from the bottom up.\nvar count = function(root) { if (root === null) return 0 return Math.max(count(root.left),count(root.right)) + 1}Then the overall code is\nvar countNodes = function(root) { if (!root) { return 0 } var count = function(root) { if (root === null) return 0 return Math.max(count(root.left),count(root.right)) + 1 } var leftLevel = count(root.left) var rightLevel = count(root.right) if (leftLevel === rightLevel) { return Math.pow(2,leftLevel) + countNodes(root.right) } else { return Math.pow(2,rightLevel ) + countNodes(root.left) } };","date":1615593600,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"7e7bc9dd9b6567c49ed485fa8e491edc","permalink":"https://siqi-liu.com/en/post/222-number-of-nodes-in-a-complete-binary-tree/","publishdate":"2021-03-13T00:00:00Z","relpermalink":"/en/post/222-number-of-nodes-in-a-complete-binary-tree/","section":"post","summary":"Time: 80 minutes The first reaction when I got the question was of course to solve it with DFS. But this is a complete binary tree, so we should use the properties of a complete binary tree. You can examine the depth of …","tags":null,"title":"222-Number of nodes in a complete binary tree","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: 120 minutes\nThe initial idea was to use BFS, and remove a whole queue when removing it (later I learned that this is called breadth traversal)\nThen the double pointer finds the left and right nodes. End when both the left and right nodes do not exist\nvar widthOfBinaryTree = function(root) { var que = [root] var max = 0 while(que.length) { var len = que.length var left = 0 var right = que.length - 1 while(!que[left] \u0026amp;\u0026amp; left \u0026lt; len) { left ++ } while(!que[right] \u0026amp;\u0026amp; right \u0026gt;= 0) { right -- } if (left \u0026gt; right) { break } max = Math.max(max,right - left+ 1) for(let i = 0 ; i \u0026lt; len ; i ++) { var node = que.shift() if (!node) { que.push(node) que.push(node) continue } node.left ? que.push(node.left) : que.push(null) node.right ? que.push(node.right) : que.push(null) } } return max };Soon, the submission showed that the execution exceeded the time limit, so a queue for storing sequence numbers was added, which was synchronized with the BFS stack to calculate the left and right distances.\nvar widthOfBinaryTree = function(root) { var que = [root] var numQue = [1] var max = 0 while(que.length) { var len = que.length max = Math.max(numQue[numQue.length - 1] - numQue[0] + 1) for(let i = 0 ; i \u0026lt; len ; i ++) { var node = que.shift() var val = numQue.shift() if (node.left) { que.push(node.left) numQue.push(val * 2) } if (node.right) { que.push(node.right) numQue.push(val * 2 + 1) } } } return max };Soon, I was told that the stack overflowed. I then realized the seriousness of the problem (mistake\nThen I flipped through the answers\nThe first number of the current layer is subtracted from the table below each time.\nvar widthOfBinaryTree = function(root) { var que = [root] var numQue = [0] var max = 0 while(que.length) { var len = que.length var start = numQue[0] max = Math.max( numQue[numQue.length - 1] - start + 1,max) for(let i = 0 ; i \u0026lt; len ; i ++) { var node = que.shift() var val = numQue.shift() - start if (node.left) { que.push(node.left) numQue.push(val * 2 + 1 ) } if (node.right) { que.push(node.right) numQue.push(val * 2 + 2 ) } } } return max };","date":1615593600,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"1fd0c6b07ae297750cc2869e2b24a4c6","permalink":"https://siqi-liu.com/en/post/662-binary-tree-maximum-width/","publishdate":"2021-03-13T00:00:00Z","relpermalink":"/en/post/662-binary-tree-maximum-width/","section":"post","summary":"Time: 120 minutes The initial idea was to use BFS, and remove a whole queue when removing it (later I learned that this is called breadth traversal) Then the double pointer finds the left and right nodes. End when both …","tags":null,"title":"662-Binary Tree Maximum Width","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: 60 minutes\nThe idea is still very simple. First use DFS to add a parent to each node, and find the required target by the way.\nThen starting from the target, use DFS to find a point K away from the target.\nFind all the parents of target in turn and use DFS to find the point with a distance K.\nThe reason why it takes so much time is that after finding the parent, DFS is used to find it back, causing a memory overflow.\nThe way to avoid this is to find the parent, and if it is the left child, go to the right child (because the left child has already been found), otherwise go the other way.\nvar distanceK = function(root, target, K) { var node = null var res = [] var dfs = function (root) { if (root === target) { node = root } if (root.left) { root.left.parent = root dfs(root.left) } if (root.right) { root.right.parent = root dfs(root.right) } } dfs(root) var search = function (root,index) { if (!root) return if (index === K) { res.push(root.val) } root.left \u0026amp;\u0026amp; search(root.left,index + 1) root.right \u0026amp;\u0026amp; search(root.right,index + 1) } search(node,0) var index = 1 // The initial distance between the parent node and node is 1 while(index \u0026lt;= K \u0026amp;\u0026amp; node.parent) { if(index === K) { res.push(node.parent.val) break } if (node.parent.left === node) { search(node.parent.right,++index) } else { search(node.parent.left,++index) } node = node.parent } return res };A few points to note\nBe careful about checking for null points The parent node may be the node you need to find. If the parent node is already at distance k, there is no need to search further. ","date":1615593600,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"9a55f9fa6759e2f96b2fb1464460bc5d","permalink":"https://siqi-liu.com/en/post/863-all-nodes-with-distance-k-in-a-binary-tree/","publishdate":"2021-03-13T00:00:00Z","relpermalink":"/en/post/863-all-nodes-with-distance-k-in-a-binary-tree/","section":"post","summary":"Time: 60 minutes The idea is still very simple. First use DFS to add a parent to each node, and find the required target by the way. Then starting from the target, use DFS to find a point K away from the target. Find all …","tags":null,"title":"863-All nodes with distance -K- in a binary tree","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: 10 minutes\nJust use the increasing property of in-order traversal of the binary search tree\nIn-order traversal to find the left child Does pre exist? If not, assign , find the right child, and return 1 If it exists, is it less than val? If yes, do not increment, and return false If not, increment, assign pre, and return 1 var isValidBST = function(root) { var preVal = null var DFS = function(root) { var left = true var right = true if (root.left) { left = DFS(root.left) } if (preVal !== null) { if (preVal \u0026gt;= root.val) return false preVal = root.val } else { preVal = root.val } if (root.right) { right = DFS(root.right) } return left \u0026amp;\u0026amp; right } return DFS(root) };","date":1615593600,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"393ca0c3137cba7e28012656e63bfe0e","permalink":"https://siqi-liu.com/en/post/98-verify-binary-search-tree/","publishdate":"2021-03-13T00:00:00Z","relpermalink":"/en/post/98-verify-binary-search-tree/","section":"post","summary":"Time: 10 minutes Just use the increasing property of in-order traversal of the binary search tree In-order traversal to find the left child Does pre exist? If not, assign , find the right child, and return 1 If it …","tags":null,"title":"98-Verify Binary Search Tree","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: 60 minutes\nThe idea is still in-order traversal, using pre to cache the previous node. Because the in-order traversal is incremental, you must find the larger one first and then the smaller one.\nSo the first problem is pre, the second is root\nvar recoverTree = function(root) { var left = null var right = null var pre = null // You must find the big one first, then the small one, so the first one to have a problem is pre, and the second one to have a problem is root var DFS = function (root) { if (root.left) { DFS(root.left) } if (pre) { console.log(pre.val) if (pre.val \u0026gt; root.val) { if(!left) { left = pre right = root } else { right = root } } pre = root } else { pre = root } if (root.right) { DFS(root.right) } } DFS(root) if (left \u0026amp;\u0026amp; right) { [left.val,right.val] = [right.val,left.val] } return root}","date":1615593600,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"d99dc41abef192cf62bc60dd07867ac0","permalink":"https://siqi-liu.com/en/post/99-restore-binary-search-tree/","publishdate":"2021-03-13T00:00:00Z","relpermalink":"/en/post/99-restore-binary-search-tree/","section":"post","summary":"Time: 60 minutes The idea is still in-order traversal, using pre to cache the previous node. Because the in-order traversal is incremental, you must find the larger one first and then the smaller one. So the first …","tags":null,"title":"99-Restore Binary Search Tree","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: 30 minutes\nThe question is divided into two parts to be solved, search and delete\nThe search part is to find the node. You can find the required node according to the properties of the binary search tree.\nThere are three types of deletion\nIf there is only a left child, replace it with the left child. If there is only a right child, replace it with the right child. If there is neither child, assign null. (Since there was a recursive assignment earlier, this will succeed.) If there are both children, store the left child, replace it with the right child, and then find the left child’s position on the right. var deleteNode = function(root, key) { function insert(root,node) { if (root.val \u0026gt; node.val) { if (!root.left) { root.left = node } else { insert(root.left,node) } } else { if (!root.right) { root.right = node } else { insert(root.right,node) } } } function search(root) { if (!root) { return null } if (root.val \u0026gt; key) { root.left = search(root.left) } else if (root.val \u0026lt; key){ root.right = search(root.right) } else { if (!root.left \u0026amp;\u0026amp; !root.right) { root = null } else if (root.left \u0026amp;\u0026amp; root.right) { var left = root.left root = root.right insert(root,left) } else if (root.left) { root = root.left } else if (root.right) { root = root.right } } return root } return search(root) };Points to note\nConsider the case where the node does not exist. To delete a node, assign the left (or right) node the return value of the recursion. ","date":1615420800,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"03482321ea5fdc35db9f269218c8a3eb","permalink":"https://siqi-liu.com/en/post/450-delete-a-node-in-a-binary-search-tree/","publishdate":"2021-03-11T00:00:00Z","relpermalink":"/en/post/450-delete-a-node-in-a-binary-search-tree/","section":"post","summary":"Time: 30 minutes The question is divided into two parts to be solved, search and delete The search part is to find the node. You can find the required node according to the properties of the binary search tree. There are …","tags":null,"title":"450-Delete a node in a binary search tree","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"#669. Pruning a binary search tree\nRelease Date: March 11, 2021\nTime: 20 minutes\nSimilar to the previous question, most of them are divided into situations where the parent node is in the interval and not in the interval\nvar trimBST = function(root, low, high) { var add = function (root,node) { if (!node || !root) return null if (node.val \u0026gt; root.val ) { if (!root.right) { root.right = node } else { add(root.right,node) } } if (node.val \u0026lt; root.val) { if (!root.left) { root.left = node } else { add(root.left,node) } } } var walk = function (root) { if (!root) return root if (root.val \u0026lt; low || root.val \u0026gt; high) { var left = walk(root.left) var right = walk(root.right) if (!left \u0026amp;\u0026amp; !right) { root = null } else if (right \u0026amp;\u0026amp; left) { root = right add(root,left) } else if (left) { root = left } else { root = right } } else { root.left= walk(root.left) root.right = walk(root.right) } return root } return walk(root) };After reading the analysis, I found that it can be simpler. The idea is that if it is greater than the right boundary, look directly in the left branch; if it is less than the left boundary, look directly in the right branch. This saves the step of deleting the node.\nvar trimBST = function(root, low, high) { if(root == null) return null if (root.val \u0026gt; high) return trimBST(root.left,low,high) if(root.val \u0026lt; low) return trimBST(root.right,low,high) root.left = trimBST(root.left,low,height) root.right = trimBST(root.right,low,height) return root};","date":1615420800,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"49df7b2f7523aa181fe256de1bec29d9","permalink":"https://siqi-liu.com/en/post/669-pruning-a-binary-search-tree/","publishdate":"2021-03-11T00:00:00Z","relpermalink":"/en/post/669-pruning-a-binary-search-tree/","section":"post","summary":"#669. Pruning a binary search tree Release Date: March 11, 2021 Time: 20 minutes Similar to the previous question, most of them are divided into situations where the parent node is in the interval and not in the interval …","tags":null,"title":"669-Pruning a Binary Search Tree","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: Not done\nThe main reason I didn’t make it was that I kept thinking about how to use a stack. When I looked at the answer to the stack later, I found that recursion was the simplest.\nrecursion\nThe idea is very simple. First, write out how to insert a child node.\nIf the node is smaller than the parent node and the parent node’s left node is empty, make it the left node. If the node is larger than the parent node and the parent node’s right node is empty, make it the right node. If the node is smaller than the parent node, assign the parent node to the parent node’s left node and return to 1. If the node is larger than the parent node, assign the parent node to the parent node’s right node and return to 1. Traverse the entire array and insert each node one by one to get the result. var bstFromPreorder = function(preorder) { var add = function (node,val) { if (val \u0026lt; node.val \u0026amp;\u0026amp; !node.left) { node.left = new TreeNode(val) } if (val \u0026gt; node.val \u0026amp;\u0026amp; !node.right) { node.right = new TreeNode(val) } if (val \u0026lt; node.val) { add(node.left,val) } if (val \u0026gt; node.val) { add(node.right,val) } } var root = new TreeNode(preorder.shift()) for(let i = 0 ; i \u0026lt; preorder.length ; i ++) { add(root,preorder[i]) } return root};Time: Not done\nThe main reason I didn’t make it was that I kept thinking about how to use a stack. When I looked at the answer to the stack later, I found that recursion was the simplest.\nrecursion\nThe idea is very simple. First, write out how to insert a child node\nIf the node is smaller than the parent node and the parent node’s left node is empty, make it the left node. If the node is larger than the parent node and the parent node’s right node is empty, make it the right node. If the node is smaller than the parent node, assign the parent node to the parent node’s left node and return to 1. If the node is larger than the parent node, assign the parent node to the parent node’s right node and return to 1. Traverse the entire array and insert each node one by one to get the result. var bstFromPreorder = function(preorder) { var add = function (node,val) { if (val \u0026lt; node.val \u0026amp;\u0026amp; !node.left) { node.left = new TreeNode(val) } if (val \u0026gt; node.val \u0026amp;\u0026amp; !node.right) { node.right = new TreeNode(val) } if (val \u0026lt; node.val) { add(node.left,val) } if (val \u0026gt; node.val) { add(node.right,val) } } var root = new TreeNode(preorder.shift()) for(let i = 0 ; i \u0026lt; preorder.length ; i ++) { add(root,preorder[i]) } return root};","date":1615334400,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"a5f7ce9d0e908fbcf5526cbab15082da","permalink":"https://siqi-liu.com/en/post/1008-pre-order-traversal-to-construct-a-binary-search-tree/","publishdate":"2021-03-10T00:00:00Z","relpermalink":"/en/post/1008-pre-order-traversal-to-construct-a-binary-search-tree/","section":"post","summary":"Time: Not done The main reason I didn’t make it was that I kept thinking about how to use a stack. When I looked at the answer to the stack later, I found that recursion was the simplest. recursion The idea is very …","tags":null,"title":"1008-Pre-order traversal to construct a binary search tree","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: 25 minutes\nThe main idea is to use the stack. The difference from the ordinary level-order traversal is that the stack is cleared every time\nvar connect = function(root) { if (!root) return null var stack = [root] while(stack.length) { var _stack = [...stack,null] stack = [] var pre = _stack.shift() while(_stack.length || pre) { pre.left \u0026amp;\u0026amp; stack.push(pre.left) pre.right \u0026amp;\u0026amp; stack.push(pre.right) pre.next = _stack.shift() pre = pre.next } } return root};Obviously, the memory space of On I used does not meet the constant space requirement of the question, considering that the links of each layer can be found in the form of a linked list by relying on the parent node.\nwhile(pre) { var cur = pre while(cur) { cur.left.next = cur.right cur.right.next = cur.next.left cur = cur.next } }The remaining things to consider are\nPre needs to be saved as the leftmost node to facilitate subsequent linked list connections. Next is empty on the first level. The last level has no left or right nodes. Time: 25 minutes\nThe main idea is to use the stack. The difference from the ordinary level-order traversal is that the stack is cleared every time\nvar connect = function(root) { if (!root) return null var stack = [root] while(stack.length) { var _stack = [...stack,null] stack = [] var pre = _stack.shift() while(_stack.length || pre) { pre.left \u0026amp;\u0026amp; stack.push(pre.left) pre.right \u0026amp;\u0026amp; stack.push(pre.right) pre.next = _stack.shift() pre = pre.next } } return root};Obviously, the memory space of On I used does not meet the constant space requirement of the question, considering that the links of each layer can be found in the form of a linked list by relying on the parent node.\nwhile(pre) { var cur = pre while(cur) { cur.left.next = cur.right cur.right.next = cur.next.left cur = cur.next } }The remaining things to consider are\npre needs to be saved as the leftmost node to facilitate subsequent linking in a linked list. The next field on the first level is empty. The last level has no left or right nodes. ","date":1615334400,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"21f08f0ac7f62f8d6207e25fa0bb99d0","permalink":"https://siqi-liu.com/en/post/116-fill-in-the-next-right-node-pointer-of-each-node/","publishdate":"2021-03-10T00:00:00Z","relpermalink":"/en/post/116-fill-in-the-next-right-node-pointer-of-each-node/","section":"post","summary":"Time: 25 minutes The main idea is to use the stack. The difference from the ordinary level-order traversal is that the stack is cleared every time var connect = function(root) { if (!root) return null var stack = [root] …","tags":null,"title":"116-Fill in the next right node pointer of each node","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: I copied the answers and finished it.\nIt is easy to see that the idea is to use recursion, but the coding is the real problem.\nWhat we need to return is the root node, but the recursion is from bottom to top\nTry to solve it using dynamic programming, where dp(n) is the combination of dp(x) and dp (n - x - 1)\nvar allPossibleFBT = function(n) { var dp = [] var buld = function (n) { for(let i = 1 ; i \u0026lt;= n ; i ++ ) { if (i === 1) { dp[i] = new TreeNode(0) } else if (i % 2 === 0) { dp[i] = undefined } else { dp[i] = [] for(let left = 1; left \u0026lt; i; left ++) { var leftNodes = dp[left] var rightNodes = dp[i - left - 1] for(let j = 0 ; j \u0026lt; leftNodes.length ; j ++) { for(let k = 0 ; k \u0026lt; rightNodes.length ; k ++) { var node = new TreeNode(0) node.left = leftNodes[j] node.right = rightNodes[k] dp[i].push(node) } } } } } } buld(n) return dp[n] };","date":1615075200,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"1cb7fea6a2468346d071bd43f303308c","permalink":"https://siqi-liu.com/en/post/894-all-possible-full-binary-trees/","publishdate":"2021-03-07T00:00:00Z","relpermalink":"/en/post/894-all-possible-full-binary-trees/","section":"post","summary":"Time: I copied the answers and finished it. It is easy to see that the idea is to use recursion, but the coding is the real problem. What we need to return is the root node, but the recursion is from bottom to top Try to …","tags":null,"title":"894-All possible full binary trees","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: 30 minutes\nSerialization is simple and can be solved using BFS\nDeserialization requires the use of the properties of a binary tree, that is, the child nodes of the i-th node are (i + 1) * 2 - 1 and (i + 1) * 2 respectively.\nFind the parent node and push it onto the stack. At this point, pointer i is at the parent node’s val. Pop the parent node from the stack and find the parent node’s left and right nodes. The pointers find their values twice according to the rules. Push the left and right nodes onto the stack. i++ Repeat 1. var serialize = function(root) { if (!root) return [] var que = [root] var res = [] while(que.length) { var cur = que.shift() if (cur) { res.push(cur.val); que.push(cur.left); que.push(cur.right); } else { res.push(\u0026#39;null\u0026#39;); } } return res.join(\u0026#39;,\u0026#39;) };/** * Decodes your encoded data to tree. * * @param {string} data * @return {TreeNode} */var deserialize = function(data) { if (!data.length) return null var nodes = data.split(\u0026#39;,\u0026#39;) var i = 0 var root = new TreeNode(nodes[i]) var que = [root] while ( que.length) { var node = que.shift() var left = nodes[ (i + 1) * 2 - 1] var right = nodes[ (i + 1) * 2] if (left !== \u0026#39;null\u0026#39;) { node.left = new TreeNode(left) que.push(node.left) } else { node.left = null } if (right !== \u0026#39;null\u0026#39;) { node.right = new TreeNode(right) que.push(node.right) } else { node.right = null } i++ } return root};","date":1614816e3,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"758bf42dc0038c8908fb705c542ecea1","permalink":"https://siqi-liu.com/en/post/297-serialization-and-deserialization-of-binary-trees/","publishdate":"2021-03-04T00:00:00Z","relpermalink":"/en/post/297-serialization-and-deserialization-of-binary-trees/","section":"post","summary":"Time: 30 minutes Serialization is simple and can be solved using BFS Deserialization requires the use of the properties of a binary tree, that is, the child nodes of the i-th node are (i + 1) * 2 - 1 and (i + 1) * 2 …","tags":null,"title":"297-Serialization and Deserialization of Binary Trees","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: 25 minutes\nIn fact, this is a relatively common DFS type question. The reason why it took so long is because there was a problem in judging the boundary value.\nWhen starting from the root, since the root may not have child nodes, the len value is 0\nStarting from the child node, since the child nodes have been judged, it is considered that there is an edge of length 1 from the current node to the next node, so it is 1\nvar longestZigZag = function(root) { // true left false right var max = 0 var dfs = function (node,flag,len) { max = Math.max(len,max) if (flag) { node.right \u0026amp;\u0026amp; dfs(node.right,!flag,len + 1) node.left \u0026amp;\u0026amp; dfs(node.left,flag,1) } else { node.left \u0026amp;\u0026amp; dfs(node.left,!flag,len + 1) node.right \u0026amp;\u0026amp; dfs(node.right,flag,1) } } dfs(root,true,0) dfs(root,false,0) return max };Time: 25 minutes\nIn fact, this is a relatively common DFS type question. The reason why it took so long is because there was a problem in judging the boundary value.\nWhen starting from the root, since the root may not have child nodes, the len value is 0\nStarting from the child node, since the child nodes have been judged, it is considered that there is an edge of length 1 from the current node to the next node, so it is 1\nvar longestZigZag = function(root) { // true left false right var max = 0 var dfs = function (node,flag,len) { max = Math.max(len,max) if (flag) { node.right \u0026amp;\u0026amp; dfs(node.right,!flag,len + 1) node.left \u0026amp;\u0026amp; dfs(node.left,flag,1) } else { node.left \u0026amp;\u0026amp; dfs(node.left,!flag,len + 1) node.right \u0026amp;\u0026amp; dfs(node.right,flag,1) } } dfs(root,true,0) dfs(root,false,0) return max };","date":1614556800,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"d24f9cfa96059f398403fe26b0014d93","permalink":"https://siqi-liu.com/en/post/1372-longest-alternating-path-in-a-binary-tree/","publishdate":"2021-03-01T00:00:00Z","relpermalink":"/en/post/1372-longest-alternating-path-in-a-binary-tree/","section":"post","summary":"Time: 25 minutes In fact, this is a relatively common DFS type question. The reason why it took so long is because there was a problem in judging the boundary value. When starting from the root, since the root may not …","tags":null,"title":"1372-Longest Alternating Path in a Binary Tree","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Traversal itself is very simple. The problem that needs to be considered is how to mark each layer. Here we can use two approaches: iteration and recursion.\nIteration\nEnqueue root , then enqueue a null to indicate the end of level 0. Dequeue root , then enqueue the left and right nodes. If the dequeued item is null, it indicates the end of a level, and then enqueue a null. If the queue is not empty, loop 2. ⚠️ Note: The last item in the queue will remain null if not processed, so an end condition is required. var levelOrder = function(root) { var que = [root,null] var res = [] var level = [] if (!root ) return [] while(que.length) { var node = que.shift() if (node) { node.left \u0026amp;\u0026amp; que.push(node.left) node.right \u0026amp;\u0026amp; que.push(node.right) level.push(node.val) } else { res.push([...level]) level = [] if(que.length) { que.push(null) } } } return res }; ```recursion The key to the recursive solution is to pass the number of layers as a parameter ```javascript var levelOrder = function(root) { var res = [] if (!root) return [] var walk = function (node,index) { if (!res[index]) res[index] = [] res[index].push(node.val) node.left \u0026amp;\u0026amp; walk(node.left,index + 1) node.right \u0026amp;\u0026amp; walk(node.right,index + 1) } walk(root,0) return res };The steps are shown in the code\nCreate an array for the level you are on. Add root to the array. If there is a left or right node, continue to level 1, level + 1. Traversal itself is very simple. The problem that needs to be considered is how to mark each layer. Here we can use two approaches: iteration and recursion.\nIteration\nEnqueue root , then enqueue a null to indicate the end of level 0. Dequeue root , then enqueue the left and right nodes. If the dequeued item is null, it indicates the end of a level, and then enqueue a null. If the queue is not empty, loop 2. ⚠️ Note: The last item in the queue will remain null if not processed, so an end condition is required. var levelOrder = function(root) { var que = [root,null] var res = [] var level = [] if (!root ) return [] while(que.length) { var node = que.shift() if (node) { node.left \u0026amp;\u0026amp; que.push(node.left) node.right \u0026amp;\u0026amp; que.push(node.right) level.push(node.val) } else { res.push([...level]) level = [] if(que.length) { que.push(null) } } } return res }; ```recursion The key to the recursive solution is to pass the number of layers as a parameter ```javascript var levelOrder = function(root) { var res = [] if (!root) return [] var walk = function (node,index) { if (!res[index]) res[index] = [] res[index].push(node.val) node.left \u0026amp;\u0026amp; walk(node.left,index + 1) node.right \u0026amp;\u0026amp; walk(node.right,index + 1) } walk(root,0) return res };The steps are shown in the code\nCreate an array for the level you are at. Add root to the array. If a left or right node exists, continue to level 1, then level 1. ","date":1614470400,"description":"Techniques and examples for breadth-first (level-order) traversal of binary trees.","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"c7c00d673b03b5f960d7074840ad202d","permalink":"https://siqi-liu.com/en/post/102-level-order-traversal-of-a-binary-tree/","publishdate":"2021-02-28T00:00:00Z","relpermalink":"/en/post/102-level-order-traversal-of-a-binary-tree/","section":"post","summary":"Techniques and examples for breadth-first (level-order) traversal of binary trees.","tags":null,"title":"102 - Level-order Traversal of a Binary Tree","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: 10 minutes\nThe question is to find the path from the root node to the child nodes whose sum is equal to the target value. It is easy to think of using DFS. The path can be passed as a parameter. As long as the last node is a leaf node, it can be used.\nvar pathSum = function(root, sum) { var res = [] if (!root) return [] var dfs = function (node,visited,total) { if (sum === total \u0026amp;\u0026amp; (!node.left \u0026amp;\u0026amp; !node.right)) { res.push(visited) return } node.left \u0026amp;\u0026amp; dfs(node.left,[...visited,node.left.val],total + node.left.val) node.right \u0026amp;\u0026amp; dfs(node.right ,[...visited,node.right.val], total + node.right.val) } dfs(root,[root.val],root.val) return res };Time: 10 minutes\nThe question is to find the path from the root node to the child nodes whose sum is equal to the target value. It is easy to think of using DFS. The path can be passed as a parameter. As long as the last node is a leaf node, it can be used.\nvar pathSum = function(root, sum) { var res = [] if (!root) return [] var dfs = function (node,visited,total) { if (sum === total \u0026amp;\u0026amp; (!node.left \u0026amp;\u0026amp; !node.right)) { res.push(visited) return } node.left \u0026amp;\u0026amp; dfs(node.left,[...visited,node.left.val],total + node.left.val) node.right \u0026amp;\u0026amp; dfs(node.right ,[...visited,node.right.val], total + node.right.val) } dfs(root,[root.val],root.val) return res };","date":1614470400,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"ff5b735e21079929ad28d14c318edad0","permalink":"https://siqi-liu.com/en/post/133-path-sum-ii/","publishdate":"2021-02-28T00:00:00Z","relpermalink":"/en/post/133-path-sum-ii/","section":"post","summary":"Time: 10 minutes The question is to find the path from the root node to the child nodes whose sum is equal to the target value. It is easy to think of using DFS. The path can be passed as a parameter. As long as the last …","tags":null,"title":"133-Path Sum II","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"When traversing a binary tree in front, middle, or post-order, we use a stack to simplify operations. This is because they are all recursive structures of DFS, which means processing from bottom to top. However, I always start writing code from the root node, so I need a stack, and the stack is first-in-first-out. This way, I can process the root node last.\nLevel-order traversal is BFS, from top to bottom. The root element that was enqueued first is also the one I want to process first. This is why DFS uses a stack and BFS uses a queue.\nDFS algorithm flow and template First, place the root node in the stack.\nRemove the first node from the stack and check if it is the target. If all nodes are found, the search ends and the result is returned. Otherwise, add one of its unchecked direct child nodes to the stack.\nRepeat step 2.\nIf there are no unchecked direct child nodes, add the parent node to the stack.\nRepeat step 2.\nRepeat step 4.\nIf the stack is empty, the entire graph has been checked—that is, there is no target to be found. The search ends and the result is returned: “Target not found.”\nfunction dfs(root) { if (满足特定条件）{ // Return results or exit search space } for (const child of root.children) { dfs(child) } } ```### BFS algorithm flow and template 1. First, place the root node in the queue. 1. Remove the first node from the queue and check if it is the target. - If the target is found, the search ends and the result is returned. - Otherwise, all of its direct children that have not yet been checked are added to the queue. 1. If the queue is empty, the entire graph has been searched—that is, there is no target to be found. The search ends and the result is \u0026#34;target not found.\u0026#34; 1. Repeat step 2. ```javascript function bfs(root) { var que = [root] while(que.length) { var node = que.shift() if (node 是我们要找到的) return node node.left \u0026amp;\u0026amp; que.push(node.left) node.right \u0026amp;\u0026amp; que.push(node.right) } }","date":1614470400,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"46d6c22fd2dc967ce5579e0fac98f321","permalink":"https://siqi-liu.com/en/post/summary-of-binary-trees/","publishdate":"2021-02-28T00:00:00Z","relpermalink":"/en/post/summary-of-binary-trees/","section":"post","summary":"When traversing a binary tree in front, middle, or post-order, we use a stack to simplify operations. This is because they are all recursive structures of DFS, which means processing from bottom to top. However, I always …","tags":null,"title":"Summary of Binary Trees","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"As you can see from the previous questions, the recursive methods for front-, middle-, and post-order traversal of a binary tree are similar, but the iterative implementation is completely different.\nGet inspired by the three-color marking method of garbage collection and find their common points\nPush the node onto the stack and mark it as 0 (unvisited). Pop the node off the stack. If it has been visited, pop it. If it has not been visited, mark it as visited and push it onto the stack. Continue with the left and right nodes to 1. In this way, we can use similar code to iteratively traverse a binary tree in front, middle, and back order by controlling the order in which we push the nodes onto the stack. All we need to do is create an additional On space to store the node status. Take post-order traversal as an example\nvar postorderTraversal = function(root) { var stack = [[root,0]] var res = [] while(stack.length) { var [node,color] = stack.pop() if (color === 0) { // Here, we can complete the front-middle-post order traversal by controlling the order stack.push([node,1]) node.right \u0026amp;\u0026amp; stack.push([node.right,0]) node.left \u0026amp;\u0026amp; stack.push([node.left,0]) } else { res.push(node.val) } } return res };","date":1614384e3,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"906b39c5c839ade2b238d2f8c7ee4a89","permalink":"https://siqi-liu.com/en/post/two-color-labeling/","publishdate":"2021-02-27T00:00:00Z","relpermalink":"/en/post/two-color-labeling/","section":"post","summary":"As you can see from the previous questions, the recursive methods for front-, middle-, and post-order traversal of a binary tree are similar, but the iterative implementation is completely different. Get inspired by the …","tags":null,"title":"Two-color labeling","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"1h\nThe order of in-order traversal is left - right - center\nrecursion\nIt’s very easy with recursion\nvar postorderTraversal = function(root) { var res = [] if (!root) return res var travel = function (node) { node.left \u0026amp;\u0026amp; travel(node.left) node.right \u0026amp;\u0026amp; travel(node.right) res.push(node.val) } travel(root) return res }; ```Iteration 1. Push the root node onto the stack. 1. Determine whether a node can be popped. If so, record itself as the node that was popped and pop it. 1. If a node cannot be popped, push the right and left nodes onto the stack. 1. Repeat step 2. ```javascript var postorderTraversal = function(root) { var stack = [root] var res = [] if (!root) { return res } var pre = root while(stack.length) { var node = stack[stack.length - 1] // When the left/right node of a node is the last node to be output, it means that both the left and right nodes have been output (because the root node is at the end of the post-order traversal) if ( (!node.left \u0026amp;\u0026amp; !node.right) || (node.left === pre || node.right === pre)) { // Only when there is no left or right node, or both left and right nodes have been output, can node = stack.pop() be output. pre = node res.push(node.val) } else { if (node.right) { stack.push(node.right) } if(node.left) { stack.push(node.left) } } } return res }; ```Summarize Although I have completed the forward, in-order, and post-order traversal of a binary tree, I have a feeling that I will forget it soon, especially the in-order and post-order traversal. I have not found any similarities between the two, so I will make a summary later. 1h The order of in-order traversal is left - right - center recursion It\u0026#39;s very easy with recursion ```javascript var postorderTraversal = function(root) { var res = [] if (!root) return res var travel = function (node) { node.left \u0026amp;\u0026amp; travel(node.left) node.right \u0026amp;\u0026amp; travel(node.right) res.push(node.val) } travel(root) return res }; ```Iteration 1. Push the root node onto the stack. 1. Determine whether a node can be popped. If so, record itself as the node that was popped and pop it. 1. If a node cannot be popped, push the right and left nodes onto the stack. 1. Repeat step 2. ```javascript var postorderTraversal = function(root) { var stack = [root] var res = [] if (!root) { return res } var pre = root while(stack.length) { var node = stack[stack.length - 1] // When the left/right node of a node is the last node to be output, it means that both the left and right nodes have been output (because the root node is at the end of the post-order traversal) if ( (!node.left \u0026amp;\u0026amp; !node.right) || (node.left === pre || node.right === pre)) { // Only when there is no left or right node, or both left and right nodes have been output, can node = stack.pop() be output. pre = node res.push(node.val) } else { if (node.right) { stack.push(node.right) } if(node.left) { stack.push(node.left) } } } return res }; ```Summarize Although I\u0026#39;ve completed the forward, in-order, and post-order traversals of a binary tree, I have a feeling I\u0026#39;ll soon forget them. This is especially true for the in-order and post-order traversals. I haven\u0026#39;t found any similarities between them, so I\u0026#39;ll summarize them later. ","date":1614297600,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"a3c9fcacc89f28414328dfcfef5ea10b","permalink":"https://siqi-liu.com/en/post/145-post-order-traversal-of-a-binary-tree/","publishdate":"2021-02-26T00:00:00Z","relpermalink":"/en/post/145-post-order-traversal-of-a-binary-tree/","section":"post","summary":"1h The order of in-order traversal is left - right - center recursion It’s very easy with recursion var postorderTraversal = function(root) { var res = [] if (!root) return res var travel = function (node) { node.left \u0026\u0026 …","tags":null,"title":"145-Post-order traversal of a binary tree","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"10min\nThe order of pre-order traversal of a binary tree is center-left-right\nFirst traverse all root nodes and left nodes, then process the right node\nIt can be seen that this is a recursive behavior. Recursive problems can be simplified using stacks.\nIterative solution\nvar preorderTraversal = function(root) { if (!root) return null var stack = [root] var res = [] while(stack.length) { var node = stack.pop() res.push(node.val) node.right \u0026amp;\u0026amp; stack.push(node.right) node.left \u0026amp;\u0026amp; stack.push(node.left) } return res };Recursive solution\nRecursive problems can of course be solved recursively\nvar preorderTraversal = function(root) { var res = [] if (!root) return res var travel = function (node) { res.push(node.val) node.left \u0026amp;\u0026amp; travel(node.left) node.right \u0026amp;\u0026amp; travel(node.right) } travel(root) return res };10min\nThe order of pre-order traversal of a binary tree is center-left-right\nFirst traverse all root nodes and left nodes, then process the right node\nIt can be seen that this is a recursive behavior. Recursive problems can be simplified using stacks.\nIterative solution\nvar preorderTraversal = function(root) { if (!root) return null var stack = [root] var res = [] while(stack.length) { var node = stack.pop() res.push(node.val) node.right \u0026amp;\u0026amp; stack.push(node.right) node.left \u0026amp;\u0026amp; stack.push(node.left) } return res };Recursive solution\nRecursive problems can of course be solved recursively\nvar preorderTraversal = function(root) { var res = [] if (!root) return res var travel = function (node) { res.push(node.val) node.left \u0026amp;\u0026amp; travel(node.left) node.right \u0026amp;\u0026amp; travel(node.right) } travel(root) return res };","date":1614211200,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"f183e156fe65a54238be600de8077bac","permalink":"https://siqi-liu.com/en/post/144-pre-order-traversal-of-a-binary-tree/","publishdate":"2021-02-25T00:00:00Z","relpermalink":"/en/post/144-pre-order-traversal-of-a-binary-tree/","section":"post","summary":"10min The order of pre-order traversal of a binary tree is center-left-right First traverse all root nodes and left nodes, then process the right node It can be seen that this is a recursive behavior. Recursive problems …","tags":null,"title":"144-Pre-order traversal of a binary tree","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"30min\nThe first thing that comes to mind when I get the question is to sort it directly…\nBut we can optimize the quick sort to get the result\nSolution 1. Quick sort The idea of quick sort is to find a benchmark in the array and divide the array into the benchmark, the part smaller than the benchmark, and the part larger than the benchmark.\nThe benchmark itself has been sorted. If the benchmark is the kth largest element, the result can be obtained directly.\nIf the position of the benchmark is to the left of k, continue to look for the\nIf the benchmark is on the right side of k, continue to look for the part smaller than the benchmark.\nThere are three points to note\nHow to choose the base Math.floor(Math.random() * (r - l + 1) + l) Opening and closing intervals during sorting Remember to return the base after sorting function swap(a,i,j) { [a[i],a[j]] = [a[j],a[i]] } var findKthLargest = function(nums, k) { var res = null function quickSelect(a,l,r) { if (l \u0026gt; r) { return false } var piviot = Math.floor(Math.random() * (r - l + 1) + l); swap(a,l,piviot) let i = l,j = l + 1 // j increments // [ l ... i] is greater than or equal to the reference // [i + 1, .... r] is less than the reference while(j \u0026lt;= r) { if (a[j] \u0026gt;= a[l]) { swap(a,++i,j) } j++ } // Baseline homing swap(a,i,l) // At this time, i is a number greater than i + 1 var _k = i + 1 if (k === _k) { return res = a[i] } else if (_k \u0026lt; k) { // The benchmark is less than k, continue to search the right quickSelect(a,i+1,r) } else { quickSelect(a,l,i - 1) } } quickSelect(nums,0,nums.length - 1) return res }; ```### Solution 2. Max Heap Since we want to get the kth largest value, we can take the maximum value of the maximum heap k times, and the last time is the result we want. The difficulty here is that we implement a maximum heap, and the points we need to pay attention to are 1. Boundary conditions, such as when shifting Up, k must be as low as 0, and when shifting Down, the end condition is that there is no left child. 1. When shifting Down, it is necessary to determine whether the right child node exists. ```javascript function swap(a,i,j) { [a[i],a[j]] = [a[j],a[i]] } class MaxHeap{ constructor() { this.data = [] } shiftUp(k) { while(k \u0026gt;= 0 ) { let father = Math.floor( (k - 1) / 2 ) if (this.data[k] \u0026gt; this.data[father]) { swap(this.data,k,father) k = father } else { break } } } shiftDown(k) { let len = this.data.length while( 2 * k + 1 \u0026lt;= len ) { let left = 2 * k + 1 let right = 2 * k + 2 let j = k if (right \u0026lt;= len \u0026amp;\u0026amp; this.data[right] \u0026gt; this.data[k] \u0026amp;\u0026amp; this.data[right] \u0026gt; this.data[left]) { j = right } else if (this.data[left] \u0026gt; this.data[k]) { j = left } else { break } swap(this.data,j,k) k = j } } getMax() { let ret = this.data[0] swap(this.data,0,this.data.length - 1) this.data.pop() this.shiftDown(0) return ret } insert(a) { this.data[this.data.length] = a this.shiftUp(this.data.length - 1) } } var findKthLargest = function(nums, k) { var heap = new MaxHeap() for(let i = 0 ; i \u0026lt; nums.length ; i ++) { heap.insert(nums[i]) } var res = null while(k \u0026gt; 0) { res = heap.getMax() k-- } return res };30min\nThe first thing that comes to mind when I get the question is to sort it directly…\nBut we can optimize the quick sort to get the result\nSolution 1. Quick sort The idea of quick sort is to find a benchmark in the array and divide the array into the benchmark, the part smaller than the benchmark, and the part larger than the benchmark.\nThe benchmark itself has been sorted. If the benchmark is the kth largest element, the result can be obtained directly.\nIf the position of the benchmark is to the left of k, continue to look for the\nIf the benchmark is on the right side of k, continue to look for the part smaller than the benchmark.\nThere are three points to note\nHow to choose the base Math.floor(Math.random() * (r - l + 1) + l) Opening and closing intervals during sorting Remember to return the base after sorting function swap(a,i,j) { [a[i],a[j]] = [a[j],a[i]] } var findKthLargest = function(nums, k) { var res = null function quickSelect(a,l,r) { if (l \u0026gt; r) { return false } var piviot = Math.floor(Math.random() * (r - l + 1) + l); swap(a,l,piviot) let i = l,j = l + 1 // j increments // [ l ... i] is greater than or equal to the reference // [i + 1, .... r] is less than the reference while(j \u0026lt;= r) { if (a[j] \u0026gt;= a[l]) { swap(a,++i,j) } j++ } // Baseline homing swap(a,i,l) // At this time, i is a number greater than i + 1 var _k = i + 1 if (k === _k) { return res = a[i] } else if (_k \u0026lt; k) { // The benchmark is less than k, continue to search the right quickSelect(a,i+1,r) } else { quickSelect(a,l,i - 1) } } quickSelect(nums,0,nums.length - 1) return res }; ```### Solution 2. Max Heap Since we want to get the kth largest value, we can take the maximum value of the maximum heap k times, and the last time is the result we want. The difficulty here is that we implement a maximum heap, and the points we need to pay attention to are 1. Boundary conditions, such as …","date":1614211200,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"f8b26b47afd2575f0369c8c60d4c6ca2","permalink":"https://siqi-liu.com/en/post/215-kth-largest-element-in-an-array/","publishdate":"2021-02-25T00:00:00Z","relpermalink":"/en/post/215-kth-largest-element-in-an-array/","section":"post","summary":"30min The first thing that comes to mind when I get the question is to sort it directly… But we can optimize the quick sort to get the result Solution 1. Quick sort The idea of quick sort is to find a benchmark in the …","tags":null,"title":"215. Kth Largest Element in an Array","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"30min\nThe order of in-order traversal is left - middle - right\nrecursion It’s very easy with recursion\nvar inorderTraversal = function(root) { var res = [] if (!root) { return res } var travel = function (node) { node.left \u0026amp;\u0026amp; travel(node.left) res.push(node.val) node.right \u0026amp;\u0026amp; travel(node.right) } travel(root) return res }; ```Iteration The iterative steps are more complicated because the root node is not output first, so the root node needs to be retained 1. Push the root node onto the stack and check if it has a left child. If so, continue pushing until you reach a leaf node. 1. Pop the stack, output, and check if it has a right child. If so, push it onto the stack and continue with step 2. ```javascript var inorderTraversal = function(root) { var stack = [] var res = [] if (!root) return res stack.push(root) while(root.left) { stack.push(root.left) root = root.left } while(stack.length) { // At this point the top of the stack is the leftmost node in the tree var node = stack.pop() res.push(node.val) // There is a right node, push it into the stack and continue to look for the left node if (node.right) { node = node.right stack.push(node) while(node.left) { stack.push(node.left) node = node.left } } } return res };","date":1614211200,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"7334a1a91e55cce1281e9bcc1d908915","permalink":"https://siqi-liu.com/en/post/94-in-order-traversal-of-a-binary-tree/","publishdate":"2021-02-25T00:00:00Z","relpermalink":"/en/post/94-in-order-traversal-of-a-binary-tree/","section":"post","summary":"30min The order of in-order traversal is left - middle - right recursion It’s very easy with recursion var inorderTraversal = function(root) { var res = [] if (!root) { return res } var travel = function (node) { …","tags":null,"title":"94-In-order traversal of a binary tree","tldr":null,"type":"post"},{"authors":null,"categories":["frontend"],"content":"Understanding React Hooks\nRelease Date: February 25, 2021\nUnderstanding React Hooks\nThose who have used the old version of react should know that react’s class component has state to manage the internal state, as shown in the following example code\nclass Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } render() { return ( \u0026lt;div\u0026gt; \u0026lt;h1\u0026gt;Hello, world!\u0026lt;/h1\u0026gt; \u0026lt;h2\u0026gt;It is {this.state.date.toLocaleTimeString()}.\u0026lt;/h2\u0026gt; \u0026lt;/div\u0026gt; ); } }Function components do not have their own state, as shown in the following example code\nfunction Clock() { return ( \u0026lt;div\u0026gt; \u0026lt;h1\u0026gt;Hello, world!\u0026lt;/h1\u0026gt; \u0026lt;h2\u0026gt;It is {new Date().toLocaleTimeString()}.\u0026lt;/h2\u0026gt; \u0026lt;/div\u0026gt; ) }If I want to use a function component and want to have manual control over the date, I have to modify the component using props as follows, which transfers control to the parent component:\nfunction Clock(props) { return ( \u0026lt;div\u0026gt; \u0026lt;h1\u0026gt;Hello, world!\u0026lt;/h1\u0026gt; \u0026lt;h2\u0026gt;It is {props.date.toLocaleTimeString()}.\u0026lt;/h2\u0026gt; \u0026lt;/div\u0026gt; );} ```**In React 16.8, new features of Hooks enable function components to control their own ‘state’** ```javascript import React, { useState } from \u0026#39;react\u0026#39;;function Example() { const [count, setCount] = useState(0); return ( \u0026lt;div\u0026gt; \u0026lt;p\u0026gt;You clicked {count} times\u0026lt;/p\u0026gt; \u0026lt;button onClick={() =\u0026gt; setCount(count + 1)}\u0026gt; Click me \u0026lt;/button\u0026gt; \u0026lt;/div\u0026gt; );}Is it good? However, React has some clear rules for using Hooks: Don’t call Hooks in loops, conditions, or nested functions. Putting aside the question of why, how should you implement Hooks?\nThe useState function accepts a value as its initial value and returns an array.\n**This function will be rendered multiple times, so we need to ensure that the count and setCount returned during each render are the same as the previous one. ** The second point is the key to hooks, and its implementation is simple: an array.\nWhen rendering for the first time, store count and setCount in the array respectively, and take them out in order in the next rendering.\nThe following pseudo code implements a hook\n// Store statelet state = []; // Store state modification methodlet setters = []; let firstRun = true; let cursor = 0; // Create setterfunction createSetter(cursor) { return function setterWithCursor(newVal) { state[cursor] = newVal; };} export function useState(initVal) { if (firstRun) { state.push(initVal); setters.push(createSetter(cursor)); firstRun = false; } const setter = setters[cursor]; const value = state[cursor]; // cursor ensures the order of hooks cursor++; return [value, setter];} This also answers the previous question. If the hook is in a conditional statement, it may cause the cursor of the array stored in the first rendering to be inconsistent with that after that, causing the state order to be messed up.\nSummarize Hooks are nothing new. They are like an array maintained outside a function. Hooks are inserted into the function in sequence during the first rendering, and then taken out in sequence during subsequent renderings.\nrefer to https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e\n","date":1614211200,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"b44632a89896e320d9c9638b2b43bb0c","permalink":"https://siqi-liu.com/en/post/understanding-react-hooks/","publishdate":"2021-02-25T00:00:00Z","relpermalink":"/en/post/understanding-react-hooks/","section":"post","summary":"Release Date: February 25, 2021 Understanding React Hooks Those who have used the old version of react should know that react’s class component has state to manage the internal state, as shown in the following example …","tags":["React","Hook"],"title":"Understanding React Hooks","tldr":null,"type":"post"},{"authors":null,"categories":["Review Project"],"content":"A few years ago, the company needed to log in and register, and the estimated construction period was one month. As a result, the project became uncontrollable during the process, and it took almost three months to complete. In order not to waste the overtime work during this period, I reviewed the process of completing the project.\nAbout Requirements Review Let’s briefly review the requirements first. Registration/login has three channels: account/password, mobile phone number, and three-party (including WeChat QR code scanning, QQ authorization, and Weibo authorization). Through a series of rules and guidance, you will eventually reach the login/registration success page and then jump to the source web page.\nThe tasks to be completed include PC, mobile and WeChat.\nThe difference between the mobile version and the WeChat version is very small, the only difference is whether you can log in by scanning the WeChat QR code.\nThe requirements seem simple and clear, and it seems that a lot of logic can be reused, so the PC side is estimated to be half a month, the mobile side is estimated to be a week, and the rest is used for IE adaptation and problem solving.\nAbout technology selection Because it involves WeChat scan code login, the backend proposes that the interface use a polling method. I keep polling the status interface to obtain the status. For example, the WeChat login process requires WeChat scan code \u0026gt; obtain mobile phone number \u0026gt; guide to follow the official account \u0026gt; create/bind website account process, which roughly corresponds to the 4 fields given to me by the backend, and I determine which route to enter based on the presence or absence of the field.\nWhat problems were encountered and their solutions Old code The old code was a mountain of shit. Pages were a patchwork of HTML, inlined CSS/JS, and PHP statements, with a single page running to four or five thousand lines of code. It was even coupled with other projects’ CSS (referencing their shared CSS). PHP includes were nested layer upon layer, and even JS logic lay deep within. (The only bit of old code I kept was the third-party login code, which later introduced a hard-to-debug bug.)\nThe solution was to refactor the front-end. Initially, we wanted to integrate the logic for multiple pages into a single PHP file, monitoring the browser’s hash value to change routes. Later, we realized that each route had its own initialization logic, so we added a simple lifecycle. Later, we realized we needed to reuse components, so we adopted the Vue framework and imported them using the script tag.\nCode Reuse Due to some reasons of the old project path, import cannot be used, so include has to be used instead.\nAll variables that need to be reused should be made global, and symbols or singletons should be used to prevent variable pollution.\nThe Vue template is written in an HTML file, and the logic part uses the script tag include of x-template to introduce the Vue template code.\nStylesheet Use node-sass to monitor changes in scss files, compile scss, and generate corresponding css\ncompatibility The project needs to be compatible with IE10, so babel-browser and pollyfill are essential. Real-time compilation will affect performance to some extent, and I haven’t found a solution yet.\nThen the style needs to be compatible with post-css, and the prefixer plug-in of vscode is used directly.\nPolling\nEach page needs to poll the interface. If each route maintains its own timer, there will be a lot of duplicate code and the risk of memory leaks.\nTherefore, a polling singleton class is added to maintain the polling logic. The routing guard is used to start the timer when created and pass in some required parameters and callbacks, and then destroy it after destory.\nThe advantage of this is that it prevents the simultaneous existence of multiple timers due to forgetting to manually destroy them, and facilitates unified processing of the return value of the interface.\nReflection and Optimization In general, the pitfall of the front-end was the forced use of es6 + vue + sass without using any packaging tools. This solved some problems, but also introduced many problems.\nCode maintenance: Compared to the previous css style sheet, sass is indeed much clearer, but it also brings problems. Because there is no declaration file, other people who take over my code do not know that they need to use node-sass to compile it.\nUser Experience: Vue-router’s route redirection is a partial refresh, providing a better experience than traditional tag-based redirection. However, due to the several-second polling interval, some endpoints may be slow to respond. Most users definitely don’t want to experience lag when registering.\nAbout Performance: Babel compilation in a production environment can affect performance, but you can use Babel-compiled code in a production environment after the version is stable. Polling can also consume server resources if users remain idle. You can use …","date":1613865600,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"73d066234ec2dcbd042ae3e343fe5c62","permalink":"https://siqi-liu.com/en/post/review-of-login-and-registration-requirements/","publishdate":"2021-02-21T00:00:00Z","relpermalink":"/en/post/review-of-login-and-registration-requirements/","section":"post","summary":"A few years ago, the company needed to log in and register, and the estimated construction period was one month. As a result, the project became uncontrollable during the process, and it took almost three months to …","tags":null,"title":"Review of login and registration requirements","tldr":null,"type":"post"},{"authors":null,"categories":["leetcode"],"content":"Time: 40 minutes\n####105. Construct a binary tree from pre-order and in-order traversal sequences\nrecursion\nPre-order traversal: center-left-right\nIn-order traversal: left-middle-right\nTherefore, we can first find the root element from the pre-order traversal, and then determine the number of left and right subtrees from the in-order traversal.\nvar buildTree = function(preorder, inorder) { if (preorder.length === 0 || inorder.length=== 0) return null let nodeVal = preorder.shift() let node = new TreeNode(nodeVal) let index = inorder.indexOf(nodeVal) node.left = buildTree(preorder.slice(0,index), inorder.slice(0,index)) node.right = buildTree(preorder.slice(index),inorder.slice(index + 1)) return node }; ```Optimization Slices are very performance-intensive. In fact, there is no need to pass arrays. Functions can just pass pointers. ```javascript var buildTree = function(preorder, inorder) { var helper = function (p_start,p_end,i_start,i_end) { if (p_start \u0026gt; p_end || i_start \u0026gt; i_end ) return null let nodeVal = preorder[p_start] let node = new TreeNode(nodeVal) let index = inorder.indexOf(nodeVal) let left = index - i_start node.left = helper(p_start + 1 , p_start + left , i_start ,index - 1) node.right = helper(p_start + left + 1,p_end, index + 1,i_end) return node } return helper(0,preorder.length - 1,0,preorder.length - 1) };Summarize:\nUsing the properties of pre-order and in-order, locate the root node and find the number of left and right subtrees. Then recursively construct the left and right subtrees.\nTime: 40 minutes\n####105. Construct a binary tree from pre-order and in-order traversal sequences\nrecursion\nPre-order traversal: center-left-right\nIn-order traversal: left-middle-right\nTherefore, we can first find the root element from the pre-order traversal, and then determine the number of left and right subtrees from the in-order traversal.\nvar buildTree = function(preorder, inorder) { if (preorder.length === 0 || inorder.length=== 0) return null let nodeVal = preorder.shift() let node = new TreeNode(nodeVal) let index = inorder.indexOf(nodeVal) node.left = buildTree(preorder.slice(0,index), inorder.slice(0,index)) node.right = buildTree(preorder.slice(index),inorder.slice(index + 1)) return node }; ```Optimization Slices are very performance-intensive. In fact, there is no need to pass arrays. Functions can just pass pointers. ```javascript var buildTree = function(preorder, inorder) { var helper = function (p_start,p_end,i_start,i_end) { if (p_start \u0026gt; p_end || i_start \u0026gt; i_end ) return null let nodeVal = preorder[p_start] let node = new TreeNode(nodeVal) let index = inorder.indexOf(nodeVal) let left = index - i_start node.left = helper(p_start + 1 , p_start + left , i_start ,index - 1) node.right = helper(p_start + left + 1,p_end, index + 1,i_end) return node } return helper(0,preorder.length - 1,0,preorder.length - 1) };Summarize:\nUsing the pre-order and in-order properties, locate the root node and calculate the number of left and right subtrees. Then, recursively construct the left and right subtrees.\n","date":1611705600,"description":"","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"746c07ad2f36259e848ee4565292ce12","permalink":"https://siqi-liu.com/en/post/105-constructing-a-binary-tree-from-pre-order-and-in-order-traversal-sequences-1/","publishdate":"2021-01-27T00:00:00Z","relpermalink":"/en/post/105-constructing-a-binary-tree-from-pre-order-and-in-order-traversal-sequences-1/","section":"post","summary":"Time: 40 minutes ####105. Construct a binary tree from pre-order and in-order traversal sequences recursion Pre-order traversal: center-left-right In-order traversal: left-middle-right Therefore, we can first find the …","tags":null,"title":"105-Constructing a binary tree from pre-order and in-order traversal sequences-1","tldr":null,"type":"post"},{"authors":null,"categories":["Life"],"content":"I haven’t updated my blog for more than a year. I pulled down hexo s and found that the generated page was blank and there was no error.\nI checked the information and found that the theme configured in _config.yml was not downloaded: next\nSo go to next official website to find the corresponding download command```\ngit clone https://github.com/iissnan/hexo-theme-next themes/next\nI found the download speed was outrageous, so I switched the npm package source.``` git clone https://github.com.cnpmjs.org/iissnan/hexo-theme-next themes/nextAfter that, run hexo s --debug and the long-lost blog page finally appeared.\nThe next step is to deploy it to the github page\nNew Questions hexo d -g and then I get the error The \u0026#34;mode\u0026#34; argument must be integer. Received an instance of Object\nI searched online and found that the problem was caused by the npm version being too high.\nI can’t switch back to the old version of node. I have to write another project and then switch back.\nBased on the principle of using new instead of old, start upgrading hexo\nUpgrading hexo Here we use npm-upgrade to update packge.json```\nnpm install -g npm-upgrade npm-upgrade npm i\nAfter the operation, my package is as follows ```json { \u0026#34;name\u0026#34;: \u0026#34;hexo-site\u0026#34;, \u0026#34;version\u0026#34;: \u0026#34;0.0.0\u0026#34;, \u0026#34;private\u0026#34;: true, \u0026#34;hexo\u0026#34;: { ​ \u0026#34;version\u0026#34;: \u0026#34;5.2.0\u0026#34; }, \u0026#34;dependencies\u0026#34;: { ​ \u0026#34;hexo\u0026#34;: \u0026#34;^5.2.0\u0026#34;, ​ \u0026#34;hexo-asset-image\u0026#34;: \u0026#34;^1.0.0\u0026#34;, ​ \u0026#34;hexo-deployer-git\u0026#34;: \u0026#34;^2.1.0\u0026#34;, ​ \u0026#34;hexo-generator-archive\u0026#34;: \u0026#34;^1.0.0\u0026#34;, ​ \u0026#34;hexo-generator-category\u0026#34;: \u0026#34;^1.0.0\u0026#34;, ​ \u0026#34;hexo-generator-index\u0026#34;: \u0026#34;^2.0.0\u0026#34;, ​ \u0026#34;hexo-generator-searchdb\u0026#34;: \u0026#34;^1.3.3\u0026#34;, ​ \u0026#34;hexo-generator-tag\u0026#34;: \u0026#34;^1.0.0\u0026#34;, ​ \u0026#34;hexo-renderer-ejs\u0026#34;: \u0026#34;^1.0.0\u0026#34;, ​ \u0026#34;hexo-renderer-marked\u0026#34;: \u0026#34;^3.3.0\u0026#34;, ​ \u0026#34;hexo-renderer-stylus\u0026#34;: \u0026#34;^2.0.1\u0026#34;, ​ \u0026#34;hexo-renderer-swig\u0026#34;: \u0026#34;^1.1.0\u0026#34;, ​ \u0026#34;hexo-server\u0026#34;: \u0026#34;^2.0.0\u0026#34;, ​ \u0026#34;hexo-theme-next\u0026#34;: \u0026#34;^8.1.0\u0026#34; } } ```##### New issues after upgrading After the upgrade, I deployed it to github page. Sure enough, a new problem occurred. I found that the website after deployment only showed `hexo {% extends \u0026#39;_layout.swig\u0026#39; %}` Using debug, I found a warning.``` INFO Validating config WARN Deprecated config detected: \u0026#34;external_link\u0026#34; with a Boolean value is deprecated. See https://hexo.io/docs/configuration for more details.It turns out that the external_link attribute has been changed to object type. Immediately modify _config.yml```\nexternal_link: enable: true\n`\nhexo s` again found that the problem was not solved I found a solution from [issue: incompatible with hexo 5](https://github.com/ahonn/hexo-theme-even/issues/266). It turns out that Hexo 5 deleted the swig rendering plugin and needs to be installed separately.npm i hexo-renderer-swig npm i hexo-renderer-swig\nAgain `hexo s` success ##### Minor Adjustments Next theme has page turning display problem, just change it to``` {% if page.prev or page.next %} \u0026lt;nav class=\u0026#34;pagination\u0026#34;\u0026gt; {{ paginator({ prev_text: \u0026#39;上页\u0026#39;, next_text: \u0026#39;下页\u0026#39;, mid_size: 1 }) }} \u0026lt;/nav\u0026gt; {% endif %}","date":1607690275,"description":"Filling Diary","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"b6c76e19e28e20b81b3dbf554b3866d6","permalink":"https://siqi-liu.com/en/post/hexo-upgraded-to-5/","publishdate":"2020-12-11T12:37:55Z","relpermalink":"/en/post/hexo-upgraded-to-5/","section":"post","summary":"Filling Diary","tags":null,"title":"Hexo upgraded to 5","tldr":null,"type":"post"},{"authors":null,"categories":["Life"],"content":"Update after a year\nI have been lazy and haven’t updated since I joined Dahua last year.\n2021 is coming soon, and I feel that I need to make a summary of the past. I will write whatever comes to my mind.\nAbout the front-end During the interview, I was always asked how I understood the front end and what plans I had.\nIt took me four years, but I still haven’t figured this out.\nWhen you don’t know anything, think about learning xxx framework and understanding xxx source code, and you will be one step closer to the truth.\nSo I started looking at vue, react, started using ts, started to learn how to pair webpack, used nodeJs, and started to practice algorithm questions.\nThen I gradually liked simplicity. TS was too troublesome to write, so I gave it up. What to pair with webpack? Isn’t umi good? Isn’t imgcook fast for automatically generating code? Isn’t it good to write a script and then use egg and curd without any brains?\nMaybe this is the situation of most small front-end developers. There are no project highlights worth mentioning. They go through the cycle of writing business and learning new knowledge. From time to time, they are amazed by the tools developed by big guys. After npm i and npm start, they exclaim that it’s really good.\nAbout Components Participating in the development of the Dahua component library has given me valuable experience. I think the challenge lies not in implementing the business functions, but in making the components maintainable during subsequent modifications and expansions.\nLet’s take an example of setting multiple time period components by sliding the mouse.\nThe original requirement was for 3 time periods, but later we added 1 more. Fortunately, the time period logic was extracted, so the implementation didn’t take too long.\nThe original requirement was for the time period 00:00:00 - 24:00:00, but later the requirement for 23:59:59 was added. I had to add a configuration and modify the time scale display.\nOriginally, there was a button that popped up a window to copy a time period, but later several buttons with different functions were added. This became a problem. The fixed-width component that was originally set for calculation convenience had to be changed to an adaptive one, which affected the calculations in many places.\nLater, there were various requirements such as lock, customizable themes, and daily changes to monthly changes.\nTo sum up, there are several points\nDo a good job of decoupling, avoid coupling with data, and reduce dependencies between components. Otherwise, a slight change in requirements will require tinkering with thousands of lines of component code.\nDon’t believe the person who initially asks for the component and says, “Just use it on this page.” Consider the situation where the component is a subcomponent, a small screen, or a pop-up window.\nMaking good use of higher-order components in React helps simplify component code.\nDo unit testing well.\nAbout Data Structure/Algorithm A year ago, like most front-end developers, I thought that the algorithm would never be used in the project and there was no point in writing it.\nThis year, I wanted to go to Microsoft Suzhou, so I started practicing LC questions.\nAfter doing more than a hundred questions on my own, I changed my mind.\nFirst of all, it adds a new way of thinking. For example, if I write a three-layer loop, I will consider whether to optimize the time complexity or whether to prune. If I encounter some difficult-to-use data sent from the backend, I will consider whether there are any data structures such as dictionary trees or graphs to optimize.\nDuring interviews at small companies, it’s hard to come up with any highlights worth mentioning. Due to the lack of information, the things I painstakingly created might turn out to be something others have already implemented, and perhaps even better thought out. And no matter how well I memorize the eight-part essay, it’s just a matter of not losing points. However, doing the exercises at least gives me a chance to compete with students from larger companies.\nIf you don’t try these things and just listen to others saying that algorithms are not used in the front end, you won’t be able to understand them.\nFuture Plans I will continue to write algorithm questions in a casual manner and will update the article later.\nI have been amazed by imgcook recently. In addition to using it at work, I want to learn more about related AI recognition knowledge.\n","date":1607645850,"description":"One month after leaving Dahua","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"168ee1bf5289c6cd21184ce0024e70cd","permalink":"https://siqi-liu.com/en/post/update-after-a-year/","publishdate":"2020-12-11T00:17:30Z","relpermalink":"/en/post/update-after-a-year/","section":"post","summary":"One month after leaving Dahua","tags":["JavaScript","Frontend"],"title":"Update after a year","tldr":null,"type":"post"},{"authors":null,"categories":["Frontend"],"content":"About CSS pseudo-class selector: nth-child(n) I had a bad interview these days because of a simple question.\nThe question is probably like this, asking me which part is marked in red\nNaively, I would have answered with the p element.\nThe answer is of course that no element is marked red, because nth-child will first find the nth child element under all parent elements, and then find p among them.\nThat is, the set it finds in the first step is two spans, which do not contain p elements.\nIf you want to mark the p in red, you need to write the selector like this:\np:nth-of-type(1)\nThis is first select the p under the parent element, and then find the first p among them.\nbut\nThe story doesn’t end here. I tried to select the p tag under the nested p tag.\nShockingly, no elements are selected.\nI tried p \u0026gt; p :nth-child(1) but no element was selected. p:nth-of-type(1) also failed to select the element.\nOnly in this way can p1 be successfully selected.\nI already thought that it might be a violation of HTML standards, but p is a block element, so why can’t I nest another p? So I went to Mozilla to check it out.\nEvery HTML element must follow rules that define what kind of content it can contain. These rules are grouped into several common element content models. Each HTML element belongs to zero, one, or more content models, and each model has rules that ensure that the element’s content conforms to an HTML-conformant document.\nThe definition of the p element\nThe description is a p element that represents a paragraph of text. The key point is that the allowed content is Phrasing content. Let’s take a look at this Phrasing content.\nThere is no p tag in it, so I think the nth-child selector is optimized according to the HTML specification, so it will not look for p elements under p elements, right?\nLet’s look at this structure again\nHow it is rendered in the browser\nAs you can see, the outer wrapped p is rendered into two p elements by the browser. You can guess that if the CSS is written as\np { color:red }span2 will not be selected either.\n","date":1551979939,"description":"About nth-child and HTML specifications","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"85b20b7d43ffc5fa048ad28fb11977b9","permalink":"https://siqi-liu.com/en/post/css-pseudo-class-selector-nth-child/","publishdate":"2019-03-07T17:32:19Z","relpermalink":"/en/post/css-pseudo-class-selector-nth-child/","section":"post","summary":"About nth-child and HTML specifications","tags":["Frontend"],"title":"CSS pseudo-class selector nth-child","tldr":null,"type":"post"},{"authors":null,"categories":["frontend"],"content":"What you need to prepare First, install these things\nnodeJs , npm\nmongodb , adminMongo (optional)\nvue-element-admin\nexpress\nFor the front-end page, I decided to use vue-element-admin. After git clone, you can see the complete development framework.\nFront-end login ideas The verification idea is that after a successful login, the server returns a token (which uniquely identifies the user), and then stores the token in a local cookie. The next time the page is opened or refreshed, the user’s login status can be remembered.\nAfter the user logs in successfully, the global hook router.beforeEach will be used to determine whether a token has been obtained. After obtaining the token, the user’s information will be obtained.\nLet’s look at the main code of the specific login page\n//index.vue handleLogin() { this.$refs.loginForm.validate(valid =\u0026gt; { if (valid) { this.loading = true this.$store.dispatch(\u0026#39;LoginByUsername\u0026#39;, this.loginForm).then(() =\u0026gt; { this.loading = false this.$router.push({ path: this.redirect || \u0026#39;/\u0026#39; }) }).catch(() =\u0026gt; { this.loading = false }) } else { console.log(\u0026#39;error submit!!\u0026#39;) return false } }) }, After clicking Login, the form is validated and the Vuex LoginByUsername action is triggered. Search the code to see what this asynchronous operation does.\n//user.js import { loginByUsername, logout, getUserInfo } from ‘@/api/login’ import { getToken, setToken, removeToken } from ‘@/utils/auth’ actions: { LoginByUsername({ commit }, userInfo) { const username = userInfo.username.trim() return new Promise((resolve, reject) =\u0026gt; { loginByUsername(username, userInfo.password).then(response =\u0026gt; { const data = response.data commit(‘SET_TOKEN’, data.token) setToken(response.data.token) resolve() }).catch(error =\u0026gt; { reject(error) }) }) }, … You can see this LoginByUsername calls the loginByUsername method of login.js, which accepts two parameters: username and password. It can be inferred that loginByUsername sends a request, submits the SET_TOKEN method after the request is completed, and calls setToken in auth.js.\n// login.js import request from \u0026#39;@/utils/request\u0026#39; export function loginByUsername(username, password) { const data = { username, password } return request({ url: \u0026#39;/login/login\u0026#39;, method: \u0026#39;post\u0026#39;, data }) } You can see that a request is sent here, where request should be the method that encapsulates axios. We will see how to encapsulate it later.\n//user.js mutations: { SET_TOKEN: (state, token) =\u0026gt; { state.token = token }, You can see that SET_TOKEN updates the token value returned by the request to the state of vuex.\n//auth.js import Cookies from ‘js-cookie’ const TokenKey = ‘Admin-Token’ export function setToken(token) { return Cookies.set(TokenKey, token) } The setToken method stores the token in a cookie.\nBackstage ideas According to the logic of the front-end, we need a login interface. When logging in, we query from the database and return the token to the front-end if it is correct. We also need an interface to receive the token and return the specific information of the user to the front-end.\nexpress Let’s start with express\nUse the express-generator application generator to quickly create an application skeleton. After generating, you should see the following directory structure.\nnpm install //Install dependent modules npm start //Start the project\nBecause it is troublesome to restart npm start after each modification, I use nodemon for hot updates.\nnpm install -g nodemon //Install nodemon You can start the express application\nLet’s look at the structure of the express project\nbin is the startup directory of the application,\nvar http = require(\u0026#39;http\u0026#39;); var port = normalizePort(process.env.PORT || \u0026#39;3000\u0026#39;); app.set(\u0026#39;port\u0026#39;, port); var server = http.createServer(app); server.listen(port); server.on(\u0026#39;error\u0026#39;, onError); server.on(\u0026#39;listening\u0026#39;, onListening); public is the static file directory of the project\nroutes controls routing, we need to make a login interface here\nviews is the view file, we put the html file here\nmongodb Registration and login require a database. I used MongoDB and mongoose to operate the database. The documentation already explains how to do this, so I won’t go into detail here.\nCreate a database folder in the express directory, create the model.js file, and the bdHandler.js handler file.\n// models.js module.exports = { user:{ username:{type:String,required:true}, password:{type:String,required:true}, roles:{type:String,required:true} } };\n// bdHandler.js // Provide other files for model operations var mongoose = require(‘mongoose’); var Schema = mongoose.Schema; var models = require(\u0026#34;./models\u0026#34;);\nfor(var m in models){ mongoose.model(m,new Schema(models[m])); }\nmodule.exports = { getModel: function(type){ return _getModel(type); } };\nvar _getModel = function(type){ return mongoose.model(type); };\nNext, we need to connect to the database and set the bdHandler as a global for easy use in routes.\n//Add in app.js global.dbHandel = require(’./database/dbHandler’); global.db = …","date":1544724910,"description":"Keywords nodejs vue express mongodb","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"3b22dbb33e1accf234bf7c55dcda3b8a","permalink":"https://siqi-liu.com/en/post/vue-express-mongodb-build-a-backend-login-system/","publishdate":"2018-12-13T18:15:10Z","relpermalink":"/en/post/vue-express-mongodb-build-a-backend-login-system/","section":"post","summary":"Keywords nodejs vue express mongodb","tags":["Vue","Node.js","Database"],"title":"Vue + express + mongodb build a backend login system","tldr":null,"type":"post"},{"authors":null,"categories":["JavaScript","JavaScript"],"content":"Comparing POST and GET when using XHR When sending data to a server, GET is faster because, for small amounts of data, a GET request only sends one packet to the server. A POST request, on the other hand, sends at least two packets: one for headers and one for the POST body. POST is better suited for sending large amounts of data to a server, firstly because it doesn’t care about the number of extra packets, and secondly because Internet Explorer has a limit on URL length.\nGET should be used for requests that do not change the server state and only retrieve data (idempotent behavior). Data requested by GET will be cached, which helps improve the performance of multiple requests.\nWhat is dynamic script injection? What are its characteristics? var scriptElement = document.createElement(‘script’) scriptElement.src = ‘http://xxx.com/lib.js\u0026#39; document.getElementsByTagName(‘head’)[0].appendChild(scriptElement) function jsonCallBack(jsonString) { var data = eval(’(’ + jsonString + ‘)’) }\n// lib.js jsonCallBack({“status”:1}) Using JavaScript to create a new script tag and set the src attribute to a URL from a different domain allows for cross-domain data requests.\nThe request header cannot be set, only the GET method can be used, and access must wait until all data is returned.\nThe response message must be executable JS code\nAny code injected into a page using scripts can control the page, including modifying content and redirecting to other websites, so you need to be careful when introducing code from external sources.\nWhat are the characteristics of MXHR (Multipart XHR)? It can combine multiple http requests into one request. Reducing the number of requests will improve the performance of the page.\nThe element is created using a data:URL and therefore cannot be cached by the browser. This does not affect website behavior when using a separate packaged JS or CSS file for each page. For example, when using a single page, you can simply load the CSS externally once.\nWhat are Beacons? Use JavaScript to create an Image object and set the src attribute to the URL of the script on the server. The URL contains the key-value pairs that need to be transmitted.\nvar url = ‘/status_tracker.php’; var params = [‘userName=yosgi’,‘step=2’]; (new Image).src = url + ‘?’ + params.join(’\u0026amp;’); // This code sends a request to /status_tracker.php?step=2\u0026amp;time=23311\nThis code does not return any information to the client, and no image is actually displayed.\nAlthough the performance cost is small, the length of the data that can be sent is very small because the URL has a maximum length. You can only determine whether the server has accepted the data by monitoring the onload event of the Image object.\nThe difference between JSON and JSON-P When using XHR, the JSON data is returned as a string, which is then converted into a native object using eval().\nThe JSON-P data is treated as another Js file and executed as native code.\nJSON-P can be used across domains, but should not be used when dealing with sensitive data.\nAbout Custom Formats Example of creating a custom format:\n‘John;Jack;David’\nJust simply link the data with a delimiter and use split() after receiving it.\nWhen creating a custom format, it is best to use a single character that should not exist in the data. The first few characters of the ASCII character set work well in most server languages.\n\\u0001 \\u0002\nSummary of Ajax optimization methods: Reduce the number of requests, merge JS and CSS files, or use MXHR Shorten the page loading time. After the main content of the page is loaded, use Ajax to get the secondary Know when to use a mature Ajax library and when to write your own low-level Ajax code (most javascript libraries do not allow direct access to the readystatechange event) ","date":1543416460,"description":"High Performance Javascript Knowledge Points","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"21914ed0d141b7a20b21959ba2c73f29","permalink":"https://siqi-liu.com/en/post/ajax-data-transmission-optimization/","publishdate":"2018-11-28T14:47:40Z","relpermalink":"/en/post/ajax-data-transmission-optimization/","section":"post","summary":"High Performance Javascript Knowledge Points","tags":["JavaScript"],"title":"Ajax data transmission optimization","tldr":null,"type":"post"},{"authors":null,"categories":["JavaScript","JavaScript"],"content":"How to understand that Javascript is single-threaded and browsers are multi-threaded? The browser kernel is multi-threaded (the browser uses a separate process for each tab page. Relationship between processes and threads), and is usually composed of the following resident threads:\nGUI rendering thread\nJavaScript engine thread\nTimer trigger thread\nEvent trigger thread\nAsynchronous http request thread\nJavascript is single-threaded, because Js needs to operate the DOM tree. If it is multi-threaded to operate the DOM, UI operation conflicts may occur.\nHow to record code execution time? You can use native Date objects to track the running time of your code\nvar start = + Date() //(+) can convert a Date object into a number\nHow to use timers to decompose tasks and add time detection mechanisms to improve them? var todo = item.concat() // Clone the original array setTimeout(function() { // Get the next element in the array and process it process(todo.shift()) // If there are more elements to process, create another timer if (todo.length \u0026gt; 0) { setTimeout(arguments.callee, 25) } else { callback(items) } }, 25)\nSometimes executing just one task is not efficient. For example, if you process an array of 1000 items, each item takes 1 millisecond to process. If each timer processes only one item, and there is a 25 millisecond delay between each processing, the total time to process the array is (25 + 1) x 1000 milliseconds. If you change it to process 50 items at a time, the processing time is (1000 / 50) * 25 + 1000 = 1500 milliseconds, which is faster than a single processing.\nfunction timedProcessArray(items, process, callback) { var todo = items.concat() setTimeout(function () { // Arrow functions cannot be used here because they have no arguments var start = +new Date() do { process(todo.shift()) // Check the time after processing. If the time taken is less than 50 milliseconds, continue processing } while(todo.length \u0026gt; 0 \u0026amp;\u0026amp; (+new Date() - start \u0026lt; 50)) if(todo.length \u0026gt; 0 ) { setTimeout(arguments.callee, 25) } else { callback() } },25) }\nDoes the timer affect performance? The above code uses a timer sequence. Only one timer exists at a time, and the next one is created only when the timer expires. This usage does not cause performance issues.\nProblems arise when multiple repeating timers are created simultaneously, as all the timers compete for the right time to run. Low-frequency repeating timers with intervals of 1 second or more are not a problem, but multiple repeating timers with intervals between 100 and 200 milliseconds can cause problems. For this reason, you should use a single repeating timer to perform multiple operations at once.\nSummarize JavaScript tasks should not take too long to execute (100 milliseconds), otherwise it will cause delays in UI updates and affect the user experience.\nTimers can delay the execution of code and break down long-running code into multiple short-running codes.\nAbout the Event Loop The event loop is the main thread reading events from the task queue. This process is continuous and is called the event loop.\nWhen the main thread runs, it creates a heap and a stack. The code in the stack calls various external APIs, which add various events (click, load, done) to the “task queue.” Once the code in the stack completes execution, the main thread reads the “task queue” and executes the callback functions corresponding to those events in sequence.\nThe timer event is placed in the task queue. The meaning of setTimeout(fn,100) is that it adds an event to the end of the task queue after 100 milliseconds. Therefore, the callback function will not be executed until both the synchronous task and the existing events in the task queue are processed. Therefore, there is no way to guarantee that the callback function will be executed at the time specified by setTimeout().\n","date":1542905721,"description":"High Performance Javascript Knowledge Points","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"27398791ce5876294aaf42a759d53ed2","permalink":"https://siqi-liu.com/en/post/ui-interface-response-optimization/","publishdate":"2018-11-22T16:55:21Z","relpermalink":"/en/post/ui-interface-response-optimization/","section":"post","summary":"High Performance Javascript Knowledge Points","tags":["JavaScript"],"title":"UI interface response optimization","tldr":null,"type":"post"},{"authors":null,"categories":["JavaScript","JavaScript"],"content":"How to understand backtracking? What is backtracking out of control? When matching a string, a regular expression tests its components from left to right, requiring a decision when encountering quantifiers (* + ? {2}) and branches (|).\nAfter a decision is made, other options may be recorded for future reference.\nIf the current match succeeds, continue testing the following expressions. If all succeed, the process ends.\nIf no matching value is found or a subsequent match fails, the regular expression backtracks to the last decision point and selects a recorded choice.\nThis process continues until a match is found or all permutations of the branches fail, at which point it abandons the match and moves on to the next character in the string.\nRunaway backtracking is a situation where a regular expression causes the browser to freeze.\nFor example:\nUse a regular expression that matches the tag\nlet reg = /\u0026lt;html\u0026gt;[\\s\\S]*?\u0026lt;head\u0026gt;[\\s\\S]*?\u0026lt;title\u0026gt;[\\s\\S]*?\u0026lt;\\/title\u0026gt;[\\s\\S]*?\u0026lt;\\/head\u0026gt;[\\s\\S]*?\u0026lt;body\u0026gt;[\\s\\S]*?\u0026lt;\\/body\u0026gt;[\\s\\S]*?\u0026lt;\\/html\u0026gt;/ to string\nlet str = `\u0026lt;html\u0026gt;\u0026lt;head\u0026gt;\u0026lt;title\u0026gt;\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt;\u0026lt;body\u0026gt;\u0026lt;/body\u0026gt;` To match, when the last [\\s\\S]*? extends to the end of the string, because the last html closing tag is missing, the regular expression will try to expand to the second to last [\\s\\S]*? to match the last body closing tag, and then continue to search for the second closed body tag until the end of the string, and so on.\nLazy and Greedy Quantifiers The greedy quantifier (*) means repeating 0 or more times and matching as many times as possible.\nThe lazy quantifier (*?) will match 0 or more times, and will try to match 0 times.\nexample:\nlet reg1 = /\u0026lt;html\u0026gt;[\\s\\S]*?\u0026lt;\\/html\u0026gt;/ let reg2 = /\u0026lt;html\u0026gt;[\\s\\S]*\u0026lt;\\/html\u0026gt;/ let str = `\u0026lt;html\u0026gt;\u0026lt;html\u0026gt;sdasds\u0026lt;\\/html\u0026gt;\u0026lt;\\/html\u0026gt;` str.match(reg1)//[\u0026#39;\u0026lt;html\u0026gt;\u0026lt;html\u0026gt;sdasds\u0026lt;/html\u0026gt;\u0026#39;] str.match(reg2)//[\u0026#39;\u0026lt;html\u0026gt;\u0026lt;html\u0026gt;sdasds\u0026lt;/html\u0026gt;\u0026lt;/html\u0026gt;\u0026#39;] Use regular expressions to remove leading and trailing spaces? Using two regular expressions String.prototype.$trim = function() { return this.replace(/^\\s+/,\u0026#34;\u0026#34;).replace(/\\s+$/,\u0026#34;\u0026#34;) }\nTwo subexpressions are used: one to remove leading whitespace, and the other to remove trailing whitespace. The same idea can be used with a single expression.\nString.prototype.$trim = function() { return this.replace(/^\\s+|\\s+$/g, \u0026#34;\u0026#34;) } This will be slower than the above method because both branch options will be tested for each string match.\nUsing capture groups String.prototype.$trim = function() { return this.replace(/^\\s*([\\s\\S]*?)\\s*$/, \u0026#34;$1\u0026#34;) } The lazy quantifier in the capture array will cause the regular expression to backtrack, because the lazy quantifier *? of the [\\s\\S] class requires as few repetitions as possible, so the regular expression must stop and try to match the remaining \\s*? every time it matches a character. Consider the optimization plan\nString.prototype.$trim = function() { return this.replace(/^\\s*([\\s\\S]\\S)?\\s$/, “$1”) }\nFor performance reasons, the lazy quantifier is replaced with a greedy quantifier. To ensure that the capture group matches the last non-blank character, the trailing \\S is required. In this expression, the greedy quantifier * in [\\s\\S]* indicates that any character class is repeated until the end of the string. The regular expression then backtracks one character at a time until it matches the next \\S or reaches the first character.\nNot using regular expressions String.prototype.$trim = function() { var start = 0, end = this.length - 1 ws = “\\n\\r\\t\\f” // Some whitespace characters, not all while (ws.indexOf(this.charAt(start)) \u0026gt; -1) { start ++ } while (end \u0026gt; start \u0026amp;\u0026amp; ws.indexOf(this.charAt(end)) \u0026gt; -1) { end – } return this.slice(start, end + 1) // The array items returned by the slice method do not include the items indexed by the second parameter }\nThe advantage of this version is that it is not affected by the total length of the string. The disadvantage is that it is not suitable for handling large stretches of leading and trailing whitespace characters because the loop traversal efficiency is not as good as regular expressions. (Not being able to remember all whitespace characters is also a disadvantage.)\nHybrid Solutions\nYou can use regular expressions to filter out leading spaces and use non-regular expression methods to remove trailing characters.\nString.prototype.$trim = function() { var str = this.replace(/^\\s+/,\u0026#34;\u0026#34;), end = str.length - 1, ws = /\\s/; while (ws.test(str.charAt(end))) { end – } return str.slice(0,end + 1) }\nThis solution uses a regular expression in a loop to check for whitespace at the end of the string, making it possible to use the browser-defined list of whitespace characters.\nin conclusion In regular expression-based solutions, the total length of the string affects performance more than the number of characters trimmed. Non-regular expression solutions start searching from the end and are not affected by the total length of the string, but by the number of spaces trimmed.\nSimply using regular expressions twice is a good solution. …","date":1541519294,"description":"High Performance Javascript Knowledge Points","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"6fcde0b57cc2c853f4183b62b8aac624","permalink":"https://siqi-liu.com/en/post/regular-expression-performance-optimization/","publishdate":"2018-11-06T15:48:14Z","relpermalink":"/en/post/regular-expression-performance-optimization/","section":"post","summary":"High Performance Javascript Knowledge Points","tags":["JavaScript"],"title":"Regular expression performance optimization","tldr":null,"type":"post"},{"authors":null,"categories":["JavaScript","JavaScript"],"content":"Performance comparison of for, while, and do-while loops. How can I improve them? The performance of the three loops is comparable. Avoid using for-in loops.\nThe best way to improve loop performance is to reduce the amount of computation per iteration and to reduce the number of loop iterations.\nWhy does a reverse-order loop improve performance? // Forward order let len = items.length for (let i = 0; i \u0026lt; len ; i++) { } // Reverse order for (let i = items.length; i– ; ) { }\nBecause the number of iterations is reduced. The control condition is reduced from two comparisons (is the number of iterations less than the total number? Is it true?) to one comparison (is it true?).\nExample: How to use iteration instead of recursion? // Recursive form of the Fibonacci sequence function fibonacci(num) { if(num === 1 || num === 2) { return 1 } return fibonacci(num - 1) + fibonacci(num - 2) } // Iterative form function fib(num) { var n1 = 1, n2 = 1, n = 1; for(var i = 3; i \u0026lt;= num; i++) { n = n1 + n2 n1 = n2 n2 = n } return n\n","date":1540478039,"description":"High Performance Javascript Knowledge Points","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"ac3aced1c59e90519ea29d520b6f9088","permalink":"https://siqi-liu.com/en/post/algorithm-and-process-control-optimization/","publishdate":"2018-10-25T14:33:59Z","relpermalink":"/en/post/algorithm-and-process-control-optimization/","section":"post","summary":"High Performance Javascript Knowledge Points","tags":["JavaScript"],"title":"Algorithm and process control optimization","tldr":null,"type":"post"},{"authors":null,"categories":["JavaScript","JavaScript"],"content":"Why is DOM slow? Because the DOM and JavaScript are implemented independently in the browser, this separation allows other technologies and languages such as VBScript to share the use of the DOM, but also causes performance consumption.\nAre HTML collections arrays? What precautions should be taken when manipulating HTML collections? HTML collection objects are not arrays because they lack methods like push() or slice(). However, they do provide a length property. HTML collections exist in a “hypothetically live” state and automatically update when the underlying document object is updated.\nvar alldivs = document.getElementsByTagName(‘div’) for (var i = 0 ; i \u0026lt; alldivs.length; i++) { document.body.appendChild(document.createElement(‘div’)) }\nThe above code is an infinite loop because the loop’s exit condition, alldivs.length, increases with each iteration. It’s also very slow because the query is performed on each iteration.\nTherefore, when operating the HTML collection, you should first copy it to a normal array to reduce the number of accesses. And use local variables when accessing collection elements.\nWhat’s the difference between querySelectorAll() and getElementsByTagName()? Why is the first method recommended? The querySelectorAll() method takes a CSS selector as a parameter and returns a NodeList. It doesn’t return an HTML collection, so it doesn’t reflect the live document structure, avoiding performance and potential logic issues.\nWhat are repaint and reflow? After the browser downloads all the components of the page, including HTML, CSS, JS, and images, it generates two internal component structures: the DOM tree and the render tree. The DOM tree represents the page structure, and the render tree represents how the DOM nodes are displayed.\nAfter the DOM tree and render tree are constructed, the browser will start drawing the page elements. When the DOM changes affect the geometric properties of the elements, the browser needs to recalculate the geometric properties of the elements. Similarly, the geometric properties of other elements will also be affected. The browser will invalidate the affected part of the original render tree and rebuild the render tree. This process is called reflow. After the reflow is completed, the affected part is redrawn to the screen, which is called repaint.\nThey can make your web application’s UI unresponsive, so try to minimize these processes.\nWhat operations will force a refresh of the render tree queue? offsetTop,offsetLeft… (get offset)\nscrollTop, scrollLeft… (get the scroll position)\nclientTop,clientWidth…\ngetComputedStyle (computed style value)\nWhen does reflow occur? How can we reduce the impact of redrawing and reflowing? When the layout and geometric properties of the page change, such as deleting or adding DOM elements; the position of elements changes; the size of elements changes; the page renderer is initialized; the browser window changes.\nTo reduce the impact of redrawing and reflowing, you can combine multiple DOM style changes and process them all at once, or modify the class directly.\nWhen a series of operations need to be performed on a DOM element, you can remove the element from the document flow, make multiple changes to the element, and then bring the element back into the document flow. This process will only trigger two reflows. Otherwise, any operation on the element will trigger a reflow.\nA common approach is to use a document fragment to build a subtree outside the current DOM and then copy it back into the document.\nAbout event delegation In the previous DOM event flow\nSummarize There are several ways to improve the performance of DOM operations:\nMinimize DOM access times\nIf you need to access a DOM node multiple times, you should use local variables to store references.\nWhen processing HTML collections, it is recommended to cache the length in a variable. If frequent operations are required, it is recommended to copy it into an array.\nUse faster APIs such as querySelectorAll() and firstElementChild whenever possible\nPay attention to redrawing and rearranging, and do not query layout information when modifying it\nUsing absolute positioning and dragging and dropping proxies in animations\n","date":1536764973,"description":"High Performance Javascript Knowledge Points","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"c9b02b3ed16df8b4c8e87a4905deeb9d","permalink":"https://siqi-liu.com/en/post/performance-optimization-of-dom-operations/","publishdate":"2018-09-12T15:09:33Z","relpermalink":"/en/post/performance-optimization-of-dom-operations/","section":"post","summary":"High Performance Javascript Knowledge Points","tags":["JavaScript"],"title":"Performance optimization of DOM operations","tldr":null,"type":"post"},{"authors":null,"categories":["frontend"],"content":"What is MVVM? How does it differ from MVC? MVC stands for Model-View-Controller. It organizes code by separating business logic, data, and user interface. Communication is one-way: the view sends instructions to the controller. After completing the business logic, the controller instructs the model to change its state. The model then sends the new data to the view, and the user receives feedback.\nMVVM stands for Model-View-ViewModel. Like the MVC pattern, its primary purpose is to separate the view and model. However, the MVVM controller doesn’t listen to browser events. Instead, it listens to a property table. Browser events modify properties, triggering methods in the controller. This adds a layer of business control, called the VM. When data in the ViewModel changes, the View layer is updated. When a view declares two-way data binding (usually to form elements), the framework also monitors changes in the View layer (form). Once the value changes, the data in the ViewModel bound to the View layer is automatically updated.\nNew Vue is the viewmodel\nHow to chain filters and pass parameters? Series\n{{ message | filterA | filterB }}\nAccepts parameters\n{{ message | filterA(‘arg1’, arg2) }}\nHere, filterA is defined as a filter function that accepts three parameters: the value of message as the first parameter, the plain string ‘arg1’ as the second parameter, and the value of the expression arg2 as the third parameter.\nWhat is the difference between computed properties and methods? What is the difference between computed properties and listener properties? The difference between computed properties and methods is that computed properties are cached based on dependencies and are only evaluated when the relevant dependencies change.\nListeners allow us to perform asynchronous operations (access an API) and set intermediate states (like loading…).\nComputed properties have only getters by default, but you can also provide a setter if needed.\nHow does $watch observe instance expressions? How does it observe changes within an object? How does it trigger a callback immediately? Observe Expression\nvm.$watch( function () { return this.a + this.b }, function (newVal, oldVal) { } ) Changes within the object\nvm.$watch(‘someObject’, callback, { deep: true }) vm.someObject.nestedValue = 123\nThe callback is triggered immediately.\nvm.$watch(‘a’, callback, { immediate: true })\nWhat is the difference between v-show and v-if? v-if supports v-else and v-else-if syntax, and also supports syntax; v-show does not support these;\nv-show is achieved by simply switching the CSS attribute display property of the element to achieve the display and hide effect;\nv-if is a “true” conditional rendering, because it ensures that event listeners and child components within the conditional block are properly destroyed and recreated during the toggle process;\nv-if is also lazy: if the condition is false on initial rendering, nothing is done - the conditional block is not rendered until the condition becomes true for the first time;\nIn contrast, v-show is much simpler - the element will always be rendered regardless of the initial conditions, and is simply toggled based on CSS;\nWhat is the function of key in v-if? In order to render elements more efficiently, Vue usually reuses elements, and unique key values will prevent elements from being reused but re-rendered.\nHow should the key in v-for be used? Why is the key needed? When using v-for to update a list of rendered elements, the default in-place reuse strategy is used; when the list data is modified, it will determine whether a value has been modified based on the key value. If modified, it will re-render the item, otherwise reuse the previous element;\nTherefore, the key is to use an item in the array that does not change as the key value, that is, each data has a unique and unchanging ID to identify the uniqueness of this data. (Instead of using the index value of the array, because it will change after inserting a new item)\nThe purpose of key is to update the virtual DOM efficiently. In addition, when using transitions between elements with the same tag name in Vue, the key attribute is also used. Its purpose is to allow Vue to distinguish them. Otherwise, Vue will only replace its internal properties without triggering the transition effect.\nHow to make Vue detect the use of index to directly set an item or modify the length of the array? // Vue.set Vue.set(vm.items, indexOfItem, newValue) // Array.prototype.splice vm.items.splice(indexOfItem, 1, newValue)\nUsing modifiers to convert user input to numeric values? Automatically filtering leading and trailing whitespace in user input? Why must data be a function? Because only in this way can each instance maintain an independent copy of the returned object, otherwise the modification of one instance will affect other instances.\nHow to globally import basic components in batches from a folder? import Vue from ‘vue’ import upperFirst from …","date":1536604210,"description":"\u003cES6 Standard Introduction\u003e Knowledge Points Summary","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"7692ea65b198ee3174cdd6b46e2414f7","permalink":"https://siqi-liu.com/en/post/summary-of-vue-s-easy-to-error-and-easy-to-confuse-knowledge-points/","publishdate":"2018-09-10T18:30:10Z","relpermalink":"/en/post/summary-of-vue-s-easy-to-error-and-easy-to-confuse-knowledge-points/","section":"post","summary":"Knowledge Points Summary","tags":["JavaScript","Vue"],"title":"Summary of Vue's easy-to-error and easy-to-confuse knowledge points","tldr":null,"type":"post"},{"authors":null,"categories":["JavaScript"],"content":"Why should the \u0026lt;script\u0026gt; tag be placed at the bottom of the \u0026lt;body\u0026gt; tag whenever possible? Placing \u0026lt;script\u0026gt; tags at the end of the \u0026lt;body\u0026gt; helps avoid blocking page rendering because downloading and executing scripts can modify the DOM and block other resource downloads. Many browsers use a single thread for UI rendering and JavaScript execution, so moving scripts to the bottom reduces perceived load time and improves responsiveness.\nWhat are the defer and async attributes and how do they behave? defer indicates that the script will not modify the DOM during parsing, so its execution can be deferred safely. The browser will begin downloading the script as it encounters the \u0026lt;script src=\u0026#34;...\u0026#34;\u0026gt; tag during parsing, but will delay execution until the document has been parsed; deferred scripts are executed in order and before the DOMContentLoaded/load events. The defer attribute only applies when src is present.\nasync loads scripts asynchronously. Like defer, it downloads the script in parallel with other resources, but the key difference is execution timing: an async script runs as soon as it finishes downloading (which can interrupt parsing), whereas defer scripts wait until parsing completes and execute in document order.\nUse async for independent scripts (analytics, ads) and defer for scripts that rely on the document structure or need predictable ordering.\nWhat are dynamic script elements and how do you create them? You can create \u0026lt;script\u0026gt; elements dynamically via DOM methods, which allows non-blocking loading and execution. Dynamically injecting a \u0026lt;script\u0026gt; and appending it to the document starts download without blocking other tasks, making it a widely used technique for non-blocking script loading.\nExample:\nfunction loadScript(url, callback) { var script = document.createElement(\u0026#39;script\u0026#39;); script.type = \u0026#39;text/javascript\u0026#39;; script.onload = function () { callback(); // Ensure the script is ready before calling other code that depends on it }; script.src = url; document.getElementsByTagName(\u0026#39;head\u0026#39;)[0].appendChild(script); }This pattern starts downloading the script and invokes the callback when the script is loaded and executed.\nHow does XMLHttpRequest-based script injection work and what are its limitations? You can fetch script text via XMLHttpRequest (or fetch) and then inject it into the page by creating a \u0026lt;script\u0026gt; element with the response text. This avoids an extra round-trip for script loading in some scenarios but has limitations:\nSame-origin policies and CORS restrictions apply when fetching external resources. Injected scripts run with the privileges of inline code and may be harder to cache or reuse as separate static assets. Debugging is less straightforward because the script appears inline rather than as a separate file in dev tools (unless you add source mapping or use blob URLs). Example:\nvar xhr = new XMLHttpRequest(); xhr.open(\u0026#39;GET\u0026#39;, \u0026#39;test.js\u0026#39;, true); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if ((xhr.status \u0026gt;= 200 \u0026amp;\u0026amp; xhr.status \u0026lt; 300) || xhr.status === 304) { var script = document.createElement(\u0026#39;script\u0026#39;); script.type = \u0026#39;text/javascript\u0026#39;; script.text = xhr.responseText; document.getElementsByTagName(\u0026#39;head\u0026#39;)[0].appendChild(script); } } }; xhr.send(null);Use this approach carefully and prefer proper caching headers and CORS configuration when loading cross-origin code.\nWhy should the \u0026lt;script\u0026gt; tag be placed at the bottom of the \u0026lt;body\u0026gt; tag whenever possible? Placing \u0026lt;script\u0026gt; tags at the end of the \u0026lt;body\u0026gt; helps avoid blocking page rendering because downloading and executing scripts can modify the DOM and block other resource downloads. Many browsers use a single thread for UI rendering and JavaScript execution, so moving scripts to the bottom reduces perceived load time and improves responsiveness.\nWhat are the defer and async attributes and how do they behave? defer indicates that the script will not modify the DOM during parsing, so its execution can be deferred safely. The browser will begin downloading the script as it encounters the \u0026lt;script src=\u0026#34;...\u0026#34;\u0026gt; tag during parsing, but will delay execution until the document has been parsed; deferred scripts are executed in order and before the DOMContentLoaded/load events. The defer attribute only applies when src is present.\nasync loads scripts asynchronously. Like defer, it downloads the script in parallel with other resources, but the key difference is execution timing: an async script runs as soon as it finishes downloading (which can interrupt parsing), whereas defer scripts wait until parsing completes and execute in document order.\nUse async for independent scripts (analytics, ads) and defer for scripts that rely on the document structure or need predictable ordering.\nWhat are dynamic script elements and how do you create them? You can create \u0026lt;script\u0026gt; elements dynamically via DOM methods, which allows non-blocking loading and execution. Dynamically injecting a \u0026lt;script\u0026gt; and appending it to the document starts download …","date":1536052991,"description":"Notes from 'High Performance JavaScript'","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"a69b8a2326ff0bd30225261805eb1208","permalink":"https://siqi-liu.com/en/post/javascript-loading-optimization/","publishdate":"2018-09-04T09:23:11Z","relpermalink":"/en/post/javascript-loading-optimization/","section":"post","summary":"Notes from 'High Performance JavaScript'","tags":["JavaScript"],"title":"JavaScript Loading Optimization","tldr":null,"type":"post"},{"authors":null,"categories":["JavaScript"],"content":"I have never been particularly clear about this concept. This time I will summarize it through data and experiments.\nDefinition Event Bubbling IE’s event flow is called event bubbling. This means that an event begins at the most specific element (the event target) and propagates upward to less specific nodes (the document). All modern browsers support event bubbling and will propagate events all the way to the window object.\nEvent Capture The idea behind event capture is that less specific nodes should receive events first, while the most specific nodes should receive them last. Event capture is used to catch events before they reach their intended target. It’s supported by IE9+, Safari, Chrome, Opera, and Firefox, and captures events starting from the window (despite the DOM Level 2 event specification requiring it to start from the document). Because older browsers don’t support it, event capture is rarely used.\nDOM Event Flow DOM Level 2 events define an event flow as consisting of three phases: the event capture phase, the target phase, and the event bubbling phase. Event capture occurs first, providing an opportunity to intercept the event. Then, the target actually receives the event. Finally, the bubbling phase allows you to respond to the event.\nLet’s look at the example below\n\u0026lt;div id=\u0026#34;wrapDiv\u0026#34;\u0026gt;wrapDiv \u0026lt;p id=\u0026#34;middleP\u0026#34;\u0026gt;middleP \u0026lt;span id=\u0026#34;innerSpan\u0026#34;\u0026gt;innerSpan\u0026lt;/span\u0026gt; \u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; #wrapDiv, #middleP, #innerSpan{ margin: 5px;padding: 5px;box-sizing: border-box;cursor: default; } #wrapDiv{ width: 300px;height: 300px;border: indianred 3px solid; } #middleP{ width: 200px;height: 200px;border: hotpink 3px solid; } #innerSpan{ display: block; width: 100px; height: 100px; border: orange 3px solid; } var wrapDiv = document.getElementById(\u0026#34;wrapDiv\u0026#34;); var middleP = document.getElementById(\u0026#34;middleP\u0026#34;); var innerSpan = document.getElementById(\u0026#34;innerSpan\u0026#34;); Event Capture Elements (Bubble) Triggering Order First, let’s look at event capture.\nmiddleP.addEventListener(\u0026#34;click\u0026#34;, function(e){ console.log(\u0026#34;middleP capture\u0026#34;, e.target.nodeName, e.currentTarget.nodeName); }, true); wrapDiv.addEventListener(\u0026#34;click\u0026#34;, function(e){ console.log(\u0026#34;wrapDiv capture\u0026#34;, e.target.nodeName, e.currentTarget.nodeName); }, true); innerSpan.addEventListener(\u0026#34;click\u0026#34;, function(e){ console.log(\u0026#34;innerSpan capture\u0026#34;, e.target.nodeName, e.currentTarget.nodeName); }, true); //wrapDiv captures SPAN DIV middleP captures SPAN P innerSpan captures SPAN SPAN As you can see, the order of binding does not affect the capture order. Event capture is… Less specific nodes should receive events earlier\nThe event bubbling is\n** That is, the event is initially received by the most specific element (event target), and then propagates upward to less specific nodes**\nThis point will not be tested any more.\ne.target and e.currentTarget Let’s talk about the two properties of the above code, e.target and e.currentTarget, you can see\ntarget is the trigger event, and currentTarget is the listener event\nWhich happens first, event capturing or bubbling? Next, do another experiment to see which happens first, capture or bubbling\n// Bubble phase events middleP.addEventListener(“click”, function(e){ console.log(“middleP Bubble”, e.target.nodeName, e.currentTarget.nodeName); }, false); wrapDiv.addEventListener(“click”, function(e){ console.log(“wrapDiv Bubble”, e.target.nodeName, e.currentTarget.nodeName); }, false); innerSpan.addEventListener(“click”, function(e){ console.log(“innerSpan Bubble”, e.target.nodeName, e.currentTarget.nodeName); }, false); // Capture phase binding events middleP.addEventListener(“click”, function(e){ console.log(“middleP Capture”, e.target.nodeName, e.currentTarget.nodeName); }, true); wrapDiv.addEventListener(“click”, function(e){ console.log(“wrapDiv capture”, e.target.nodeName, e.currentTarget.nodeName); }, true); innerSpan.addEventListener(“click”, function(e){ console.log(“innerSpan capture”, e.target.nodeName, e.currentTarget.nodeName); }, true); //wrapDiv capture SPAN DIV // middleP capture SPAN P //innerSpan bubbles SPAN SPAN //innerSpan capture SPAN SPAN // middleP bubbles SPAN P // wrapDiv bubbles SPAN DIV\nIn theory, capturing events should occur before bubbling.\nHowever, innerSpan’s bubbling occurs first. The explanation for this result is:\nIn the event flow of the target phase, the order in which event handlers are called is the order in which they were registered.\nIn other words, when the event reaches element p, target and currentTarget are identical, indicating that capture is no longer necessary. At this point, the pre-determined event handlers are executed sequentially, and bubbling continues after completion.\nevent.stopPropagation The function stopPropagation stops further propagation of the current event during both the capture and bubbling phases.\nLet’s use the example above again. Note that I added e.stopPropagation() during the bubbling phase of innerspan.\n// Bubble phase events middleP.addEventListener(“click”, function(e){ …","date":1535563785,"description":"DOM event flow is easy to confuse","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"56956d87c58ab558ca86c6af8311f2bc","permalink":"https://siqi-liu.com/en/post/dom-event-flow/","publishdate":"2018-08-29T17:29:45Z","relpermalink":"/en/post/dom-event-flow/","section":"post","summary":"is easy to confuse","tags":["JavaScript"],"title":"DOM event flow","tldr":null,"type":"post"},{"authors":null,"categories":["JavaScript"],"content":"Array You are no stranger to arrays, so let’s summarize the methods of arrays.\nThe following are some common methods that are easy to remember incorrectly or mix up\nmap I saw it in a bad interview question\n[“1”, “2”, “3”].map(parseInt)\nLet’s review this problem. First, let’s look at the parameters in a typical map.\nvar new_array = arr.map(function callback(currentValue[, index[, array]]) { // Return element for new_array }[, thisArg]) You can see that callback and thisArg need to be passed as parameters, which are the function that generates the new array elements and the this value used when executing the callback function.\nThe callback accepts three parameters.\ncurrentValue: The current element being processed in the array.\nindex: The index of the current element being processed in the array.\narray: The array on which the map method is called.\nLet’s look at parseInt again.\nparseInt(string, radix) // string Required. The string to be parsed. // radix Optional. Indicates the radix of the number to be parsed. This value is between 2 and 36.\nIf this parameter is omitted or is 0, the number is parsed in base 10. If it begins with “0x” or “0X”, it is parsed in base 16.\nIf this parameter is less than 2 or greater than 36, parseInt() returns NaN.\nCombining these two, and using the formulas\nparseInt(‘1’,0) = 1, parseInt(‘2’,1) = NaN, parseInt(‘3’,2) = NaN\nwe can get the result [1,NaN,NaN];\nSolice and split\narray.splice(start[, deleteCount[, item1[, item2[, ...]]]]) The splice function adds or removes elements at any position in an array. The first parameter specifies the index of the element to be removed or inserted, the second parameter specifies the number of elements to be removed (0 for adding elements), and the third and subsequent parameters are the values to be added to the array.\narray.slice(start,end)\nThe slice() method returns a shallow copy of a portion of an array selected from the beginning to the end (excluding the end) into a new array object. The original array is not modified.\nStack A stack is a collection that follows the last-in-first-out principle.\nImplementing the Stack Class\nlet Stack = (function () { const items = new WeakMap() class Stack { constructor() { items.set(this, []) } push(element) { let s = items.get(this) s.push(element) //Note that the return value is the length of the array } pop() { let s = items.get(this) let r = s.pop() return r } isEmpty () { let s = items.get(this); return s.length==0 } } return Stack })()\nStack Application - Base Conversion\nfunction baseConverter(decNumber,base) { let remStack = new Stack(),rem,binaryString = “\u0026#34;,chars =“0123456789ABCDEF”; while(decNumber\u0026gt;0){ //Push remainder onto the stack rem = decNumber%base //Round the dividend decNumber = Math.floor(decNumber/base); remStack.push(rem); } while(!remStack.isEmpty()){ binaryString+=chars[remStack.pop()] } return binaryString } let result = baseConverter(10,2)//1010 let result1 = baseConverter(105,16)//69\nQueue A queue is a collection that follows a first-in-first-out principle.\nImplementing a Queue Class\nlet Queue = (function () { const items = new WeakMap() class Queue { constructor() { items.set(this, []) } enqueue(element) { let q = items.get(this) q.push(element)//Note that the return value is the length of the array } dequeue() { let q = items.get(this) let r = q.shift() return r } isEmpty () { let q = items.get(this); return q.length==0 } size () { let q =items.get(this); return q.length } } return Queue })() Application of Queue - Pass the Flower\nfunction hotPotato(nameList, num){ let queue = new Queue() for(let i=0;i\u0026lt;nameList.length;i++){ //All players are queued queue.enqueue(nameList[i]) } let eliminated = ‘’; //When there are more than one player in the queue while (queue.size()\u0026gt;1){ //Num players are queued to the end of the queue for(let i=0;i\u0026lt;num;i++){ queue.enqueue(queue.dequeue()) } eliminated = queue.dequeue() console.log(eliminated + ’eliminate’) } return queue.dequeue() } let nameList = [‘a’, ‘b’, ‘c’, ’d’]\n","date":1534950585,"description":"Javascript Data Structures","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"c04be070a00eaf44c153cf89d8ce705f","permalink":"https://siqi-liu.com/en/post/arrays-stacks-queues/","publishdate":"2018-08-22T15:09:45Z","relpermalink":"/en/post/arrays-stacks-queues/","section":"post","summary":"Javascript Data Structures","tags":["JavaScript"],"title":"Arrays, stacks, queues","tldr":null,"type":"post"},{"authors":null,"categories":["JavaScript"],"content":"I have previously used Object.defineProperty and closures to accomplish this task.\n{% post_link observer mode and vue monitoring object changes data response %}\nAfter learning about ES6 Proxy, I hope to use proxy to accomplish this\n{% post_link proxy Es6 proxy %}\nRequired:\nRespond to changes in underlying data Respond to changes in arrays If the object being modified is also an object, respond to changes in the new object Then let’s test the proxy function briefly.\nlet dog = { foods:[“food 1”,“food 2”,“food 3”], } let proxy = new Proxy(dog.foods,{ get(target,key){ return target[key] }, set(target,key,val){ console.log(“change”) target[key] = val; return true } })\nproxy.push(“food 3”) //change change\nI found that I could respond to changes in the array, but there was one more response. I guess it was caused by changes in both the length and content of the array.\nWith the previous foundation, it is easy to get this object that can respond to data changes\nfunction observe(value, cb) { //Declare the object to be returned let proxyObj = value; Object.keys(proxyObj).forEach((key) =\u0026gt; { //If the next level is a function or array, perform internal proxying if(typeof proxyObj[key]==“object”) proxyObj[key] = observe(proxyObj[key],cb) }) //Return the proxy object return proxyObj = defineReactive(proxyObj,cb) }\nfunction defineReactive (obj, cb) { //Control the callback function to be triggered only once let once = true; let proxy = new Proxy(obj,{ get(target,key){ return target[key] }, set(target,key,val){ if(typeof val==“object”){ //If the new value is also an object, return the proxy object val = observe(val,cb) } target[key] = val; if(once){ cb(); once = false } return true } }); return proxy } class Vue { constructor(options) { this._data = observe(options.data,options.render) } }\nLet’s test it.\nlet app = new Vue({ el: ‘#app’, data: { text: [1,2,3,4], text2:{ text3:1 } }, render(){ console.log(“render”); } }) //Array changes app._data.text.push(5)//“render” //Object replaced with new object app._data.text2 = { text4:[1] }//“render” //New object changes again app._data.text2.text4.push(2)//“render” console.log(app._data.text2.text4)//Proxy {0: 1, 1: 2, length: 2}\nThe basic requirements are met, but there are still two problems\napp._data.text returns a proxy object, which is not what I want The app._data.text operation will trigger the set. We need a convenient method to trigger the set directly by setting app.text ","date":1532707009,"description":"Javascript","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"3590c14e209d175f60938b17b93ddc29","permalink":"https://siqi-liu.com/en/post/proxy-implementation-of-data-response/","publishdate":"2018-07-27T15:56:49Z","relpermalink":"/en/post/proxy-implementation-of-data-response/","section":"post","summary":"Javascript","tags":["JavaScript","Vue"],"title":"Proxy implementation of data response","tldr":null,"type":"post"},{"authors":null,"categories":["frontend"],"content":"This is mainly a summary of my reading of “HTTP Illustrated”. I have omitted many parts that I think are not important or that I did not understand. I will make corrections and fill in the gaps in the future as my knowledge broadens.\nFirst is the HTTP protocol mind map Other What happens from inputting the URL to displaying the page This is a very common interview question that helps us review the content of HTTP. It can be easily answered based on the mind map above and other materials.\nFind domain name IP Occurs at the application layer of the TCP/IP protocol and is related to the DNS protocol.\nThe browser first searches its own DNS cache. It searches the system’s hosts file. It searches the router’s DNS cache. It searches the service provider’s DNS cache. If the IP address is not found, it sends a query to the domain name server until it finds the IP address. Initiating an HTTP Request An HTTP request message consists of a header and a body.\nThe message header includes:\nRequest line Request header General header Entity header Common request headers\nAccept: text/html,image/* #Types accepted by the browser Accept-Charset: ISO-8859-1 #Encoding types accepted by the browser Accept-Encoding: gzip, compress #Compression encoding types accepted by the browser Accept-Language: en-us, zh-cn #Languages and country codes accepted by the browser Host: www.lks.cn:80 #Host and port number of the browser request If-Modified-Since: Tue, 11 Jul 2000 18:23:51 GMT #Time the page has been cached Referer: http://www.lks.cn/index.html #The page the request originated from User-Agent: Mozilla/4.0 compatible; MSIE 5.5; Windows NT 5.0 #Browser-related information Cookie: #The browser temporarily stores information sent by the server Connection: close1.0/Keep-Alive1.1 #HTTP request version characteristics Date: Tue, 11 Jul 2000 18:23:51 GMT #Time to request the website Allow:GET #Request method: GET, POST is also common Keep-Alive:5 #Connection duration: 5 Connection:keep-alive #Whether to enable a persistent connection Cache-Control:max-age=300 #Maximum cache duration: 300 seconds\nThe following mainly talks about the cache rules\nHTTP Cache\nHTTP cache is divided into two categories: forced cache and comparative cache, depending on whether it needs to re-initiate a request to the server. If forced cache is effective, no further interaction with the server is required; comparative cache requires interaction with the server regardless of whether it is effective.\nBoth types of cache rules can exist at the same time. Forced cache takes precedence over comparative cache. When forced cache is executed, comparative cache rules are no longer executed.\n####### Force caching\nExpires/Cache-control indicates the expiration rule. When the client makes the first request and the server returns the data, the browser caches the data.\nThe Expire value indicates the expiration time of the cache. It is an HTTP 1.0 feature, so its role is basically ignored. The disadvantage of Expire is that the expiration time returned is the server time, while the comparison time is the client’s local time, which may cause errors.\nCache-Control is used to define cache instructions, which will override Expires, including public, private, no-cache, no-store, max-age, s-maxage, and must-revalidate.\nComparison Cache Comparison caches require comparison to determine whether the cache can be used.\nWhen the browser first requests data, the server returns the cache identifier along with the data to the client. On subsequent requests, the client sends the cache identifier to the server, which determines whether the cache is valid and returns a 304 error message, returning only the header portion.\nLast-Modified: The last modification time of the resource returned by the server in response to the request.\nIf-Modified-Since: When you request the server again, this field is used to inform the server of the last modification time of the resource returned by the server during the last request.\nUpon receiving a request and detecting the If-Modified-Since header, the server compares it with the last-modified time of the requested resource. If the resource’s last-modified time is greater than If-Modified-Since, indicating that the resource has been modified, the server responds with the entire resource content and a 200 status code. If the resource’s last-modified time is less than or equal to If-Modified-Since, indicating that the resource has not been modified, the server responds with an HTTP 304, instructing the browser to continue using the cached data.\nThe Last-Modified field only indicates the last modification time to the nearest second. If a resource is modified multiple times within a second, it may not accurately indicate the freshness of the file. If a resource is generated periodically, and the Last-Modified field changes even though the content remains unchanged, the file may not be cached. This may be because the server does not accurately obtain the resource …","date":1532388559,"description":"HTTP simple organization","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"651901c9ac7edd87a480203190ed0a41","permalink":"https://siqi-liu.com/en/post/http-protocol-summary/","publishdate":"2018-07-23T23:29:19Z","relpermalink":"/en/post/http-protocol-summary/","section":"post","summary":"HTTP simple organization","tags":["frontend","Web"],"title":"HTTP Protocol Summary","tldr":null,"type":"post"},{"authors":null,"categories":["JavaScript"],"content":"Before summarizing the es6 class, let’s review the ES5 inheritance method.\nPrototype Chain If we make the prototype of an instance equal to another instance, the prototype of this instance will contain a pointer to the other prototype. For example:\nfunction Animal(name=\u0026#34;动物\u0026#34;){ this.name = name; this.foods =[\u0026#34;食物1\u0026#34;,\u0026#34;食物2\u0026#34;] } Animal.prototype = { constructor:Animal, eat(){ console.log(this.foods) } } function Dog(name=\u0026#34;狗子\u0026#34;){ this.name = name; } Dog.prototype = new Animal() var dog1 = new Dog(\u0026#34;哈士奇\u0026#34;); console.log(dog1.name)// Husky Two types, Animal and Dog, are defined here. Dog inherits Animal by assigning its prototype to an instance of Animal. This way, the new prototype of Dog has not only the properties and methods of Dog, but also the properties and methods of Animal. Because the prototype is overwritten, the constructor of dog1 points to Animal.\nUse isPrototypeOf() or instanceof to determine the relationship between the instance and the prototype chain of the constructor\nconsole.log(Object.prototype.isPrototypeOf(dog1))//true console.log(Animal.prototype.isPrototypeOf(dog1))//true console.log(Dog.prototype.isPrototypeOf(dog1))//true As with the problem encountered in creating objects in the previous article, prototype properties containing reference types will be shared by all instances\nfunction Animal(name=\u0026#34;动物\u0026#34;){ this.name = name; } Animal.prototype = { constructor:Animal, foods:[\u0026#34;食物1\u0026#34;,\u0026#34;食物2\u0026#34;], eat(){ console.log(this.foods) } } function Dog(name=\u0026#34;狗子\u0026#34;){ this.name = name; } Dog.prototype = new Animal() var dog = new Dog(\u0026#34;哈士奇\u0026#34;); dog.foods.push(\u0026#34;哈士奇的狗粮\u0026#34;) var dog2 = new Dog(\u0026#34;柯基\u0026#34;); dog2.eat()Obviously, we don’t want Corgi to eat Husky’s dog food. The reason for the above problem is that the prototype of the dog dog1 instance is the same Animal instance, so it naturally shares attributes.\nThe second problem with the prototype chain is that there is no way to pass parameters to Animal when creating Dog.\nConstructor To solve the above problem, we can use the technique of borrowing constructors\nfunction Animal(name=\u0026#34;动物\u0026#34;){ this.name = name; this.foods = [\u0026#34;食物1\u0026#34;,\u0026#34;食物2\u0026#34;]; this.eat = function(){ console.log(this.foods) } } function SubType(){ Animal.call(this) }Just take a look at it. It is the same as the constructor method of creating an object in the previous article. It executes the constructor code. The disadvantage is similar. It does not solve the problem of function reuse. The most commonly used method is composition inheritance.\nCombination inheritance function Animal(name=\u0026#34;动物\u0026#34;){ this.name = name; this.foods = []; } Animal.prototype.eat = function(){ console.log(this.foods) } function Dog(name){ // Inherited properties Animal.call(this,name) this.color = \u0026#34;白色\u0026#34; } // Inheritance Methods Dog.prototype = new Animal() Dog.prototype.constructor = Dog; var dog1 = new Dog(\u0026#34;柯基\u0026#34;) dog1.foods.push(\u0026#34;柯基的狗粮\u0026#34;) dog1.eat()// [\u0026#34;Corgi\u0026#39;s Dog Food\u0026#34;] var dog2 = new Dog(\u0026#34;哈士奇\u0026#34;) dog2.foods.push(\u0026#34;哈士奇的狗粮\u0026#34;) dog2.eat()// [\u0026#34;Husky Dog Food\u0026#34;] console.log(dog2.eat ==dog2.eat)//true In fact, the combined inherited properties are created by creating the subclass’s this and then adding the parent class’s properties to this. The method is to get the parent class instance and its prototype after overriding the subclass’s prototype.\n","date":1531821454,"description":"ES5 knowledge review","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"bd7ef18d848fbb4c30b9331cd31627a0","permalink":"https://siqi-liu.com/en/post/es5-inheritance/","publishdate":"2018-07-17T09:57:34Z","relpermalink":"/en/post/es5-inheritance/","section":"post","summary":"ES5 knowledge review","tags":["JavaScript","OOP"],"title":"ES5-Inheritance","tldr":null,"type":"post"},{"authors":null,"categories":["JavaScript"],"content":"Before summarizing ES6 Class, I think it is necessary to sort out the knowledge of ES5 first. This will make it easier to compare and remember.\nFactory Pattern The factory pattern encapsulates the process of creating an object using a function. Example:\nvar Animal = function(name=\u0026#34;动物\u0026#34;){ var animal = new Object(); animal.name=name animal.eat = function(){ console.log(\u0026#34;吃食物\u0026#34;) } return animal } var dog = Animal(\u0026#34;狗\u0026#34;) Although it solves the problem of creating multiple similar objects, it does not know the type of the created object, and each time an object is created, a new function is generated and there is no reuse.\nConstructor Pattern What happens after the constructor is called is:\nCreate a new object Assign the constructor’s scope to the new object (this refers to the new object) Execute the code in the constructor (add properties to the object) Return the new object The code expression is:\nvar Animal = function(name=\u0026#34;动物\u0026#34;){ this.name = name; this.eat = function(){ console.log(\u0026#34;吃食物\u0026#34;) } } function newAnimal() { // The following is the code generated by calling the constructor var obj = {}; // Without this step, dog1 instanceof Animal will be false obj.__proto__ = Animal.prototype; Animal.call(obj) return obj } var dog1= newAnimal() console.log(dog1 instanceof Animal)As you can see, by pointing the instance’s __proto__ to the prototype object of Animal, Animal can be turned into the instance’s constructor, which will be discussed later.\nNow rewrite the previous example using the constructor pattern.\nvar Animal = function(name=\u0026#34;动物\u0026#34;){ this.name = name; this.eat = function(){ console.log(\u0026#34;吃食物\u0026#34;) } } var dog = new Animal(\u0026#34;狗\u0026#34;) console.log(dog.constructor==Animal)//true The constructor attribute of the instance object generated by the constructor points to its constructor.\nThe instanceof operator is used to detect the type of an object.\ndog instanceof Animal //true dog instanceof Object // true (all objects inherit from Object) Since Animal is a constructor, it can be run just like a normal function. Let’s see what happens if we execute it directly without using the new operator:\nvar Animal = function(name=\u0026#34;动物\u0026#34;){ this.name = name; this.food = \u0026#34;食物1\u0026#34; this.eat = function(){ console.log(\u0026#34;吃食物\u0026#34;) } } Animal() console.log(food)// \u0026#34;Food 1\u0026#34; eat()// Eat food You can see that the properties and methods of Animal are added to the global object because when calling a function in the global scope, this always points to Globol\nDon’t forget, usually, this inside a function called by a non-object always points to the global\nThe constructor is a slight improvement over the factory pattern, but it does not solve the problem of function reuse. Let’s verify it as follows:\nvar Animal = function(name=\u0026#34;动物\u0026#34;){ this.name = name; this.food = \u0026#34;食物1\u0026#34; this.eat = function(){ console.log(\u0026#34;吃食物\u0026#34;) } } var dog1= new Animal() var dog2 = new Animal() console.log(dog1.eat ==dog2.eat)//false Obviously, for an action like eating food, I don’t need two functions to handle it. So…\nfunction Animal(){ /.../ } function eat(){ console.log(\u0026#34;吃食物\u0026#34;) }However, such globally defined functions are easily tampered with, so it is not practical.\nPrototype Pattern\nEvery function we create has a prototype property that points to a prototype object. The prototype object contains properties and methods shared by instances created by the constructor.\nThis resolves all instances sharing the function.\nChange the above to prototype mode:\nExample 1:\nfunction Animal(){ } Animal.prototype = { name : \u0026#34;动物\u0026#34;, food : [\u0026#34;食物1\u0026#34;,\u0026#34;食物2\u0026#34;], eat(){ console.log(this.foods) } } var dog = new Animal(); var cat = new Animal() console.log(dog.eat == cat.eat)//true console.log(dog.name == cat.name)//true console.log(dog.food == cat.food)//true As you can see, dog and cat share the same eat function. So what exactly does the prototype object do?\nPrototype Object Whenever a new function is created, it will have a prototype property that points to the prototype object. By default, the prototype object will automatically have a constructor property (so that instances can also use this property), which points to the constructor function.\nThat is to say\nAnimal.prototype.constructor points to Animal itself\nIt also means that dog.__proto__.constructor==Animal points to the constructor Animal\nThen use the previous example 1 to experiment\nconsole.log(dog.__proto__.constructor==Animal)//false console.log(Animal.prototype.constructor===Animal)//false I found that this was not the case, because I had overwritten the prototype object. I will talk about this later.\nAfter creating a constructor, its prototype object will only obtain the constructor property by default, and other methods are inherited from Object.\nWhen the constructor is called to create an instance, the instance will contain a pointer to the prototype object of the constructor [[prototype]]. There is no standard way to access it in Js. In some browsers, __proto__ can be used to access it.\nThe relationship between the instance and the prototype object can …","date":1531500712,"description":"ES5 knowledge review","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"ed0f1edeb337553608a4a929ef8b2699","permalink":"https://siqi-liu.com/en/post/es5-creating-objects/","publishdate":"2018-07-13T16:51:52Z","relpermalink":"/en/post/es5-creating-objects/","section":"post","summary":"ES5 knowledge review","tags":["JavaScript","JavaScript","OOP"],"title":"ES5 - Creating Objects","tldr":null,"type":"post"},{"authors":null,"categories":["JavaScript"],"content":"Usage Example 1: Control the order in which asynchronous operations are completed\nvar fetchData = function(id){ return new Promise((reslove, reject)=\u0026gt;{ setTimeout(function(){ console.log(Task ${id} executed) reslove(Execution result ${id}) },Math.random()*1000) }) }\nasync function tasksList(){ // Tasks here execute concurrently let list = [1,2,3,4,5] let promises = list.map((task)=\u0026gt;{ return fetchData(task) }) let results = await Promise.all(promises); return results }\ntasksList().then((res)=\u0026gt;{ console.log(res) console.log(“All tasks completed”) }) Task 1 executes Task 2 executes Task 4 executes Task 5 executes Task 3 executes [“Execution result 1”, “Execution result 2”, “Execution result 3”, “Execution result 4”, “Execution result 5”] As you can see, it’s very similar to promises. The results are output in the order of the list. The difference is that promises execute the asynchronous request before the function executes.\nExample 2: Subsequent completion of asynchronous operations\n//fetchData is the same as in Example 1 async function tasksList(){ //Tasks here are executed sequentially let results = []; try{ for(let i=0;i\u0026lt;5;i++){ let result = await fetchData(i) results.push(result) } }catch(e){ console.log(e) } return results }\ntasksList().then((res)=\u0026gt;{ console.log(res) console.log(“All tasks completed”) }) Task 0 executed Task 1 executed Task 2 executed Task 3 executed [“Execution result 0”, “Execution result 1”, “Execution result 2”, “Execution result 3”, “Execution result 4”] All tasks completed\nConcept async is syntactic sugar for the Generator function.\nThe improvements async offers over Generator are:\nBuilt-in Executor asycn does not require an executor (co module) like Generator\nBetter semantics\nAsync means there is asynchronous operation in the function, await means that the expression following it needs to wait for the result.\nWider applicability\nAfter the await command of the async function, you can use Promise objects and primitive values. The primitive value will return an immediately resolved promise object.\nThe return value is a promise You can use the then method to specify the next step of the operation\n","date":1531320425,"description":"\"ES6 Standard Introduction\" Knowledge Points Summary","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"f98a47d8cbc612575d15c1f0b34b9e99","permalink":"https://siqi-liu.com/en/post/async/","publishdate":"2018-07-11T14:47:05Z","relpermalink":"/en/post/async/","section":"post","summary":"\"ES6 Standard Introduction\" Knowledge Points Summary","tags":["JavaScript","JavaScript"],"title":"async","tldr":null,"type":"post"},{"authors":null,"categories":["JavaScript"],"content":"\nBasic Concepts Formally, a Generator function is a regular function, but it has two characteristics. First, there’s an asterisk between the function keyword and the function name. Second, the function body uses yield expressions to define different internal states.\nfunction* helloWorldGenerator() { yield ‘hello’; yield ‘world’; return ’ending’; }\nvar hw = helloWorldGenerator();\nAfter calling the Generator function, the function is not executed, and what is returned is not the result of the function execution, but a traversal object pointing to the internal state.\nCalling the Generator function returns an iterator object, which represents the internal pointer to the Generator function. Each subsequent call to the iterator object’s next method returns an object with two properties: value and done. The value property represents the current internal state, which is the value of the expression following the yield expression. The done property is a Boolean value indicating whether the iteration has completed.\nyield expression The yield expression is a pause signal.\nWhen a yield expression is encountered, the execution of subsequent operations is suspended, and the value of the expression immediately following the yield is used as the value attribute value of the returned object.\nUntil the return statement is reached, the value of the expression after the return statement is used as the value property of the returned object. If the function does not have a return statement, the value property of the returned object is undefined.\nThe yield expression can only be used inside a Generator function.\nIn addition, if the yield expression is used in another expression, it must be placed in parentheses.\nThe yield expression can be used as a function parameter or placed on the right side of an assignment expression without parentheses.\nfunction* demo() { console.log(\u0026#39;Hello\u0026#39; + yield); // SyntaxError console.log(\u0026#39;Hello\u0026#39; + yield 123); // SyntaxError console.log(\u0026#39;Hello\u0026#39; + (yield)); // OK console.log(\u0026#39;Hello\u0026#39; + (yield 123)); // OK foo(yield \u0026#39;a\u0026#39;, yield \u0026#39;b\u0026#39;); // OK let input = yield; // OK } Traverser object You can assign a Generator to the Symbol.iterator property of an object, giving the object the Iterator interface.\nvar myIterable = {}; myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3; };\n[…myIterable] // [1, 2, 3]\nAfter executing the Generator function, it returns an iterator object. This object also has the Symbol.iterator property and returns itself after execution. (ES6 specifies that this iterator is an instance of the Generator function and inherits the methods of the Generator function’s prototype object).\nfunction* gen() { // some code }\nvar g = gen();\ngSymbol.iterator === g // true g instanceof gen === g // true\nThe for…of loop automatically iterates over the Iterator objects generated by the Generator function.\nfunction* foo() { yield 1; yield 2; yield 3; yield 4; yield 5; return 6; }\nfor (let v of foo()) { console.log(v); } // 1 2 3 4 5\nSimilarly, you can iterate over objects that use a Generator as an Iterator interface.\nfunction* objectEntries() { let propKeys = Object.keys(this); for (let propKey of propKeys) { yield [propKey, this[propKey]]; } } let jane = { first: \u0026#39;Jane\u0026#39;, last: \u0026#39;Doe\u0026#39; }; jane[Symbol.iterator] = objectEntries; for (let [key, value] of jane) { console.log(`${key}: ${value}`); } // first: Jane // last: Doe method What next(), throw(), and return() have in common\nTheir function is to resume the execution of the Generator function and replace the yield expression with a different statement.\n//next() replaces the yield expression with a value. const g = function* (x, y) { let result = yield x + y; return result; };\nconst gen = g(1, 2); gen.next(); // Object {value: 3, done: false}\ngen.next(1); // Object {value: 1, done: true} // Equivalent to replacing let result = yield x + y // with let result = 1; //throw() replaces the yield expression with a throw statement. gen.throw(new Error(’error’)); // Uncaught Error: error // Equivalent to replacing let result = yield x + y // with let result = throw(new Error(’error’)); return() replaces the yield expression with a return statement. gen.return(2); // Object {value: 2, done: true} // Equivalent to replacing let result = yield x + y // with let result = return 2;\nyield* Syntactically, if a yield expression is followed by an iterator object, an asterisk () must be added after the yield expression to indicate that it returns an iterator object. This is called a yield expression.\nAny data structure that has an Iterator interface can be traversed by yield*.\nlet read = function* () { yield ‘hello’; yield* ‘hello’; };\nfor(let i of read()){ console.log(i) } //hello h,e,l,l,o\nThe yield* statement traverses a complete binary tree.\n// Below is the binary tree constructor. // The three parameters are the left tree, the current node, and the right tree. function Tree(left, label, right) { this.left = left; this.label = label; this.right = right; …","date":1530902275,"description":"\"ES6 Standard Introduction\" Knowledge Points Summary","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"f3680e7820323e792bc448f93457f7fa","permalink":"https://siqi-liu.com/en/post/generator-functions-and-applications/","publishdate":"2018-07-06T18:37:55Z","relpermalink":"/en/post/generator-functions-and-applications/","section":"post","summary":"\"ES6 Standard Introduction\" Knowledge Points Summary","tags":["JavaScript","JavaScript"],"title":"Generator functions and applications","tldr":null,"type":"post"},{"authors":null,"categories":["Frontend"],"content":"\nXSS Concept Cross-site scripting (CSS) is an attack that executes invalid HTML tags or JavaScript in the browser of a registered user of a website with a security vulnerability. Dynamically created HTML may contain hidden security vulnerabilities. Attackers can create scripts that trap users in their browsers, leaving them vulnerable to passive attacks.\nPotential Impacts Using fake input forms to steal user personal information Using scripts to steal user cookies and send malicious requests Displaying forged articles or images Classification The first type is reflected XSS, also known as non-persistent XSS. Specifically, we pass our malicious script to the server via a URL, and the server simply “reflects” the script back to the visitor’s browser without processing, causing the visitor’s browser to execute the script.\nOne type is stored XSS, also known as persistent XSS. The biggest difference between it and reflected XSS is that the server will do some processing when it receives our malicious script.\nFor example, it can be stored in a database, and then when we visit the same page again, the malicious script can be retrieved from the database and returned to the browser for execution.\nFor example, when someone inserts a malicious script into a message, the server will display the previous message content to each visitor, so subsequent visitors will naturally receive the malicious script in the previous message and be unfortunately targeted.\nCase There are many examples of XSS online Example 1 Example 2\nXSS Defense Measures Principle: Never allow data to become executable code. Do not trust any user data. Strictly separate data and code.\nHttpOnly: The browser prohibits JavaScript in the page from accessing cookies with the HttpOnly attribute. Input Validation: The XSS Filter performs format checks on input content, similar to a “whitelist,” which can prevent attacks based on special characters. Implement the same input validation in both client-side JavaScript and server-side code (required on the server side). Output Validation: When outputting variables to HTML pages, use encoding or escaping to prevent XSS attacks. HtmlEncode: Converts characters to HTMLEntities, according to the ISO-8859-1 standard. JavaScriptEncode can be used in JavaScript. Special characters () must be escaped, and output variables must be enclosed in quotes. Output in HTML tag: $var Variables output within a tag, if not processed, can directly lead to XSS.\nor Defense method: Use HtmlEncode for variables. Output in Html attribute: ","date":1530785136,"description":"Web security related content","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"145cc3865034fe8e08bad605d8d55914","permalink":"https://siqi-liu.com/en/post/a-brief-introduction-to-web-security/","publishdate":"2018-07-05T10:05:36Z","relpermalink":"/en/post/a-brief-introduction-to-web-security/","section":"post","summary":"Web security related content","tags":["Frontend","JavaScript"],"title":"A Brief Introduction to Web Security","tldr":null,"type":"post"},{"authors":null,"categories":["JavaScript"],"content":"\nBasic usage const promise = new Promise(function(resolve, reject) { // … some code if (/* Asynchronous operation succeeded */) { resolve(value); } else { reject(error); } });\nPromise parameters The resolve function changes the state of the Promise object from “pending” to “resolved”. It is called when the asynchronous operation succeeds and passes the result of the asynchronous operation as a parameter.\nThe function of reject is to change the state of the Promise object from “pending” to “rejected”. It is called when the asynchronous operation fails and passes the error reported by the asynchronous operation as a parameter.\nIn addition to normal values, the parameters of the resolve function may also be another Promise instance\nconst p1 = new Promise(function (resolve, reject) { // … });\nconst p2 = new Promise(function (resolve, reject) { // … resolve(p1); }) Both p1 and p2 are instances of Promise, but p2’s resolve method takes p1 as a parameter. This means that the result of one asynchronous operation is another asynchronous operation. In this case, the state of p1 is passed to p2, meaning that the state of p1 determines the state of p2.\nIf the status of p1 is pending, then the callback function of p2 will wait for the status of p1 to change; if the status of p1 is already resolved or rejected, then the callback function of p2 will be executed immediately.\nsummary: \u0026#34;\u0026#34; Generally speaking, after calling resolve or reject, the Promise’s mission is completed. Subsequent operations should be placed in the then method, not directly after resolve or reject. Therefore, it is best to add a return statement before them to avoid unexpected situations.\nChaining Calls The then method returns a new Promise instance. Therefore, you can use chaining, calling another then method after the then method.\ngetJSON(\u0026#34;/post/1.json\u0026#34;).then( post =\u0026gt; getJSON(post.commentURL) ).then( comments =\u0026gt; console.log(\u0026#34;resolved: \u0026#34;, comments), err =\u0026gt; console.log(\u0026#34;rejected: \u0026#34;, err) ); Error catching If promise throws an error, it will be caught by the callback function specified by the catch method.\nIf a promise throws an error after the resolve statement, it will not be caught. This is because once the state of a promise changes, it remains in that state forever.\nThe error of the Promise object has the “bubbling” nature and will be passed back until it is caught. In other words, the error will always be caught by the next catch statement.\nGenerally speaking, do not define the Reject callback function (the second parameter of then) in the then method, always use the catch method.\n","date":1530105527,"description":"\"ES6 Standard Introduction\" Knowledge Points Summary","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"fe2d1aa10e609415c621fb0143084b7c","permalink":"https://siqi-liu.com/en/post/promise/","publishdate":"2018-06-27T13:18:47Z","relpermalink":"/en/post/promise/","section":"post","summary":"\"ES6 Standard Introduction\" Knowledge Points Summary","tags":["JavaScript","JavaScript"],"title":"promise","tldr":null,"type":"post"},{"authors":null,"categories":["JavaScript"],"content":"How to implement deep copy Object.assign() Can only handle one layer of deep copy\nvar oldObj = {a:1}; var newObj = Object.assign({},oldObj) console.log(oldObj===newObj)//false Sequence/Desequence It is not possible to clone special objects such as functions and RegExp.\nThe object’s constructor will be discarded, and all constructors will point to Object\nIf the object has a circular reference, an error will be reported.\nThis method can only correctly handle objects such as Number, String, Boolean, Array, and flat objects, that is, data structures that can be directly represented by json.\n// Constructor function person(pname) { this.name = pname; }\nconst Messi = new person(‘Messi’);\n// Function function say() { console.log(‘hi’); };\nconst oldObj = { a: say, b: new Array(1), c: new RegExp(‘ab+c’, ‘i’), d: Messi };\nconst newObj = JSON.parse(JSON.stringify(oldObj));\n// Unable to copy function console.log(newObj.a, oldObj.a); // undefined [Function: say] // Sparse array copy error console.log(newObj.b[0], oldObj.b[0]); // null undefined // Unable to copy regular object console.log(newObj.c, oldObj.c); // {} /ab+c/i // Constructor pointing to error console.log(newObj.d.constructor, oldObj.d.constructor); // [Function: Object] [Function: person]\nRecursive copy function isArray(arr) { return Object.prototype.toString.call(arr) == \u0026#39;[object Array]\u0026#39; }; function _clone(oldObj) { console.log(typeof oldObj) if (typeof oldObj !== \u0026#34;object\u0026#34;) { return oldObj; } var obj = isArray(oldObj) ? [] : {} for (var i in oldObj) { if (oldObj.hasOwnProperty(i)) { if (oldObj[i] == oldObj) { continue } obj[i] = _clone(oldObj[i]) } } return obj } //Still use the above test function Person(pname) { this.name = name; } const Messi = new Person(\u0026#39;Messi\u0026#39;); function say() { console.log(\u0026#39;hi\u0026#39;); }; const oldObj = { a: say, b: new Array(1), c: new RegExp(‘ab+c’, ‘i’), d: Messi }; const newObj = _clone(oldObj) // Functions can be copied console.log(newObj.a, oldObj.a); // [Function: say] [Function: say] // Sparse arrays can be copied console.log(newObj.b[0], oldObj.b[0]); // undefined undefined // Regular objects cannot be copied console.log(newObj.c, oldObj.c); // {} /ab+c/i // Constructor pointing to error console.log(newObj.d.constructor, oldObj.d.constructor); // [Function: Object] [Function: person] // Circular references are not allowed either. Removing the continue above will cause memory overflow.\nThere are also issues like being unable to copy regular objects and constructor pointer errors.\nImproved Recursive Copy const isType = function (obj, type) { //Function used to distinguish attribute types if (typeof obj !== \u0026#39;object\u0026#39;) return false const typeString = Object.prototype.toString.call(obj); let flag; //flag is used as the identifier for attribute judgment switch (type) { case \u0026#39;Array\u0026#39;: flag = typeString === \u0026#39;[object Array]\u0026#39;; break; case \u0026#39;Date\u0026#39;: flag = typeString === \u0026#39;[object Date]\u0026#39;; break; case \u0026#39;RegExp\u0026#39;: flag = typeString === \u0026#39;[object RegExp]\u0026#39;; break; case \u0026#39;Set\u0026#39;: flag = typeString === \u0026#39;[object Set]\u0026#39;; break; case \u0026#39;Map\u0026#39;: flag = typeString === \u0026#39;[object Map]\u0026#39;; break; case \u0026#39;Symbol\u0026#39;: flag = typeString === \u0026#39;[object Symbol]\u0026#39;; break; default: flag = false; } return flag; }\nfunction _clone(oldObj) { let obj; if (isType(oldObj) == false) { //Basic data types, functions, and types I haven’t considered are all directly referenced (lazy) obj = oldObj } else if (isType(oldObj, ‘Array’)) { //Process arrays obj = []; } else if (isType(oldObj, ‘RegExp’)) { //Process regular expressions let flags = ‘’; if (oldObj.global) flags += “g”;//Global if (oldObj.ignoreCase) flags += “i”;//Case if (oldObj.multiline) flags += “m”;// return obj = new RegExp(oldObj.source, getRegExp(oldObj)); } else if (isType(oldObj, ‘Date’)) { //Processing time object return obj = new Date(oldObj.getTime()); } else if (isType(oldObj, ‘Set’)) { //Processing Set return obj = new Set([…obj]); } else if (isType(oldObj, ‘Map’)) { //Processing Map return obj = new Map([…obj]); } else if (isType(oldObj, ‘Symbol’)) { //Processing Symbol return obj = Symbol(String(oldObj).slice(7, -1)) } else { // Processing object prototype let proto = Object.getPrototypeOf(oldObj); obj = Object.create(proto); } for (var i in oldObj) { if (oldObj.hasOwnProperty(i)) { if (oldObj[i] == oldObj) { obj[i] = oldObj } else { obj[i] = _clone(oldObj[i]) } } } return obj }\nfunction Person(pname) { this.name = pname; } const Messi = new Person(\u0026#39;Messi\u0026#39;); function say() { console.log(\u0026#39;hi\u0026#39;); }; const oldObj = { a: say, b: new Array(1), c: new RegExp(\u0026#39;ab+c\u0026#39;, \u0026#39;i\u0026#39;), d: Messi, e:new Set([1,2,3]), f:new Map([[\u0026#39;foo\u0026#39;, 1],[\u0026#39;bar\u0026#39;, 2]]), g:Symbol(\u0026#34;yosgi\u0026#34;) }; const newObj = _.clone(oldObj) // function console.log(newObj.a, oldObj.a); // [Function: say] [Function: say] // Sparse array console.log(newObj.b[0], oldObj.b[0]); // undefined undefined // Regular object console.log(newObj.c, oldObj.c); // /ab+c/i /ab+c/i // Constructor console.log(newObj.d.constructor, oldObj.d.constructor); // [Function: person] [Function: person] //Set …","date":1530032510,"description":"Javascript Basics","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"547cf43c4a97de8ba92fba33eb870323","permalink":"https://siqi-liu.com/en/post/javascript-deep-copy-implementation/","publishdate":"2018-06-26T17:01:50Z","relpermalink":"/en/post/javascript-deep-copy-implementation/","section":"post","summary":"Javascript Basics","tags":["JavaScript"],"title":"Javascript deep copy implementation","tldr":null,"type":"post"},{"authors":null,"categories":["JavaScript"],"content":"Bubble Sort Bubble sort compares any two adjacent items and swaps them if the first is greater than the second. The items move upward into the correct order, much like bubbles rising to the surface, hence the name.\nvar bubbleSort = function (array) { function swap(index1,index2){ var aux = array[index1]; array[index1] = array[index2]; array[index2] = aux; } for (let i = 0; i \u0026lt; array.length; i++) { // The outer loop will iterate from the first to the last position in the array, controlling how many rounds the array goes through. for (let j = 0; j \u0026lt; array.length-1; j++) { // The inner loop iterates from the first to the second to last item, comparing and exchanging the current item and the next item. if(array[j] \u0026gt; array[j+1]){ swap(j,j+1); } } } return array }It is the simplest of all sorting algorithms. However, from a running time perspective, bubble sort is the worst.\nvar bubbleSort = function (array) { function swap(index1,index2){ var aux = array[index1]; array[index1] = array[index2]; array[index2] = aux; } var cost = 0; for (let i = 0; i \u0026lt; array.length; i++) { cost ++; for (let j = 0; j \u0026lt; array.length-1; j++) { cost++; if(array[j] \u0026gt; array[j+1]){ swap(j,j+1); } } } console.log(\u0026#39;cost for bubbleSort with input size \u0026#39; + array.length + \u0026#39; is\u0026#39; + cost); } bubbleSort([5, 4, 3, 2, 1])//cost for bubbleSort with input size 5 is 25 If we perform bubbleSort on an array of size 10, the cost is 100, so the complexity is O(n^2)\nNotice that by the time the algorithm executes the second pass through the outer loop, the numbers 4 and 5 are already correctly sorted. They are still being compared in subsequent comparisons, even though this is unnecessary. Therefore, we can improve the bubble sort algorithm slightly.\nvar bubbleSort = function (array) { function swap(index1,index2){ var aux = array[index1]; array[index1] = array[index2]; array[index2] = aux; } var cost = 0; for (let i = 0; i \u0026lt; array.length; i++) { cost ++; for (let j = 0; j \u0026lt; array.length-1-i; j++) { cost++; if(array[j] \u0026gt; array[j+1]){ swap(j,j+1); } } } console.log(\u0026#39;cost for bubbleSort with input size \u0026#39; + array.length + \u0026#39; is\u0026#39; + cost); } bubbleSort([5, 4, 3, 2, 1])////cost for bubbleSort with input size 5 is 15 The cost of an array of size 5 is 15 and the complexity is O(n^2-(1+2+..+n))\nSelection Sort The selection sort algorithm is an in-place comparison sort algorithm. The general idea behind selection sort is to find the smallest value in the data structure and place it first, then find the second smallest value and place it second, and so on.\nvar swap = function(array,index1,index2) { [array[index1],array[index2]] = [array[index2],array[index1]] } var selectionSort = function(array) { for(let i=0;i\u0026lt;array.length; i++ ) { // After each outer loop, the i-th item is the smallest item in the array after i. let minIndex = i for(let j=i ; j\u0026lt;array.length ; j++) { if(array[minIndex] \u0026gt; array[j] ) { swap(array,minIndex,j) } } } return array }Selection sort is also an O(n²) algorithm. Like bubble sort, it involves two nested loops, resulting in a quadratic complexity.\nInsertion Sort\nInsertion sort constructs the final sorted array by sorting one item at a time. Assuming the first item is already sorted, and then it is compared to the second item, should the second item remain in its original position or be inserted before the first?\nIf it is before, then the first item should be moved back one position, if it is after, then it should not be moved.\nThis way, the first two items are correctly sorted and can be compared with the third item, and so on.\nvar insertionSort = function(array){ var length = array.length; for(let i=1;i\u0026lt;length;i++){ // The first item of the array is sorted by default var temp = array[i]; // Insert a new item and sort var j = i; // If the previous item is larger than the newly inserted one, move it back one position. while(j\u0026gt;0\u0026amp;\u0026amp;array[j-1]\u0026gt;temp){ array[j] = array[j - 1]; j-- } array[j] = temp; } return array }The complexity is O(n-1+(2+3+..n-1)). This algorithm performs better than selection sort and bubble sort when sorting small arrays.\nMerge Sort Merge sort is a divide-and-conquer algorithm. The idea is to split the original array into smaller arrays until each small array has only one position, and then merge the small arrays into larger arrays until there is only one sorted large array.\nBecause the leftmost of the two sorted arrays is always the smallest, and the array with only one item left is already sorted, the final large array is also sorted.\nvar merge = function(left,right){ // It is responsible for merging and sorting small arrays to generate large arrays until the original array is returned and sorted. var result = [],il=0,ir=0; // Compares whether the items from the left array are smaller than the items from the right array while(il\u0026lt;left.length\u0026amp;\u0026amp;ir\u0026lt;right.length){ // If so, add the item from the left array to the merged result array if(left[il]\u0026lt;right[ir]){ // and increment the control variable of the iteration array ( …","date":1529952522,"description":"Javascript algorithm sorting algorithm","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"fdba981535d73d26e6db4d94a328b7f3","permalink":"https://siqi-liu.com/en/post/javascript-sorting-algorithm/","publishdate":"2018-06-25T18:48:42Z","relpermalink":"/en/post/javascript-sorting-algorithm/","section":"post","summary":"Javascript algorithm sorting algorithm","tags":["JavaScript","JavaScript"],"title":"Javascript sorting algorithm","tldr":null,"type":"post"},{"authors":null,"categories":["JavaScript"],"content":"Overview Proxy is used to modify the default behavior of certain operations, which is equivalent to making changes at the language level, so it is a kind of “meta programming”, that is, programming the programming language.\nProxy can be understood as a layer of “interception” set up before the target object. All external access to the object must first pass through this interception layer, thus providing a mechanism to filter and rewrite external access. The word “proxy” originally means “agent”, and here it is used to indicate that it “proxy” certain operations, which can be translated as “proxy”.\nvar proxy = new Proxy(target, handler);\nAll usages of the Proxy object follow the same format as above, differing only in the way the handler parameter is written. Here, new Proxy() creates a Proxy instance, the target parameter represents the target object to be intercepted, and the handler parameter is also an object used to customize the interception behavior.\nFor example\nvar proxy = new Proxy({}, { get: function(target, property) { return 35; } });\nproxy.time // 35 proxy.name // 35 proxy.title // 35\nProxy instances can also serve as prototypes for other objects.\nvar proxy = new Proxy({}, { get: function(target, property) { return 35; } });\nlet obj = Object.create(proxy); obj.time // 35\nIn the above code, the proxy object is the prototype of the obj object. The obj object itself does not have a time property. Therefore, according to the prototype chain, the property is read from the proxy object, resulting in interception.\nThe same interceptor function can be set to intercept multiple operations.\nvar handler = { get: function(target, name) { if (name === \u0026#39;prototype\u0026#39;) { return Object.prototype; } return \u0026#39;Hello, \u0026#39; + name; }, apply: function(target, thisBinding, args) { return args[0]; }, construct: function(target, args) { return {value: args[1]}; } }; var fproxy = new Proxy(function(x, y) { return x + y; }, handler); fproxy(1, 2) // 1 new fproxy(1, 2) // {value: 2} fproxy.prototype === Object.prototype // true fproxy.foo === \u0026#34;Hello, foo\u0026#34; // true Below is a list of interception operations supported by Proxy There are 13 types in total.\nget(target, propKey, receiver) : intercepts the reading of object properties, such as proxy.foo and proxy[‘foo’].\nset(target, propKey, value, receiver) : intercepts the setting of object properties, such as proxy.foo = v or proxy[‘foo’] = v, and returns a Boolean value.\nhas(target, propKey): Intercepts the propKey in proxy operation and returns a Boolean value. deleteProperty(target, propKey): Intercepts the delete proxy[propKey] operation and returns a Boolean value.\nownKeys(target): Intercepts Object.getOwnPropertyNames(proxy), Object.getOwnPropertySymbols(proxy), Object.keys(proxy), and the for…in loop, returning an array. This method returns the property names of all the target object’s own properties, while Object.keys() only returns the target object’s own traversable properties.\ngetOwnPropertyDescriptor(target, propKey): intercepts Object.getOwnPropertyDescriptor(proxy, propKey) and returns the property description object.\ndefineProperty(target, propKey, propDesc): Intercepts Object.defineProperty(proxy, propKey, propDesc) and Object.defineProperties(proxy, propDescs), returning a Boolean value. preventExtensions(target): Intercepts Object.preventExtensions(proxy), returning a Boolean value.\ngetPrototypeOf(target): intercepts Object.getPrototypeOf(proxy) and returns an object.\nisExtensible(target): intercepts Object.isExtensible(proxy) and returns a Boolean value.\nsetPrototypeOf(target, proto): Intercepts Object.setPrototypeOf(proxy, proto) and returns a Boolean value. If the target object is a function, two additional operations can be intercepted.\napply(target, object, args): intercepts the Proxy instance as a function call operation, such as proxy(…args), proxy.call(object, …args), proxy.apply(…).\nconstruct(target, args): intercepts operations where the Proxy instance is called as a constructor, such as new proxy(…args).\nSee Ruan Yifeng’s ES6 Primer\nUsage Scenarios Proxy is similar to the proxy pattern in design patterns and is commonly used in the following areas:\nIntercept and monitor external access to objects Reduce the complexity of functions or classes Verify complex operations or manage required resources before performing them Extract the verification module Assume that the Person object has an age attribute, which should be an integer not greater than 200. Then you can use Proxy to ensure that the age attribute value meets the requirements.\nlet validator = { set: function(obj, prop, value) { if (prop === ‘age’) { if (!Number.isInteger(value)) { throw new TypeError(‘The age is not an integer’); } if (value \u0026gt; 200) { throw new RangeError(‘The age seems invalid’); } }\n// For the age property and other properties that meet the conditions, save them directly obj[prop] = value; } };\nlet person = new Proxy({}, validator);\nperson.age = 100;\nperson.age // 100 person.age = …","date":1526637566,"description":"\"ES6 Standard Introduction\" Knowledge Points Summary","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"1043cd4c870a9b754264567ad9387cf1","permalink":"https://siqi-liu.com/en/post/proxy/","publishdate":"2018-05-18T09:59:26Z","relpermalink":"/en/post/proxy/","section":"post","summary":"\"ES6 Standard Introduction\" Knowledge Points Summary","tags":["JavaScript","JavaScript"],"title":"proxy","tldr":null,"type":"post"},{"authors":null,"categories":["JavaScript"],"content":"Publish-Subscribe Mode The publish-subscribe pattern is also called the observer pattern. It defines a one-to-many dependency relationship between objects. When the state of an object changes, all objects that depend on it will be notified. In Js development, we generally use the event pattern instead of the traditional publish-subscribe pattern.\nSteps to implement the publish-subscribe model\nSpecify the publisher\nAdd a cache list to the publisher to store callback functions for notifying subscribers\nWhen publishing a message, the publisher will traverse the entire cache list and trigger the callback functions in it in turn\nFor example, a sales office can add information such as the unit price and area of the house to the information sent to the subscribers, and the subscribers receive the information and process it accordingly.\nSales Office Example var salesOffices = {}; salesOffices.clientList = []; salesOffices.listen = function(fn){ // Publisher cache callback function salesOffices.clientList.push(fn); } salesOffices.trigger = function(){ for(var i=0,fn;fn = this.clientList[i];i++){ fn.apply(this,arguments) } } salesOffices.listen(function(price,squareMeter){ console.log(\u0026#34;价格=\u0026#34;+price); console.log(\u0026#39;squareMeter=\u0026#39;+squareMeter); }) salesOffices.trigger(2000000,88) // Price = 2000000 // squareMeter=88 Next, create a key for the subscribed message, add the subscriber ID, and extract the publish-subscribe functionality as an object.\nvar event = { clientList:{}, listen:function(key,id,fn){ // key subscription representation id subscriber id fnu callback function // There may be many subscribers and callback functions under a subscription identifier. // So the subscriber is treated as an array, and the callback function is stored in the array if(!this.clientList[key]){ this.clientList[key]=[]; } this.clientList[key].push({ id,fn }) }, trigger:function(...args){// rest parameters var key = args.shift(); fns = this.clientList[key]; // The callback function array is empty and does nothing. if(!fns||fns.length==0) return; // Trigger the functions in the callback function array in sequence for(var i=0,fn;fn = fns[i];i++){ // fn is an object with a property id fn fn.fn.apply(this,args) } }, remove:function(key,fn){ var fns = this.clientList[key]; if(!fns) return false// No one has subscribed to the corresponding key if(!fn){// If no specific callback is passed in, all subscriptions for the key will be canceled. fns \u0026amp;\u0026amp; (fns.length=0) }else{ for(var i = fns.length-1;i\u0026gt;=0;i--){ var _fn = fns[i].fn; if(_fn===fn){ fns.splice(i,1)// Delete callback } } } } }; // Define an installEvent function to install publish and subscribe functions for the object var installEvent = function(obj){ for(var i in event){ // Traverse the key of the event object and add attributes and attribute methods to obj obj[i] = event[i] } } var salesOffices = {}; installEvent(salesOffices) salesOffices.listen(\u0026#34;square88\u0026#34;,1,f1 = function(price){ console.log(`square88的消息，价格是${price}`) }) salesOffices.listen(\u0026#34;square88\u0026#34;,2,f3 = function(price){ console.log(`square88的消息，价格是${price}`) }) salesOffices.listen(\u0026#34;square100\u0026#34;,2,f2 = function(price){ console.log(`square100的消息，价格是${price}`) }) salesOffices.trigger(\u0026#39;square88\u0026#39;,1000000)// According to square88, the price is 1000000 * 2 salesOffices.trigger(\u0026#39;square100\u0026#39;,1500000)// Square100\u0026#39;s news, the price is 1500000 salesOffices.remove(\u0026#34;square88\u0026#34;,f1); salesOffices.trigger(\u0026#39;square88\u0026#39;,1000000)// Square88\u0026#39;s news, the price is 1000000 ```##### How does Vue monitor object changes? `Object.defineProperty` You can customize get and set functions, and trigger corresponding callback functions when getting and setting object properties. Using this method, you can install publish and subscribe functions for each property in the object. ```javascript // Create a Vue constructor using prototype inheritance function Vue(data){ // The new object will have data attributes this.data = data; // watchList is equivalent to the clientList above this.watchList = []; // Add the function of publishing messages to the data object this.$bindObserver(data)； } var $watch = function (key,fn){ // It doesn\u0026#39;t matter who the monitored ID is, so it is removed. if(!this.clientList[key]){ this.clientList[key]=[]; } this.clientList[key].push(fn) } var $emit = function(...args){ var key = args.shift(); fns = this.watchList[key]; if(!fns||fns.length==0) return; for(var i=0,fn;fn = fns[i];i++){ fn.apply(this,args) } } var $remove = function(key,fn){ var fns = this.clientList[key]; if(!fns) return false if(!fn){ fns \u0026amp;\u0026amp; (fns.length=0) }else{ for(var i = fns.length-1;i\u0026gt;=0;i--){ var _fn = fns[i].fn; if(_fn===fn){ fns.splice(i,1) } } } } var $bindObserver = function(data){ var self = this; var keys = Object.keys(data) keys.forEach(key =\u0026gt; { var result = data[key]; // Here, the closure result is used as a variable referenced by the inner function within the function, and it always exists in the cache. Object.defineProperty(data,key, { enumerable:true, …","date":1525559934,"description":"Javascript Design Patterns Knowledge Points","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"ecaed220831c55620f7ec42576d5836a","permalink":"https://siqi-liu.com/en/post/observer-mode-and-vue-monitoring-object-changes/","publishdate":"2018-05-05T22:38:54Z","relpermalink":"/en/post/observer-mode-and-vue-monitoring-object-changes/","section":"post","summary":"Javascript Design Patterns Knowledge Points","tags":["JavaScript","Design Patterns"],"title":"Observer mode and Vue monitoring object changes","tldr":null,"type":"post"},{"authors":null,"categories":["JavaScript"],"content":"Set and Map Data Structures\nset Basic usage\nES6 provides a new data structure called Set. It is similar to an array, but the values of its members are unique and there are no duplicate values.\nSet itself is a constructor used to generate a Set data structure.\nconst s = new Set();\n[2, 3, 5, 4, 5, 2, 2].forEach(x =\u0026gt; s.add(x));\nfor (let i of s) { console.log(i); } // 2 3 5 4\nThe Set function can accept an array (or other data structure with an iterable interface) as a parameter for initialization.\n// Array deduplication […new Set(array)]\nWhen adding values to a Set, no type conversion occurs, so 5 and “5” are two different values. Set internally determines whether two values are different using an algorithm called “same-value-zero equality,” which is similar to the exact equality operator (===). The main difference is that NaN is equal to itself, while the exact equality operator considers NaN not equal to itself.\nAdditionally, two objects are never equal.\nProperties and methods of Set instances Set.prototype.constructor: Constructor, the default is the Set function.\nSet.prototype.size: Returns the total number of members in the Set instance.\nThe methods of Set instances are divided into two categories: operation methods (used to operate on data) and traversal methods (used to traverse members).\nadd(value): Add a value and return the Set structure itself.\ndelete(value): Deletes a value and returns a Boolean value indicating whether the deletion is successful.\nhas(value): Returns a Boolean value indicating whether the value is a member of the Set.\nclear(): Clears all members and has no return value.\nTraversal Operation Instances of the Set structure have four traversal methods that can be used to traverse members.\nkeys(): Returns a traversal of key names\nvalues(): Returns a traversal of key values\nentries(): Returns a traversal of key-value pairs\nforEach(): Iterate over each member using a callback function\nkeys(), values(), entries() Since the Set structure has no keys, only values (or the key and value are the same), the keys method and the values method behave identically.\nlet set = new Set([\u0026#39;red\u0026#39;, \u0026#39;green\u0026#39;, \u0026#39;blue\u0026#39;]); for (let item of set.keys()) { console.log(item); } // red // green // blue for (let item of set.values()) { console.log(item); } // red // green // blue for (let item of set.entries()) { console.log(item); } // [\u0026#34;red\u0026#34;, \u0026#34;red\u0026#34;] // [\u0026#34;green\u0026#34;, \u0026#34;green\u0026#34;] // [\u0026#34;blue\u0026#34;, \u0026#34;blue\u0026#34;] forEach() set = new Set([1, 4, 9]); set.forEach((value, key) =\u0026gt; console.log(key + \u0026#39; : \u0026#39; + value)) // 1 : 1 // 4 : 4 // 9 : 9 Application of traversal The spread operator (…) uses a for…of loop internally, so it can also be used with the Set structure.\nlet set = new Set([‘red’, ‘green’, ‘blue’]); let arr = […set]; // [‘red’, ‘green’, ‘blue’]\nThe spread operator combined with the Set construct can remove duplicate elements from an array.\nFurthermore, the map and filter methods of arrays can also be used indirectly on Sets.\nlet set = new Set([1, 2, 3]); set = new Set([…set].map(x =\u0026gt; x * 2)); // Returns a Set structure: {2, 4, 6}\nlet set = new Set([1, 2, 3, 4, 5]); set = new Set([…set].filter(x =\u0026gt; (x % 2) == 0)); // Returns a Set structure: {2, 4}\nThus, using Sets, it’s easy to implement unions, intersections, and differences.\nlet a = new Set([1, 2, 3]); let b = new Set([4, 3, 2]);\n// Union let union = new Set([…a, …b]); // Set {1, 2, 3, 4}\n// Intersection let intersect = new Set([…a].filter(x =\u0026gt; b.has(x))); // Set {2, 3}\n// Difference let difference = new Set([…a].filter(x =\u0026gt; !b.has(x)));\nIf you want to synchronously modify the original Set structure during a traversal operation, there is currently no direct method, but there are two workarounds. One is to map a new structure from the original Set structure and then assign it to the original Set structure; the other is to use the Array.from method.\nlet set = new Set([1, 2, 3]); set = new Set([…set].map(val =\u0026gt; val * 2)); // The values of set are 2, 4, and 6\n// Method 2 let set = new Set([1, 2, 3]); set = new Set(Array.from(set, val =\u0026gt; val * 2)); // The values of set are 2, 4, and 6\nWeakSet The WeakSet structure is similar to a Set in that it is also a collection of unique values. However, it differs from a Set in two ways.\nFirst, the members of WeakSet can only be objects, not other types of values.\nSecondly, the objects in WeakSet are all weak references, that is, the garbage collection mechanism does not consider the reference of WeakSet to the object. In other words, if other objects no longer reference the object, the garbage collection mechanism will automatically reclaim the memory occupied by the object, regardless of whether the object still exists in the WeakSet.\nBecause of this characteristic, WeakSet members are not suitable for reference, as they can disappear at any time. Furthermore, the number of members in a WeakSet depends on whether garbage collection has run. The number of members before and after garbage collection is likely to be different, and the timing of …","date":1525219200,"description":"\"ES6 Standard Introduction\" Knowledge Points Summary","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"4be2f65c412b4d52081271893fe117f9","permalink":"https://siqi-liu.com/en/post/set-and-map-data-structures/","publishdate":"2018-05-02T00:00:00Z","relpermalink":"/en/post/set-and-map-data-structures/","section":"post","summary":"\"ES6 Standard Introduction\" Knowledge Points Summary","tags":["JavaScript","JavaScript"],"title":"Set and Map Data Structures","tldr":null,"type":"post"},{"authors":null,"categories":["JavaScript"],"content":"Object Extensions Concise Notation for Object Properties //A few examples function f(x,y){ return {x,y} } f(1,2)//Objext {x:1,y:2} module.exports = { getItem, setItem, clear }; const cart = { _wheels: 4, get wheels() { return this._wheels; }, set wheels(value) { if (value \u0026lt; this._wheels) { throw new Error(‘The value is too small!’); } this._wheels = value; } } cart.wheels //4 set.wheels = 1 //Error ‘The value is too small!’\nAttribute Name Expression // Method 1 obj.foo = true;\n// Method 2 obj[‘a’ + ‘bc’] = 123;\nNote that if the property name expression is an object, the object will be automatically converted to the string [object Object] by default. Be especially careful with this. const keyA = {a: 1}; const keyB = {b: 2};\nconst myObject = { [keyA]: ‘valueA’, [keyB]: ‘valueB’ };\nmyObject // Object { [object Object]: “valueB”}\nIn the above code, both [keyA] and [keyB] return [object Object], so [keyB] will overwrite [keyA], and myObject will end up with only one [object Object] property.\nMethod name property The function’s name property returns the function’s name. Object methods are also functions and therefore have a name property.\nconst person = { sayName() { console.log(‘hello!’); } }; person.sayName.name // “sayName”\nIf an object method uses accessors and setters, the name property is not located on the method itself, but on the get/set property of the object that describes the method.\nconst obj = { get foo(){}, set foo(x){} } const descriptor = Object.getOwnPropertyDescriptor(obj,“foo”); //The getOwnPropertyDescriptor method returns the property descriptor for an owned property on the specified object. descriptor.get.name // “get foo” descriptor.set.name // “set foo”\nIf the object’s method is a Symbol value, the name property returns the description of the Symbol value.\nconst key1 = Symbol(‘description’); const key2 = Symbol(); let obj = { key1 {}, key2 {}, }; obj[key1].name // “[description]” obj[key2].name // \u0026#34;\u0026#34;\nObject.is() ES5 uses the == and === operators to compare two values for equality, but both have drawbacks.\nThe first one automatically converts the data type, and the following NaN is not equal to itself. And +0 is equal to -0. JavaScript lacks an operation that should ensure that two values are equal in all environments as long as they are the same.\nES6 introduces the “same-value equality” algorithm to address this issue. Object.is is a new method that implements this algorithm. It compares two values for strict equality, differing from === only in that +0 is not equal to -0 and NaN is equal to itself.\nES5 can deploy Object.is through the following code.\nObject.defineProperty(Object, ‘is’, { value: function(x, y) { if (x === y) { // For the case where +0 is not equal to -0 return x !== 0 || 1 / x === 1 / y; } // For the case where NaN is present return x !== x \u0026amp;\u0026amp; y !== y; }, configurable: true, enumerable: false, writable: true });\nObject.assign() The Object.assign() method is used to merge objects and copy all enumerable properties of the source object to the target object.\nThe first parameter is the target object, and the following parameters are source objects.\nconst target = {a:1}; const source1 = {b:2}; const source2 = {c:3}; Object.assign(target,source1,source2)\nObject.assign copies properties with restrictions. It only copies the source object’s own properties, not inherited properties or non-enumerable properties.\nObject.assign({b: \u0026#39;c\u0026#39;}, Object.defineProperty({}, \u0026#39;invisible\u0026#39;, { enumerable: false, value: \u0026#39;hello\u0026#39; }) ) // { b: \u0026#39;c\u0026#39; } Properties whose property names are Symbol values will also be copied by Object.assign.\nObject.assign({ a: \u0026#39;b\u0026#39; }, { [Symbol(\u0026#39;c\u0026#39;)]: \u0026#39;d\u0026#39; }) // { a: \u0026#39;b\u0026#39;, Symbol(c): \u0026#39;d\u0026#39; } The Object.assign method performs a shallow copy, not a deep copy. That is, if the value of a property of the source object is an object, the target object will get a reference to that object.\nconst obj1 = {a: {b: 1}}; const obj2 = Object.assign({}, obj1); obj1.a.b = 2; obj2.a.b // 2 The source object obj1’s a attribute is the object, and the target object copies the object’s reference. Any changes to the object will be reflected in the target object.\nFor nested objects, once a property with the same name is encountered, Object.assign is handled by replacing\nconst target = { a: { b: ‘c’, d: ’e’ } } const source = { a: { b: ‘hello’ } } Object.assign(target, source) // { a: { b: ‘hello’ } } This means that the object is copied up to one level.\nCommon uses of Object.assign\nAdding methods to objects Object.assign(SomeClass.prototype,{ someMethod(arg1,arg2){ //... } }) SomeClass.prototype.someMethod = function (arg1, arg2) { //··· }; Cloning an object function clone(origin) { let originProto = Object.getPrototypeOf(origin); return Object.assign(Object.create(originProto), origin); } // Maintain the inheritance chain\nMerging multiple objects const merge = (target, ...sources) =\u0026gt; Object.assign(target, ...sources); //or const merge = (...sources) =\u0026gt; Object.assign({}, ...sources); Assigning default …","date":1524528e3,"description":"\"ES6 Standard Introduction\" Knowledge Points Summary","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"a9fd8f3646d172c96710582f60c7b175","permalink":"https://siqi-liu.com/en/post/object-expansion/","publishdate":"2018-04-24T00:00:00Z","relpermalink":"/en/post/object-expansion/","section":"post","summary":"\"ES6 Standard Introduction\" Knowledge Points Summary","tags":["JavaScript","JavaScript"],"title":"Object Expansion","tldr":null,"type":"post"},{"authors":null,"categories":["JavaScript"],"content":"Iterator Pattern Internal and External Iterators Internal Iterator Implement a custom “each” function. The “each” function accepts two parameters: the first is the array to be iterated over, and the second is the callback function to be triggered by the loop.\nvar each = function(arr, callback) { for(var i=0, l=arr.length; i\u0026lt;l; i++) { callback.call(arr[i], i, arr[i]) // Need to bind this to the item being traversed } } each([1,2,3], function(i, n) { console.log([i, n]) }) // [0,1] [1,2] [2,3]\nInternal iterators are very convenient when called. The outside world does not need to care about the implementation of the iterator. This is also the disadvantage of internal iterators. The internal iteration rules are already limited.\nExternal Iterators External iterators must explicitly request the next element to iterate over.\nExternal iterators increase the complexity of the call, but also increase the flexibility of the iterator. We can manually control the iteration process or order.\nfunction Iterator(obj) { var current = 0 var next = function() { current += 1 } var isDone = function() { return !(current \u0026lt; obj.length) } var getItem = function() { return obj[current] } return {next, isDone, getItem} } Example: Determine whether the values of two arrays are exactly equal\nvar iterator1 = Iterator([1, 2, 3, 4,5]), iterator2 = Iterator([1, 2, 3, 4]); var compare = function (item1, item2) { while (!item1.isDone() || !item2.isDone()) { console.log(item1.getItem(), item2.getItem()) if (item1.getItem() !== item2.getItem()) { throw new Error(\u0026#34;not equal\u0026#34;) } item1.next() item2.next() } console.log(\u0026#34;Equal\u0026#34;) } compare(iterator1, iterator2)//Equal ","date":1524217265,"description":"Javascript Design Patterns Knowledge Points","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"7ba65548418aa3f41d5c32120f185833","permalink":"https://siqi-liu.com/en/post/iterator-pattern/","publishdate":"2018-04-20T09:41:05Z","relpermalink":"/en/post/iterator-pattern/","section":"post","summary":"Javascript Design Patterns Knowledge Points","tags":["JavaScript","Design Patterns"],"title":"Iterator pattern","tldr":null,"type":"post"},{"authors":null,"categories":["JavaScript"],"content":"Proxy Mode Proxy Mode and Image Preloading Let’s implement a picture preloading function without using proxy mode.\nvar myImage = (function(){ var imgNode = document.createElement(“img”); document.body.appendChild(imgNode); var img = new Image; img.onload = function(){ imgNode.src = img.src; } return { setSrc:function(src){ imgNode.src = “./bd_logo1.png” img.src = src } } })() myImage.setSrc(“http://data.17jita.com/attachment/forum/201412/20/213333npk8mvppcav3rmv8.png\u0026#34;)\nThe problem is that if our network speed no longer requires preloading, we have to modify the myImage object.\nAt the same time, in addition to setting the src for the img node, myImage is also responsible for preloading images, which violates the single responsibility principle.\nThe following uses the proxy mode to implement the preloading function\nvar myImage = (function(){ var imgNode = document.createElement(\u0026#34;img\u0026#34;); document.body.appendChild(imgNode) return { setSrc:function(src){ imgNode.src=src } } })() var proxyImage = (function(){ var img = new Image; img.onload = function(){ myImage.setSrc(this.src) } return { setSrc:function(src){ myImage.setSrc(\u0026#34;./bd_logo1.png\u0026#34;) img.src = src; } } })() proxyImage.setSrc(\u0026#34;http://data.17jita.com/attachment/forum/201412/20/213333npk8mvppcav3rmv8.png\u0026#34;) Indirectly access myIname through proxyImage to control the preloading of images.\nWhen preloading is not required, just change the reference to proxyImage to myImage.\nVirtual proxy merges http requests In web development, the biggest overhead is network requests. Suppose we are doing a file synchronization function. When we select a checkbox, the corresponding file will be synchronized to another server.\nvar　synchronousFile = function(id){ console.log(\u0026#34;Start synchronizing files\u0026#34;+id) } var proxysynchronousFile = (function(){ var cache = [], timer; return function(id){ cache.push(id); if(timer){ return }; timer = setTimeout(function(){ synchronousFile(cache.join(\u0026#34;,\u0026#34;)) clearTimeout(timer); timer = null; cache.length = 0; },2000) } })() var checkbox = document.getElementByTagName(\u0026#34;input\u0026#34;); for(var i=0,c;c = checkbox[i];i++){ c.onclick = function(){ if(this.checked===true){ proxysynchronousFile(this.id) } } } Caching proxy Application of Cache Proxy - Calculating Product The cache proxy can provide temporary storage for the calculation results. During the next calculation, if the parameters passed are the same as before, the previously stored calculation results can be directly returned.\nvar mult = function(…numbers){ // ES6 rest parameter var a = 1; for(var i = 0, number; number = numbers[i]; i++){ a* = number } return a } mult(2, 3) // 6\nAdd caching proxy\nvar proxyMult = (function(){ var chache = {}; return function(…numbers){ var args = numbers.join(’,’) if(chache[args]){ return chache[args] } return chache[args] = mult(…numbers)//ES6 function extension operator, inverse operation of rest parameters } })( ) proxyMult(2,3)//6\nApplication of cache proxy-ajax asynchronous request resources For paging requirements encountered in projects, the backend only needs to fetch the same page once. The next time the same page is requested, the cached data can be used directly. A cache proxy can also be used. Unlike calculating a product, fetching data is an asynchronous operation, so the result cannot be directly cached. An asynchronous callback must be used.\nvar chache = {}; var request = function(num){ ajax(num) // Pretend to be an Ajax request .success(function(res){ render(res.data) // Pretend to be a rendering operation chache[num] = res.data }) } var getPage = function(num){ request(num) }\nvar proxyPage = function(){ return function(num){ if(chache[num]){ return chache[num] } getPage(num) } }\nDynamically creating proxies with higher-order functions You can pass a calculation function as a parameter to the proxy that creates the cache, so you can create a unified proxy for multiplication, addition, and subtraction.\n//Multiplication var mult = (…args)=\u0026gt;{ var a = 1 ; for(var i=0,c;c=args[i];i++){ a *=c } return a } //Addition var plus = (…args)=\u0026gt;{ var a = 0; for(var i=0,c;c=args[i];i++){ a+=c } return a }\n//Proxy factory var createProxyFactory = (fn)=\u0026gt;{ var chache = {}; return function(…args){ var arg = args.join(”,\u0026#34;); if(arg in chache){ return chache[arg] } return chache[arg] = fn(…args) } }; var proxyMult = createProxyFactory(mult), proxyPlus = createProxyFactory(plus);\nproxyMult(1,2,3,4)//24 proxyPlus(1,2,3,4)//10 Summary The key to the proxy pattern is that when direct access to an object is inconvenient or unsatisfactory, a proxy object is provided to control access to the object. The proxy object then processes the request and then forwards it to the original object.\nThe most commonly used proxy modes in JavaScript development are virtual proxy and caching proxy.\nVirtual proxies delay the creation of expensive objects until they are actually needed. (The imgNode sets its src after the img onload is complete.)\nThe cache proxy provides temporary storage for the …","date":1524131171,"description":"Javascript Design Patterns Knowledge Points","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"dc3bd4b2e10affd9c03c11e7a0ff23e6","permalink":"https://siqi-liu.com/en/post/proxy-mode/","publishdate":"2018-04-19T09:46:11Z","relpermalink":"/en/post/proxy-mode/","section":"post","summary":"Javascript Design Patterns Knowledge Points","tags":["JavaScript","Design Patterns"],"title":"Proxy Mode","tldr":null,"type":"post"},{"authors":null,"categories":["JavaScript"],"content":"Singleton Pattern Simple Singleton Pattern Implementation A variable is needed to indicate whether an instance of the object has been created.\nvar Single = function(name){ this.name = name; this.instance = null; } Single.getInstance = function(name){ if(!this.instance){ this.instace = new Single(name) } return this.instance } var a = Single.getInstance(\u0026#34;a\u0026#34;); var b = Single.getInstance(\u0026#34;b\u0026#34;); a===b //true Disadvantages: Opaque, compared to the normal new creation instance, the getInstance method must be used to create a singleton.\nTransparent Singleton Pattern The goal is to implement a transparent singleton class. Below we will use the CreateDiv singleton class to create a unique div node on the page.\nvar CreateDiv = (function(){ var instance = null; //Self-executing function ensures instance is initialized only once function CreateDiv(html){ if(instance){ return instance } this.html = html; //this.init(); return instance = this; } CreateDiv.prototype.init = function(){ var div = document.createElement(“div”); div.innerHTML = this.html; document.body.append(div); }\nreturn CreateDiv })() var a = new CreateDiv(“a”); var b = new CreateDiv(“b”); a===b//true\nTo encapsulate the instance, a self-executing anonymous function and closure are used, which increases the complexity. CreateDiv is responsible for two things: first, creating the object and executing the init method; second, ensuring that there is only one object. This violates the single responsibility principle.\nImplementing Singleton Pattern with Proxy To solve the single responsibility problem mentioned above, we introduce the proxy pattern to implement this singleton pattern. First, we separate object creation from executing the init method.\nfunction CreateDiv(html){ this.html = html; this.init(); } CreateDiv.prototype.init = function(html){ var div = document.createElement(“div”); div.innerHTML = this.html; document.body.append(div); } Next, introduce the proxy class\nvar createDivProx = (function(){ var instance; return function(html){ if(!instance){ instance = new CreateDiv(html) } return instance } })() var a = new createDivProx(\u0026#34;a\u0026#34;) var b= new createDivProx(\u0026#34;b\u0026#34;) a==b//true Js singleton mode - private variables encapsulated using closures Since we need an object, there is no need to create a class first.\nTo avoid the abuse of global variables, you can encapsulate the variables inside the closure and only expose the interface and external communication\nvar user = (function(){ var _name = “yosgi” var _age = 29; return { getUserInfo(){ return _name + “-” + _age } } })()\nOr use ES6 Symbols\nvar _name = Symbol.for(’name’); var _age = Symbol.for(‘age’); window[_name] = “yosgi” window[_age] = 29 // _name and _user won’t be accidentally overwritten\nCreating a Lazy Singleton A lazy singleton is one where an instance is created only when needed.\nThe next requirement is to create a div box (which may also be an iframe box in the future) when the login button is clicked.\nvar createLoginLayer = (function () { vardiv; return function () { if (!div) { div = document.createElement(\u0026#34;div\u0026#34;); div.style.display = \u0026#34;none\u0026#34;; document.body.appendChild(div); } return div } })() document.getElementById(\u0026#34;login\u0026#34;).onclick = function () { var LoginLayer = createLoginLayer() LoginLayer.style.display = \u0026#39;block\u0026#39;; }; To solve the single responsibility problem, extract the code that maintains a single instance\n//getSingle is used to manage singletons var getSingle = (function(){ var result; return function(fn){ if(!result){ //The closure allows result to store the result of fn result = fn.apply(this, arguments) } return result } })()\n//createSingleIframe is used to create instances var createSingleIframe = function(){ iframe = document.createElement(\u0026#34;iframe\u0026#34;); iframe.style.display = \u0026#34;none\u0026#34;; document.body.appendChild(iframe); return iframe } document.getElementById(\u0026#34;login\u0026#34;).onclick = function(){ var Iframe = getSingle(createSingleIframe) Iframe.style.display = \u0026#39;block\u0026#39;; Iframe.src=\u0026#34;http://baidu.com\u0026#34; }; Applying the Singleton Pattern After rendering a list on the page, bind the addEventListener click event. If you’re dynamically adding data via Ajax, you only need to bind it once, on the first render. If you don’t want to determine the number of renders, you can use getSingle\nvar getSingle = (function(){ var result; return function(fn){ if(!result){ result = fn.apply(this, arguments) } return result } })() var bindEvent = function(){ document.getElementById(\u0026#34;div1\u0026#34;).addEventListener(\u0026#34;click\u0026#34;,()=\u0026gt;{ console.log(\u0026#34;click\u0026#34;) }) return true } var render = function() { getSingle(bindEvent) } render() render() render() You can notice that clicking div1 multiple times will only trigger a click event once (of course you can also use onclick, a click can only point to a single object at the same time, which I didn’t notice before)\n","date":1523649990,"description":"Javascript Design Patterns Knowledge Points","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"49f79da2c9eecaa9e6c1b281587852a7","permalink":"https://siqi-liu.com/en/post/js-singleton-pattern/","publishdate":"2018-04-13T20:06:30Z","relpermalink":"/en/post/js-singleton-pattern/","section":"post","summary":"Javascript Design Patterns Knowledge Points","tags":["JavaScript","Design Patterns"],"title":"Js singleton pattern","tldr":null,"type":"post"},{"authors":null,"categories":["JavaScript"],"content":"Function Extension Default values for function parameters ES5\nfunction log(x,y){ if(typeof y===\u0026#39;undefined\u0026#39;){ y=\u0026#39;world\u0026#39; }; console.log(x,y) } log(\u0026#34;hello\u0026#34;)//hello world ES6\nfunction log(x,y=‘world’) { console.log(x,y) }\nUsually, parameters with default values should be the last parameters of a function, making it easier to see which parameters were omitted.\nFunction’s length property After specifying a default value, the length property of the function will return the number of parameters without a default value. The meaning of length is the number of parameters passed to the function. Similarly, rest parameters are not counted in the length property.\n(function(a){}).length //1 (function(a=5){}).length //0 (function(…args){}).length //0 Application Using default parameter values, you can specify that a parameter must be present, otherwise an error will be thrown.\nfunction throwIfMissing(){ throw new Error(\u0026#39;Missing Params\u0026#39;) } function foo(mustBeProvided = throwIfMissing()){ return mustBeProvided; } foo() // Uncaught Error: Missing Params rest parameters The rest parameter is used to retrieve excess function parameters, replacing the arguments object.\nfunction add(…values){ let sum = 0;\nfor(var val of values){ sum += val\n} return sum\n} add(1,2,3) // 6\nES5 arguments\nvar sortNumbers = function() { return Array.prototype.slice.call(arguments).sort() // slice can be converted to an array-like object } ES6 rest\nconst sortNumbers = (…numbers) =\u0026gt; numbers.sort() sortNumbers(3,7,4,5) // [3,4,5,7]\nExtension Operators The spread operator is three dots (…). It is the inverse operation of the rest parameter, converting an array into a comma-separated sequence of parameters;\nconsole.log(1,…[1,2,3],4) //1 1 2 3 4\nThis operator is mainly used for function calls\nfunction push(array,…items){ array.push(items) } push([1,2,3],4,5) //1,2,3,4,5 function add(x,y){ return x+y } var numbers = [4,38]; add(…numbers); //42\nAlternative to array apply var arr1 = [0,1,2] var arr2 = [3,4,5] es5\nArray.prototype.push.apply(arr1,arr2)//[1,2,3,4,5] es6\narr1.push(...arr2)//[1,2,3,4,5] New way to merge arrays es5\n[1,2].concat(more)\nes6\n[1,2,…more]\nCombining with destructuring assignment to create an array es5\na = list[0],rest = list.slice(1) es6\n[a,…rest] = list\nIf the spread operator is used with array assignments, it must be placed last in the argument list; otherwise, an error will be generated.\nConvert array-like objects var nodelist = document.querySelectorAll(“div”); var array = […nodelist]; Map and Set deconstruction, Generator function\n[…map.keys()] […generator()]\nname attribute The name attribute of a function returns the function name\nfunction foo(){} foo.name //\u0026#34;foo\u0026#34; var func1 = function(){}//\u0026#34;func1\u0026#34; (new Function).name //\u0026#34;anonymous\u0026#34; Arrow Functions es5\nvar f = function(v){ return v } es6\nvar f = v =\u0026gt; v\nNote:\nArrow functions do not have this. The inner this is the this of the outer code block. Because there is no this, they cannot be used as constructors.\nArrow functions cannot use arguments objects, you can use rest parameters instead\nArrow functions cannot be used as Generator functions\nes5\nfunction foo(){ this.id = 1; setTimeout(function(){ console.log(this.id)//undefined this points to window },1000) } foo.call({id:1}) es6\nfunction foo(){ setTimeout(()=\u0026gt;{ console.log(this.id) // 1 points to the called object }, 1000) } foo.call({id: 1}) Or it can be rewritten as function foo(){ var _this = this; setTimeout(function(){ console.log(_this.id) }, 1000) }\nIn addition to this, the following three variables do not exist in arrow functions and refer to the variables of the outer function.\narguments super new.target\nYou cannot use call(), apply(), or bind() to change the direction of this.\nFunction Binding The function binding operator (::) combines an object on the left with a function on the right. The operator automatically binds the object on the left as the context (this) to the function on the right.\nfoo::bar; // Equivalent to bar.bind(foo)\nfoo::bar(…arguments) // Equivalent to bar.apply(foo,arguments)\nTail Call Optimization Tail Recursion A tail call is a function that calls another function as its last step.\nfunction f(x) { return g(x) }\nA tail call doesn’t have to appear at the end of a function. It can be as long as it’s the last step.\nfunction f(x){ if(x\u0026gt;0){ return m(x) } return n(x) } // Both m and n are tail calls\nA function call creates a call frame in memory, which stores information such as the call location and internal variables. If function A calls function B from within function A, a call frame for function B will be created above A’s call frame. The call frame disappears only when B completes and returns its result to A.\nAll call frames form a call stack.\nSince a tail call is the last step of a function, there is no need to retain the outer function’s call frame, because the call location, memory variables and other information will no longer be used. The outer function’s call frame can be directly replaced with the …","date":1523491200,"description":"\"ES6 Standard Introduction\" Knowledge Points Summary","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"087f4ae8ff82a048005ca145a75e321e","permalink":"https://siqi-liu.com/en/post/function-extension/","publishdate":"2018-04-12T00:00:00Z","relpermalink":"/en/post/function-extension/","section":"post","summary":"\"ES6 Standard Introduction\" Knowledge Points Summary","tags":["JavaScript","JavaScript"],"title":"Function extension","tldr":null,"type":"post"},{"authors":null,"categories":["JavaScript"],"content":"Array expansion Array.from() Convert array-like objects and iterables to real arrays\nES5\nvar arr1 = [].slice.call(arraylike) ES6\nlet arr2 = Array.from(arraylike)\nNote that this is a shallow copy (if the element is an object reference, the object reference is copied to the new array).\nArray.from() accepts a second argument, similar to the array’s map method.\nlet names1 = Array.prototype.map.call(spans, s=\u0026gt;s.textContent); let names2 = Array.from(spans, s=\u0026gt;s.textContent, this);\nThe spread operator (…) can also convert certain data structures into arrays.\nfunction foo(){ var args = [...arguments] } Array.of() Used to convert a set of values into an array, it can replace Array() or new Array()\nES5\nfunction ArrayOf(){ return [].slice.call(arguments) } ArrayOf(3,11,8)//[3,11,8] ES6\nArray.of(3,11,8) // [3,11,8]\ncopyWithin() Copies the member at a specified position within the current array to another position (overwriting the existing member), then returns the current array.\nES5\nvar i32a = new Int32Array([1, 2, 3, 4, 5]); i32a.copyWithin(0, 2); // Int32Array [3, 4, 5, 4, 5]\nES6\nlet a = [1,2,3,4,5]; a.copyWithin(0,2) // [3, 4, 5, 4, 5]\n[].copyWithin.call({length: 5, 3: 1}, 0, 3); // Array-like object // {0: 1, 3: 1, length: 5}\nfind() and findIndex() Used to find the first matching array member, addressing the limitation of IndexOf that cannot detect NaN.\nES5\n[1,2,NAN].indexOf(NaN) //-1 ES6\n[1,2,NAN].find(Y=\u0026gt;Object.is(NaN,y)) //2 fill() Fills an array with the given value, erasing existing elements. If a second and third parameter are provided, they specify the starting and ending positions of the fill.\n[‘a’,‘b’,‘c’].fill(7,1,2) //[a,7,c]\nentries() keys() values() Used for traversing an array, both return an iterator object.\nfor(let index of [\u0026#34;a\u0026#34;,\u0026#34;b\u0026#34;].keys()){ cobsole.log(index) }//0 1 for(let [index,ele] of [\u0026#34;a\u0026#34;,\u0026#34;b\u0026#34;].entries()){ console.log(index,ele) }//0 \u0026#34;a\u0026#34; 1 \u0026#34;b\u0026#34; ","date":1523404800,"description":"\"ES6 Standard Introduction\" Knowledge Points Summary","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"f3bafbb1229daa793f348b97a8594752","permalink":"https://siqi-liu.com/en/post/array-expansion/","publishdate":"2018-04-11T00:00:00Z","relpermalink":"/en/post/array-expansion/","section":"post","summary":"\"ES6 Standard Introduction\" Knowledge Points Summary","tags":["JavaScript","JavaScript"],"title":"Array expansion","tldr":null,"type":"post"},{"authors":null,"categories":["JavaScript"],"content":"Variable Destructuring Definition: Allows you to extract values from objects in an array according to a certain pattern.\nArray destructuring assignment var [foo, bar] = [1, 2] // foo 1 bar 2\nDestructuring assignment allows you to specify a default value. If the default value is an expression, the expression is evaluated lazily.\nvar [foo = true] = [] //foo true var f = () =\u0026gt; 1 var [foo = f()] = [null] //foo null var [foo = f()] = [undefined] //foo 1\nObject Destructuring The difference between object structure assignment and array is that the variable must have the same name as the attribute\nvar {bar,foo} = {foo:\u0026#34;aaa\u0026#34;,bar:\u0026#34;bbb\u0026#34;} //or ({bar,foo}) = {foo:\u0026#34;aaa\u0026#34;,bar:\u0026#34;bbb\u0026#34;} Tips: (…) is a syntax that does not require declaration when using object literal destructuring assignment. However, the preceding statement needs a semicolon to avoid being treated as a function.\nObject destructuring allows you to easily assign existing object methods to a variable.\nlet {log, sin, cos} = Math\nDestructuring Function Parameters [[1,2],[3,4]].map(([a,b])=\u0026gt;a+b); Destructuring of function parameters can also use default values\nfunction move({x=0,y=0}={}){ return [x,y] }; move({x:3,y:8}) //[3,8]\nIf you remove the ={}, you will get an error when executing move().\nUses of destructuring assignment Returning multiple values from a function\nfunction foo() { return [1, 2, 3] } var [a, b, c] = foo()\nFunction parameter definition\nfunction f({x,y,z}){} f({z:3,x:1,y:2}) Extract json\nvar json = { id:42, status:“ok”, data:[1,2,3,4,] } let {id,status,data} = json\nTraversing the map deconstruction\nfor(let [key,value] of map){}; Get the key\nfor(let [key] of map){}; Just want to get the value\nfor(let [,value] of map){}; Input module specification method\n","date":1523358454,"description":"\"ES6 Standard Introduction\" Knowledge Points Summary","expirydate":-62135596800,"keywords":null,"kind":"page","lang":"en","lastmod":1781238520,"objectID":"0aae6d933672e72d183f781c55b79d0e","permalink":"https://siqi-liu.com/en/post/destructuring-assignment-of-variables/","publishdate":"2018-04-10T11:07:34Z","relpermalink":"/en/post/destructuring-assignment-of-variables/","section":"post","summary":"\"ES6 Standard Introduction\" Knowledge Points Summary","tags":["JavaScript","JavaScript"],"title":"Destructuring assignment of variables","tldr":null,"type":"post"}]