How to write high quality JS function (1) - Qiaoshanzhenhu articles

This article first appeared in vivo micro-channel public number of Internet technology 
links:https://mp.weixin.qq.com/s/7lCK9cHmunvYlbm7Xi7JxQ
Author: Yang Kun

Thousand readers, there are a thousand Hamlet.

This series of articles will function in terms of enforcement mechanisms, robustness, functional programming, design patterns, etc., a comprehensive exposition of how to write high-quality functions using JavaScript.

I. Introduction

How to write high quality through JavaScript function, which is a difficult question to answer, the hearts of different people have their own views on quality, and here I will be fully elaborated some of my personal views on how to write high quality function. View may not be comprehensive, there may be some wrong ideas are welcome to discuss, to live like a man, minor arguments always inadvertently appear, a tolerant heart than the best of best practice.

I intend to finish with articles "How to write high quality JS function"  in this series.

Mainly explained from the following aspects:

  • Function (anything is possible)

  • Naming function

  • Notes functions

  • The complexity of the function

  • Robust function (defensive programming)

  • And a function of the content of the reference parameters (return)

  • How to get through Renduermo function using functional programming

  • How to Design Mode function allows even more powerful

  • The preparation of the V8-friendly function is what style

  • Front-end engineers fantasy record function

Benpian only said first  function  , we must remember, let's dish seventy-seven thousand eight hundred eighty-one function.

Second, the function (anything is possible)

Function word represents everything is possible.

We think about it: we use function exactly how far away from us. Like playing mahjong, like, you think you can be like birds of God as to what you want to feel what it (rhetorical hyperbole).

Function every day and deal with, what is the purpose of function appears? Want to go any further, what enforcement mechanisms function is? Here we take a simple analysis.

1, the objective function appears

Function is by far the invention is used to conserve space and out of the most important means of improving performance.

PS: Note that there is no one.

2, enforcement mechanisms function

There is a saying that good, to know ourselves, know yourself. Want to win, we must be very understanding of the enemy. JS certainly not enemies anymore but want to know the function of JS, to more easily write high-quality function, it would have to master the enforcement mechanisms in the JS function.

How to explain the function of the enforcement mechanism it?

First to mimic a front end face questions: After entering a url, what will happen?

Perform a function, what will happen?

Reference to the following codes:

function say() {
  let str = 'hello world'
  console.log(str)    
}

If this pavement questions to you, how much you can know the result of it?

If I had to answer, I would say roughly:

First, I will create a function. If you learn C ++, you might say I have to open up a heap memory.

So, I will function from creation to execution of the function and its underlying implementation, the analysis of these three levels.

(1) create a function

Function is not created for no reason, we need to create. What happens when you create a function?

The first step: to open up a new heap memory

Each letter is to be storage space, as long as there is data, we will have to have a place to store data. The computer composition principle, the heap allows programs to dynamically allocate memory space of a certain size at runtime, so you can be when the program is running, as a function of application memory.

Step 2: Create a function say, put this code in the function body of the heap memory.

Functions in the body in the form of a string heap memory.

why? We look at say the function body code:

let str = 'hello world'
console.log(str)

These statements in the form of a string of heap memory is better, because there is no law. If the object is, because regularly be in the form of key-value pairs stored in the heap memory. And not the law are usually turned into a string.

The third step: say function declarations (variables) in the current context, will increase function declarations and definitions to the front

Note that the current context, we can understand the context stack (Stack), say is on the stack (stack) in the same time it's on the right there is a heap memory address, pointing to the heap of bodies function.

PS: recommend to learn about data structures, stack piece by piece, we are called frames. You can stack understanding DOM tree, frame understood as nodes, each frame (node) has its own name and content.

Step four: the opening up of heap memory address assigned to the function name say

The key here is to heap memory address assigned to the function name say.

Now I drew a simple diagram:

How to write high quality JS function (1) - Qiaoshanzhenhu articles

Say the right combination of storage on a map, go to understand the four steps above, sentiment is a bit of it.

(2) Do you really understand the assignment this operation it?

Here mentioned assignment. I heap memory address assigned to the function name say what does it mean?

Assignment is from a computer composition principle point of view, memory is divided into several areas, such as code area, stack area, a heap area and so on.

Each of these areas of memory address space of a memory is not the same. That is, the assignment (reference type) is to place a stack of a certain address area, flows into the (copy) via the bus conduit to the stack at an address corresponding to the region, so that memory space within the stack at an address region With reference to the address of the heap area data. The industry called the handle, which is a pointer. Only in high-level languages, the pointer is hidden, instead of using a variable pointer directly.

So a simple assignment, its underlying implementation on a computer, are very complex. Here, perhaps through assembly language, the better to understand the true meaning of assignment, such as 1 + 1 written in assembly language, the code is the following:

start:
mov ax, 1
mov bx, 1
add ax, bx
end start;

From the above code, we can see that the one assigned to ax, to use the mov instruction. Mov move the abbreviation is moved, it also proves that, in the assignment operation, essentially handle data flow or data in an address table.

PS: If it is a value type, that is, directly to the data stream (moved) to the specified memory address storage space.

These are my to explain some of the most fundamental aspects of phenomena create a function from the bottom of the computer, first elaborated here.

(3) executing the function

Execution of the function process is also very important, I've personally to explain the summary execution of this process.

Thinking a point.

We know that the body of the function of the code is stored in a string of heap memory. If we were to execute the code heap memory, first of all to become a real string of JS code, as serialization and de-serialization of data transmission.

Questions one: why there serialization and de-serialization? You can think about on their own, some of the more simple truth, the more behind with extraordinary ideas.

(4) the string into a real JS code

Each function call, the function will create a frame in the context of the stack. The stack is a fundamental data structure.

Why do you want to perform a function in the stack it?

After the stack is advanced out of the data structure, which means you can call a good save and restore the site.

Look at a piece of code:

function f1() {
  let b = 1;
  function f2() {
    cnsole.log(b)
  }
  return f2
}

let fun = f1()
fun()

What stack is a function of context?

A stack is a function context data structure, if the learned C or C ++, it is understood to be a struct (structure). This structure is responsible for managing the implementation of the function has been closed variable scope. Function in the context stack runtime generated and added to the beginning of the stack frame which is the global context, located on bottom of the stack.

(5) starts executing function

You must first understand one thing: execution of the function (function calls) is done on the stack.

This is why JS function recursively. Because the stack is advanced out of the data structure, given its ability to recursively.

Read on to perform functions generally have the following four steps:

The first step: forming a environment for code execution, as well as a stack memory.

Here, let's ponder a few questions:

  • What are the conditions for the implementation of the code is?

  • This is how the stack memory allocation out?

  • The internal stack memory is what kind of look like?

Step two: Copy the string stored in a new open stack memory, so that it becomes a real JS code.

The third step: firstly parameter assignment, then the variable lift, such as the variable lift var function.

Step Four: In this top-down implementation of the new open scope.

Questions: Why is top-down enforce it?

The execution results back to the function of the current call

Questions: The current implementation of the results returned to the function call, how it is achieved behind it?

Third, talk about the underlying implementation

1, the most essential computer interpretation closure

Function when executed, will form a new private scope, also known as private stack memory.

The purpose of the following two points:

  • The first point: the original string stored in the heap memory into real JS code.
  • The second point: the protection of the private variable stack memory without interference from the outside world.

This protection function is executed in a computer called the closure.

Some people may not understand, private ye of it?

No problem, we can reverse thrust. Assuming that not proprietary stack memory, then when performing a recursive, basically over, as a function of the context stack, there are many of the same JS code, such as local variables, if not privatization, it would not messed up? So assuming that contradiction, the establishment of private stack memory.

2, stack memory allocation is how out?

JS stack memory is automatically allocated a fixed size. If the automatic adaptation, then it basically does not exist an infinite loop stack in addition to this situation it overflowed.

3. The internal stack memory is what kind of look like?

For example, the return statement to write every day, then you know how the underlying return is achieved it? Write routine every day, then you know some truth underlying routines do?

We look at a map:

How to write high quality JS function (1) - Qiaoshanzhenhu articles

The figure shows a stack structure of a function call can be seen from the structure, the interior of the part, such as arguments, local variables, return addresses.

Look at the code below:

function f1() {
  return 'hello godkun'    
}
let result = f1()
f2(result)

The underlying meaning of this line is above, F () function is completed in the private memory stack, return after use, the execution result is transmitted to the EAX (accumulator registers), commonly used in the function return value.

Here to talk about Return Addr, Addr main purpose is to allow subroutine can be called many times.

Look at the code below:

function main() {
  say()
  // TODO:
  say()
}

As above, the main function in multiple calls subroutine say, to achieve the above at the bottom, it is stored in the stack structure by a running start address Addr is used to save function, say when a function is finished after the first run, it Addr It will be the starting point to run address, in order to prepare a subroutine call back several times.

How four, JS engine is to perform a function

In many ways the above analysis of the mechanism of function execution. Now let's briefly examine how JS engine is to perform the function.

Recommend a blog "to explore the JS engine works" , I will analyze some very important details on the basis of a blog on this post.

code show as below:

//定义一个全局变量 x
var x = 1
function A(y) {
//定义一个局部变量 x
var x = 2
function B(z) {
//定义一个内部函数 B
console.log(x + y + z)
}
//返回函数B的引用
return B
}
//执行A,返回B
var C = A(1)
//执行函数B
C(1)

A function is executed

ESCstack structure JS engine is configured as follows:

FIG referred to as A:

How to write high quality JS function (1) - Qiaoshanzhenhu articles

B function execution

ESCstack structure JS engine is configured as follows:

FIG referred to as B:

How to write high quality JS function (1) - Qiaoshanzhenhu articles

1, how local variables are saved up

Core code:

EC(B) = {
  [scope]:AO(A),
  var AO(B) = {
    z:1,
    arguments:[],
    this:window
  },
  scopeChain:<AO(B),B[[scope]]>  
}

This is the execution environment B B function when performing a function, created (an object structure). There is a AO (B), which is a function of the active object B.

What purpose AO (B) is? In fact, it points to the content of AO (B) is a list of each node.

Meanwhile, there is also defined a [scope] property, we can be understood as a pointer, [scope] point to AO (A), and AO (A) is a function of the object A is active.

Function active object holds the local variables, parameters, arrays, this property. This is also the reason why the use of this and the arguments within the function.

scopeChain 是作用域链,熟悉数据结构的同学肯定知道我函数作用域链本质就是链表,执行哪个函数,那链表就初始化为哪个函数的作用域,然后把当前指向的函数活动对象放到 scopeChain 链表的表头中。

比如执行 B 函数,那 B 的链表看起来就是 AO(B) --> AO(A)

同时,A 函数也是有自己的链表的,为 AO(A) --> VO(G) 。所以整个链表就串起来来,B 的链表(作用域)就是:AO(B) --> AO(A) --> VO(G)

链表是一个闭环,因为查了一圈,回到自己的时候,如果还没找到,那就返回 undefined 。

思考题:[scope] 和 [[scope]] 为什么以这种形式命名?

2、通过 A 函数的 ECS 我们能看到什么

我们能看到,JS 语言是静态作用域语言,在执行函数之前,整个程序的作用域链就确定了,从 A 图中的函数 B 的 B[[scope]] 就可以看到作用域链已经确定。不像 lisp 那种在运行时才能确定作用域。

3、执行环境,上下文环境是一种什么样的存在

执行环境的数据结构是栈结构,其实本质上是给一个数组增加一些属性和方法。

执行环境可以用 ECStack 表示,可以理解成 ECSack = [] 这种形式。

栈(执行环境)专门用来存放各种数据,比如最经典的就是保存函数执行时的各种子数据结构。比如 A 函数的执行环境是 EC(A)。当执行函数 A 的时候,相当于 ECStack.push[A] ,当属于 A 的东西被放入到栈中,都会被包裹成一个私有栈内存。

私有栈是怎么形成的?从汇编语言角度去看,一个栈的内存分配,栈结构的各种变换,都是有底层标准去控制的。

4、开启上帝模式看穿 this

this 为什么在运行时才能确定

上面两张图中的红色箭头,箭头处的信息非常非常重要。

看 A 图,执行 A 函数时,只有 A 函数有 this 属性,执行 B 函数时,只有 B 函数有 this 属性,这也就证实了 this 只有在运行时才会存在。

this 的指向真相

看一下 this 的指向,A 函数调用的时候,属性 this 的属性是 window ,而 通过 var C = A(1) 调用 A 函数后,A 函数的执行环境已经 pop 出栈了。此时执行 C() 就是在执行 B 函数,EC(B) 已经在栈顶了,this 属性值是 window 全局变量。

通过 A 图 和 B 图的比较,直接展示 this 的本质。

5、作用域的本质是链表中的一个节点

通过 A 图 和 B 图的比较,直接秒杀 作用域 的所有用法

看 A 图,执行 A 函数时,B 函数的作用域是创建 A 函数的活动对象 AO(A) 。作用域就是一个属性,一个属于 A 函数的执行环境中的属性,它的名字叫做 [scope] 。

[scope] 指向的是一个函数活动对象,核心点是把这个函数对象当成一个作用域,最好理解成一个链表节点。

PS: B 执行 B 函数时,只有 B 函数有 this 属性,这也就交叉证实了 this 只有在运行时才会存在。

6、作用域链的本质就是链表

通过比较 A 图和 B 图的 scopeChain ,可以确定的是:

作用域链本质就是链表,执行哪个函数,链表就初始化为哪个函数的作用域,然后将该函数的 [scope] 放在表头,形成闭环链表。作用域链是通过链表查找的,如果走了一圈还没找到,那就返回 undefined 。

五、用一道面试题让你更上一层楼(走火入魔)

再举一个例子,这是一道经常被问的面试题,看下面代码:

第一个程序如下:

function kun() {
  var result = []
  for (var i = 0; i < 10; i++) {
    result[i] = function() {
      return i
    }
  }
  return result
}

let r = kun()
r.forEach(fn => {
  console.log('fn',fn())
})

第二个程序如下:

function kun() {
  var result = []
  for (var i = 0; i < 10; i++) {
    result[i] = (function(n) {
      return function() {
        return n
      }
    })(i)
  }
  return result
}

let r = kun()
r.forEach(fn => {
  console.log('fn', fn())
})

输出结果大家应该都知道了,结果分别是如下截图:

第一个程序,输出 10 个 10 :

How to write high quality JS function (1) - Qiaoshanzhenhu articles

第二个程序,输出 0 到 9 :

How to write high quality JS function (1) - Qiaoshanzhenhu articles

那么问题来了,其内部的原理机制是什么呢?

  • 一部分 coder 只能答到立即调用,闭包。

  • 大多数 coder 可以答到作用域相关知识。

  • 极少部分 coder (大佬级别) 可以从核心底层原因来分析。

下面从核心底层原因来分析 。

1、分析输出10个10

代码如下:

function kun() {
  var result = []
  for (var i = 0; i < 10; i++) {
    result[i] = function() {
      return i
    }
  }
  return result
}

let r = kun()
r.forEach(fn => {
  console.log('fn',fn())
})

只有函数在执行的时候,函数的执行环境才会生成。依据这个规则,在完成 r = kun() 的时候,kun 函数只执行了一次,生成了对应的 AO(kun) 。如下:

AO(kun):{
  i = 10;
  kun = function(){...};
  kun[[scope]] = this;
}

这时,在执行 kun() 之后,i 的值已经是 10 了。请注意,kun 函数只执行了一次,也就意味着:

在 kun 函数的 AO(kun) 中的 i 属性是 10 。

继续分享, kun 函数的作用域链如下:

AO(kun) --> VO(G)

而且 kun 函数已经从栈顶被删除了,只留下 AO(kun) 。

注意:这里的 AO(kun) 表示一个节点。这个节点有指针和数据,其中指针指向了 VO(G) ,数据就是 kun 函数的活动对象。

那么,当一次执行 result 中的数组的时候,会发生什么现象?

注意:result 数组中的每一个函数其作用域都已经确定了,而 JS 是静态作用域语言,其在程序声明阶段,所有的作用域都将确定。

那么 ,result 数组中每一个函数其作用域链如下:

AO(result[i]) --> AO(kun) --> VO(G)

因此 result 中的每一个函数执行时,其 i 的值都是沿着这条作用域链去查找的,而且由于 kun 函数只执行了一次,导致了 i 值是最后的结果,也就是 10 。所以输出结果就是 10 个 10 。

总结一下,就是 result 数组中的 10 个函数在声明后,总共拥有了 10 个链表(作用域链),都是 AO(result[i]) --> AO(kun) --> VO(G)这种形式,但是 10 个作用域链中的 AO(kun) 都是一样的。所以导致了,输出结果是 10 个 10 。

下面我们来分析输出 0 到 9 的结果。

2、分析输出0到9

代码如下:

function kun() {
  var result = []
  for (var i = 0; i < 10; i++) {
    result[i] = (function(n) {
      return function() {
        return n
      }
    })(i)
  }
  return result
}

let r = kun()
r.forEach(fn => {
  console.log('fn', fn())
})

首先,在声明函数 kun 的时候,就已经执行了 10 次匿名函数。函数在执行时将生成执行环境,也就意味着,在 ECS 栈中,有 10 个 EC(kun) 执行环境,分别对应result 数组中的 10 个函数。

下面通过伪代码来展示:

ECSack = [
  EC(kun) = {
    [scope]: VO(G)
    AO(kun) = {
      i: 0,
      result[0] = function() {...// return i},
      arguments:[],
      this: window
    },
    scopeChain:<AO(kun), kun[[scope]]>
  },
  // .....
  EC(kun) = [
    [scope]: VO(G)
    AO(kun) = {
      i: 9,
      result[9] = function() {...// return i},
      arguments:[],
      this: window
    },
    scopeChain:<AO(kun), kun[[scope]]>
  ]
]

上面简单的用结构化的语言表示了 kun 函数在声明时的内部情况,需要注意一下两点:

第一点:每一个 EC(kun) 中的 AO(kun) 中的 i 属性值都是不一样的,比如通过上面结构化表示,可以看到:

  • result[0] 函数的父执行环境是 EC(kun) ,这个 VO(kun) 里面的 i 值 是 0 。

  • result[9] 函数的父执行环境是 EC(kun) ,这个 VO(kun) 里面的 i 值 是 9 。

记住 AO(kun) 是一段存储空间。

第二点:关于作用域链,也就是 scopeChain ,result 中的函数的 链表形式仍然是下面这种形式:

AO(result[i]) --> AO(kun) --> VO(G)

但不一样的是,对应节点的存储地址不一样,相当于是 10 个新的 AO(kun) 。而每一个 AO(kun) 的节点内容中的 i 值是不一样的。

所以总结就是:执行 result 数组中的 10 个函数时,运行10 个不同的链表,同时每个链表的 AO(kun) 节点是不一样的。每个 AO(kun) 节点中的 i 值也是不一样的。

所以输出的结果最后显示为 0 到 9 。

六、总结

通过对底层实现原理的分析,我们可以更加深刻的去理解函数的执行机制,从而写出高质量的函数。

如何减少作用域链(链表)的查找

比如很多库,像JQ 等,都会在立即执行函数的最外面传一个 window 参数。这样做的目的是因为,window 是全局对象,通过传参,避免了查找整个作用域链,提高了函数的执行效率。

如何防止栈溢出?每一次执行函数,都会创建函数的执行环境,也就意味着占用一些栈内存,而栈内存大小是固定的,如果写了很大的递归函数,就会造成栈内存溢出,引发错误。

我觉得,我们要去努力的达成这样一个成就:

When I do a handwriting function, my mind very clearly know every line of code I'm writing, which is how memory performance, or that its bottom is how to implement to achieve the  eyes of code hearts Uncensored  realm.

If we could do that, then afraid not write high-quality function?

Seven reference documents

  1. Creation and enforcement mechanisms JS function

  2. Explore the JS engine works

  3. Program memory space (code segments, data segments, stack segment)

  4. Function call - function stack

  5. Stack up and down the growth of in-depth understanding of growth

Guess you like

Origin blog.51cto.com/14291117/2432728