javascript_如何不再害怕JavaScript

javascript

成为一名出色的Javascript开发人员要知道的事情 (Things to know to be a great Javascript developer)

Have you been there before? Where Javascript just doesn’t seem to work. Where the functions you write don’t do what you expect them to? Where this just doesn’t make sense? What is this? This is this.

你以前去过那里吗? Javascript似乎不起作用的地方。 您编写的功能在哪里没有达到您的期望? this根本没有意义吗? 什么是this ? 这就是this

I have. So, I wrote this article. It covers everything from closures and classes to objects and hoisting.

我有。 所以,我写了这篇文章。 它涵盖了从闭包和类到对象和提升的所有内容。

It has helped me become a better developer. I hope it helps you too.

它帮助我成为了更好的开发人员。 希望对您有帮助。

资料模型 (Data Model)

类型 (The types)

Stick with me. I’m doing this because there are two not so well-known types I want you to know about: Symbols and Numbers.

坚持我 我这样做是因为有两种我想让您了解的不太知名的类型:符号和数字。

Also the difference between undefined and null eludes many.

同样,未定义和null之间的区别也很多。

号码 (Numbers)

All numbers in JS are “double precision 64-bit format IEEE 754 values”. Commonly known as floats, which means there is no concept of an integer. Your integers are stored as floats.

JS中的所有数字均为“双精度64位格式IEEE 754值”。 通常称为浮点数,这意味着没有整数的概念。 您的整数存储为浮点数。

To convert strings to numbers: use parseInt('123', 10) . The second argument is the base. So, when dealing with binary, you could do:

要将字符串转换为数字:使用parseInt('123', 10) 。 第二个参数是基数。 因此,在处理二进制文件时,您可以执行以下操作:

> parseInt('101',2)
5

Similarly, parseFloat('number') exists for floating point numbers. The base here is always 10.

同样,对于浮点数存在parseFloat('number') 。 此处的底数始终为10。

符号 (Symbols)

The only purpose of this data type is to identify object properties. Iteration protocol and Regex are the most popular examples using Symbols. We’ll cover the iteration protocol in the next part!

该数据类型的唯一目的是识别对象属性。 迭代协议和Regex是使用Symbol的最受欢迎的示例。 我们将在下一部分中介绍迭代协议!

You can create one via Symbol(). Every call generates a new symbol. Thus,

您可以通过Symbol()创建一个。 每次通话都会产生一个新的符号。 从而,

console.log(Symbol(42) === Symbol(42)) // false

Symbols can persist across files in JavaScript. In this sense, they are different from global variables.

符号可以在JavaScript中的文件之间持久存在。 从这个意义上说,它们不同于全局变量。

There exists a global symbol registry which stores all symbols encountered. To add a Symbol to the registry, use Symbol.for(), and to retrieve the symbol use Symbol.keyFor().

存在一个全局符号注册表,其中存储了所有遇到的符号。 一个符号添加到注册表中,用Symbol.for()并获取符号使用Symbol.keyFor()

More information on Symbols see here.

有关符号的更多信息,请参见此处

未定义和空 (Undefined and Null)

Why the distinction between undefined and null?

为什么要区分undefined和null?

By convention, Null indicates a deliberate non-existing value. And undefined is an un-initialized value.

按照约定,Null表示故意的不存在的值。 而undefined是未初始化的值。

For example, say you have a field which stores an ID if it exists. In this case, instead of using a magic value like “NOT_EXISTS”, you can use null. If it’s supposed to exist but isn’t there right now, you can show that via undefined.

例如,假设您有一个存储ID的字段(如果存在)。 在这种情况下,可以使用null来代替使用“ NOT_EXISTS”之类的魔术值。 如果应该存在但现在不存在,则可以通过undefined来显示。

变量和范围 (Variables and scopes)

在ES2015之前 (Before ES2015)

var was the only way to define variables.

var是定义变量的唯一方法。

Further, we had only two scopes: global and function scope. Variables declared inside a function become local to that function. Anything outside the function scope couldn’t access them.

此外,我们只有两个范围: 全局范围和功能范围。 在函数内部声明的变量成为该函数的局部变量。 功能范围之外的任何内容都无法访问它们。

Thus, they had function scope.

因此,它们具有功能范围。

在ES2015之后 (After ES2015)

ES2015 introduced two new ways of defining variables:

ES2015引入了两种定义变量的新方法:

  • let

    let

  • const

    const

With them came the concept of block scope. A block is everything between two curly braces {..}

随之而来的是作用域的概念。 一个块是两个大括号{..}之间的所有内容

ES2015 is backwards compatible, so you still can use var, although their usage is discouraged.

ES2015向后兼容,因此尽管不鼓励使用var,但仍可以使用var。

var x = 1;
{
  var x = 2;
}
console.log(x) // OUTPUT: 2, as block doesn't mean anything to var.
let x = 1;
{
  let x = 2;
}
console.log(x) // OUTPUT: 1
可变吊装 (Variable Hoisting)

JavaScript has a peculiar idea with var called hoisting.

JavaScript具有var的独特概念,称为提升。

function something() {
  console.log(name);
  let name = 'neil';
  console.log(name);
}

Can you guess what would happen above?

你能猜出上面会发生什么吗?

I say a ReferenceError: we are using the variable name before it’s defined. It makes sense, that’s what happens.

我说一个ReferenceError :我们在定义变量名之前就使用它。 这是有道理的,这就是发生的情况。

However, if I were using var instead of let, I’d get no error.

但是,如果我使用var而不是let ,那么我不会出错。

function something() {
  console.log(name); // OUTPUT: undefined
  var name = 'neil';
  console.log(name); // OUTPUT: neil
}

What’s happening behind the scenes?

幕后发生了什么?

function something() {
  var name; // variable hoisting

  console.log(name); // OUTPUT: undefined
  name = 'neil';
  console.log(name); // OUTPUT: neil
}

This is another reason why the use of var is discouraged. It can lead to interesting bugs.

这是不鼓励使用var另一个原因。 它可能导致有趣的错误。

短路逻辑:&&和|| (Short circuit logic: && and ||)

With JavaScript, something peculiar goes on with logic operations. (And in Python too.)

使用JavaScript,逻辑运算会发生一些特殊的事情。 (在Python中也是。)

Something that lets you do arcane stuff like this:

可以让您做一些神秘的事情,例如:

// o is an object
var name = o && o.name;

What do you think name is? If the object, o is null or undefined, name is null or undefined.

你觉得name叫什么? 如果对象o为null或未定义,则name为null或未定义。

If o is defined but o.name is undefined, name is undefined.

如果定义了o但未定义o.name ,则name未定义。

If o is defined, o.name is defined, then name = o.name.

如果定义了o则定义了o.name ,然后name = o.name

We were using a boolean logic operator right? How is this possible then?The answer is short circuiting and truthiness.

我们使用的是布尔逻辑运算符,对吗? 那怎么可能呢?答案是短路和真实性。

真实性 (Truthiness)

A value is truthy if it evaluates to true in a Boolean context. All values are truthy except for the following falsy values:

如果值在布尔上下文中为true,则该值为true。 除以下虚假值外,所有值都是真实值:

  • false

    false

  • 0

    0

  • ""

    ""

  • null

    null

  • undefined

    undefined

  • NaN

    NaN

Note: which means, {} and [] are truthy!

注意:这意味着{}[]是真实的!

A usual trick to convert something to its truthy value: !!

将某物转换为其真实值的常用技巧: !!

! converts to not — the falsy value — and ! again converts it back to true/false.

! 转换为not-伪造的值-和! 再次将其转换回true / false。

短路 (Short circuiting)

The idea is Boolean operators return the final value that makes the statement true or false, not whether the statement is true or false. Like we saw above, to convert it to the truthy value, you can use !!.

想法是布尔运算符返回使语句为true或false的最终值,而不是语句为true或false的最终值。 就像我们在上面看到的,要将其转换为真实值,您可以使用!!

Short circuiting happens when the boolean expression isn’t evaluated completely. For example,

如果布尔表达式未完全求值,则会发生短路。 例如,

null && ...

null && ...

It doesn’t matter what ... is. null is falsy, so this expression would return null.

没关系...是什么。 null是虚假的,因此此表达式将返回null

Same case with [] || .... [] is truthy, so this expression would return [], irrespective of what ... is.

[] || ...相同 [] || ... []是真实的,因此此表达式将返回[] ,而不管...是什么。

对象 (Objects)

An Object in JavaScript is a collection of name value pairs. If you’re coming from How not to be afraid of Python anymore, don’t confuse the Python Object with the JavaScript Object.

JavaScript中的对象是名称值对的集合。 如果您来自“不再怕Python”的知识 ,请不要将Python Object与JavaScript Object混淆。

The closest equivalence to the JavaScript Object is the Python dict.

与JavaScript Object最接近的等效项是Python dict

For the types available in an Object, name: string or Symbol value: Anything.

对于对象中可用的类型,名称: stringSymbol值:任何。

Arrays are a special type of object. They have a magic property: length (and a different prototype chain. See below.) The length of the array is one more than the highest index. This is mutable, which means you can do funky stuff with it (not recommended):

Arrays是一种特殊的对象类型。 它们具有一个神奇的特性:长度(和不同的原型链。请参见下文。)数组的长度比最高索引大一倍。 这是可变的,这意味着您可以使用它来做一些时髦的事情(不推荐):

const funkyArray = [];
funkyArray['0'] = 'abcd';
funkyArray['length'] = 3

> console.log(funkyArray);
(3) ["abcd", empty × 2]

> funkyArray[4] = 'x';
> console.log(funkyArray);
(5) ["abcd", empty × 3, "x"]

Notice the use of numbers and strings as array indexes. Numbers work because Objects implicitly call toString() on the name.

注意使用数字和字符串作为数组索引。 数字之所以有效,是因为对象在名称上隐式调用toString()

Iterating over arrays and objects, using constructs like for...of, for...in and forEach is something I’ll leave for the next part. (Plus, an interesting bug when using objects as maps in JavaScript!)

使用for...offor...inforEachfor...of结构遍历数组和对象是我接下来forEach内容。 (此外,在JavaScript中将对象用作地图时,这是一个有趣的错误!)

全局对象 (Global object)

A global object is an object that always exists in the global scope. In JavaScript, there’s always a global object defined. In a web browser, when scripts create global variables, they’re created as members of the global object [1]. The global object’s interface depends on the execution context in which the script is running. For example:

全局对象是始终存在于全局范围中的对象 。 在JavaScript中,总是定义一个全局对象。 在Web浏览器中,脚本创建全局变量时,会将它们创建为全局对象[ 1 ]的成员。 全局对象的接口取决于脚本在其中运行的执行上下文。 例如:

  • In a web browser, any code which the script doesn’t specifically start up as a background task has a Window as its global object. This is the vast majority of JavaScript code on the Web.

    在Web浏览器中,脚本未专门作为后台任务启动的任何代码都将Window作为其全局对象。 这是Web上绝大多数JavaScript代码。
  • Code running in a Worker has a WorkerGlobalScope object as its global object.

    在Worker中运行的代码具有一个WorkerGlobalScope对象作为其全局对象。
  • Scripts running under Node.js have an object called global as their global object. [2]

    在Node.js下运行的脚本具有一个称为global的对象作为其全局对象。 [ 2 ]

功能 (Functions)

In JavaScript, functions are first class objects. They can have properties and methods like any other objects. They can be passed to other functions as parameters (meta-recursion!). The way functions differ from objects is that they are callable.

在JavaScript中,函数是一流的对象。 它们可以像其他任何对象一样具有属性和方法。 它们可以作为参数传递给其他函数(元递归!)。 函数与对象的区别在于它们是可调用的。

All functions extend the Function object. This object has no properties or methods pre-defined, but inherits some from the Function.prototype. (This will become clear in the prototype section below). Further, this Function object is a constructor for functions. You can create functions in at least 4 ways:

所有功能都扩展了Function对象。 该对象没有预定义的属性或方法,但继承自Function.prototype 。 (这将在下面的原型部分中变得清楚)。 此外,此Function对象是Function的构造函数。 您可以通过至少4种方式创建函数:

function functionDeclaration() {};
var anonymousFunctionExpression = function() {};
var namedFunctionExpression = function named() {};
var arrowFunctionExpression = () => {};
var constructorFunction = new Function(...args, functionBody); // functionBody is a string

The return statement can return a value at any time, terminating the function. JavaScript returns undefined if it sees no return statement (or an empty return with no value).

return语句可以随时返回一个值,从而终止该函数。 如果JavaScript没有看到return语句(或者没有值的空返回),则返回undefined。

All arguments defined for the function go in arguments var. The default value for all the arguments is undefined.

为该函数定义的所有参数都放在var中。 所有参数的默认值为undefined

Have you ever seen the three dots in JavaScript before? ... . Like the one I used above in constructorFunction ? They boggled my mind the first time I saw them. They are a part of syntax in JavaScript. It’s not pseudocode (like I first thought).

您以前看过JavaScript中的三个点吗? ... 就像我上面在constructorFunction使用的那样? 我第一次见到他们时,他们就把我的脑袋吓坏了。 它们是JavaScript语法的一部分。 它不是伪代码(就像我首先想到的那样)。

They are the rest and spread parameter syntax.

它们是restspread参数的语法。

The are opposites of each other. spread spreads arguments, rest brings them back together.

彼此相反。 spread传播论点, rest使他们重新团结起来。

Here’s an example: Excuse the poorly designed function — which doesn’t need the arguments to be named — but I am making a point.

这是一个示例:不好意思设计的函数-不需要命名参数-但是,我要指出一点。

const average = function( val1, val2, val3, ...otherValues) { // rest
  console.log(otherValues);
  let sum = 0;
  for (let i = 0; i < arguments.length; i++) { 
    sum += arguments[i];
  }
  return sum / arguments.length;
}
let values = [1, 2, 3, 4, 5, 6]
const averageValue = average(...values); // spread

What’s happening here? otherValues is using the rest syntax to collect an infinite number of arguments passed to average. The console.log() would print [4, 5, 6] above.

这里发生了什么事? otherValues正在使用rest语法来收集传递给平均值的无数个参数。 console.log()将在上面打印[4, 5, 6]

values is using the spread syntax to convert the array into single arguments. It works such that behind the scenes, the below is equivalent to the above.

values使用传播语法将数组转换为单个参数。 它的工作原理是,在幕后,以下等同于上面。

const averageValue = average(1,2,3,4,5,6)

Another thing to note is that default argument values are evaluated every time function is called, unlike Python where it happens only once.

需要注意的另一件事是,每次调用函数时都会对默认参数值进行求值,这与Python每次仅调用一次的情况不同。

There are 3 interesting prototype functions available to function objects. These are apply(), bind() and call(). The A,B,C of JavaScript.

有3个有趣的原型函数可用于函数对象。 这些是apply()bind()call() 。 JavaScript的A,B,C。

With the advent of spread and rest syntax, apply() and call() aren’t different anymore.

随着spread和rest语法的出现, apply()call()不再相同。

apply() calls a function with an array of args; call() calls a function with individual values.

apply()调用带有args数组的函数; call()调用具有单个值的函数。

The cool bit is, they allow you to call the function with a custom this object.

最酷的一点是,它们允许您使用自定义this对象来调用函数。

We will talk more about apply() and bind() once we cover the this object.

讨论完this对象后,我们将进一步讨论apply()bind()

匿名函数和内部函数 (Anonymous and inner functions)
const avg = function () {
  let sum = 0;
  for (let i = 0, argLength = arguments.length; i < argLength; i++) { // arguments variable is an array containing all args passed to the function.
    sum += arguments[i];
  }
  return sum / arguments.length; // argLength isn't available here
};

The expressions function avg() and var avg = function () are semantically equivalent.

function avg()var avg = function ()的表达式在语义上是等效的。

However, there is a distinction between the function name (here anonymous — so doesn’t exist) and the variable the function is assigned to.

但是,函数名称(此处为匿名,因此不存在)与函数所分配的变量之间存在区别。

The function name cannot be changed, while the variable the function is assigned to can be reassigned. The function name can be used only within the function’s body. Attempting to use it outside the function’s body results in an error (or undefined if the function name was previously declared via a var statement).

函数名称不能更改,而可以将分配了该函数的变量重新分配。 函数名称只能在函数体内使用。 尝试在函数主体外部使用它会导致错误(如果先前通过var语句声明了函数名称,则将导致未定义)。

This idea of functions being passed as variables gives rise to enormous power. For example, you can hide local variables:

函数作为变量传递的想法产生了巨大的力量。 例如,您可以隐藏局部变量:

var a = 1;
var b = 2;
(function() {
  var b = 3; // hidden local variable
  a += b;
})();
a; // 4
b; // 2

The expression above is called an IIFE (Immediately invoked function expression) — where you create a function and immediately call it.

上面的表达式称为IIFE(立即调用的函数表达式),您可以在其中创建一个函数并立即对其进行调用。

Further, we can nest functions inside each other too! These are called inner functions. The important thing to keep in mind: inner functions have access to variables defined in the parent functions, but not the other way around. This is a direct result of closures, which we will cover soon.

此外,我们也可以将函数嵌套在一起! 这些称为内部函数 。 要记住的重要一点:内部函数可以访问父函数中定义的变量,但不能相反。 这是关闭的直接结果,我们将在稍后讨论。

This lets you create functions like:

这使您可以创建如下功能:

let joiner = function(separator) {    // The outer function defines separator
    return function(left, right) {      
        return left + " " + separator + " " + right;    // The inner function has access to separator
    }    // This exposes the inner function to the outside world
}
let and = joiner("and");
and("red", "green"); // There's no way to change the separator for AND now; except by reassigning the function variable.
// red and green
const or = joiner("or"); // There's no way to change the separator for OR now.
or("black", "white"); 
// black or white
功能提升 (Function hoisting)

With function declarations, the function definitions are hoisted to the top of the scope.

使用函数声明,将函数定义提升到作用域的顶部。

With function declarations, the function definitions are hoisted to the top of the scope.With function expressions, the function definitions aren’t hoisted.

使用函数声明,将函数定义提升到作用域的顶部。 使用函数表达式时,不会悬挂函数定义

Okay, you might be confused about what’s the difference between the terms. I was.

好的,您可能对这两个术语之间的区别感到困惑。 我曾是。

function declaredFunction() { // this is the function declaration
    // what comes here is the function definition
}
let functionExpression = function() { // this is a function expression
    // what comes here is the function definition
}

类和原型链 (Classes and The Prototype Chain)

JavaScript uses functions as classes. The recently introduced class statement is syntactic sugar over functions.

JavaScript使用函数作为类。 最近引入的类声明是功能上的语法糖。

Since all data in JavaScript is an Object, it makes sense that our functions — which are a class constructor — will return an Object.

由于JavaScript中的所有数据都是Object ,因此我们的函数(是类构造函数)将返回Object才有意义。

Thus, given all the basics we know about functions and objects, we can do something like this to create a class for, say (thinks really hard to figure out a non trivial, useful and relatable example…)…. … .. .A tweet interface! That sounds like fun.

因此,考虑到我们了解的有关函数和对象的所有基础知识,我们可以做类似的事情来创建一个类,例如(认为​​很难找出一个非琐碎,有用和相关的示例……) ……。 ……一个推特界面! 这听起来很有趣。

Imagine you’re building your own front-end to show tweets, talking to the twitter API to get data for the tweets.

想象一下,您正在构建自己的前端以显示推文,并与twitter API交谈以获取推文数据。

function Tweet(id, username, content, parent = null) {
  return {
    id, // Javascript implicitly converts this into id: id
    username,
    content,
    getUrl: function() {
      return 'https://twitter.com/' + this.username + '/' + this.id;
    },
    isComment: function() {
      return parent !== null;
    }
  };
}
var t = Tweet(1, '@neilkakkar', 'How not to be afraid of JS anymore'); 
// Remember, we can fill any number of args
// the rest are undefined or default
// All args are in the arguments variable
t.getUrl(); // "https://twitter.com/@neilkakkar/1"
t.isComment(); // "false"

this keyword references the current object. Using dot notation, this becomes the object on which dot was applied. Otherwise, it’s the global object.

this关键字引用当前对象。 使用点表示法,它成为应用点的对象。 否则,它是全局对象。

A note from MDN:

来自MDN的说明:

In most cases, the value of this is determined by how a function is called. It can’t be set by assignment during execution, and it may be different each time the function is called. ES5 introduced the bind()method to set the value of a function’s this regardless of how it’s called, and ES2015 introduced arrow functions which don’t provide their own this binding (it retains the this value of the enclosing lexical context).

在大多数情况下,其值取决于函数的调用方式。 在执行过程中不能通过赋值来设置它,并且每次调用该函数时可能会有所不同。 ES5引入了bind()方法来设置函数this的值,而不管其调用方式如何,ES2015引入了arrow函数,该函数不提供自己的this绑定(它保留了封闭词法上下文的this值)。

This (pun intended) is a frequent cause of mistakes. For example:

这(双关语是故意的)经常导致错误。 例如:

const t = Tweet(1, '@neilkakkar', 'How not to be afraid of JS anymore');
const urlFetcher = t.getUrl; // assigning the function
urlFetcher(); // https://twitter.com/undefined/undefined

When we call urlFetcher() alone, without using t.getUrl(), this is bound to the global object. Since there are no global variables called username or id we get undefined for each one.

当我们单独调用urlFetcher()而不使用t.getUrl()this将绑定到全局对象。 由于没有名为usernameid全局变量,因此每个变量均未undefined

We can take advantage of the this keyword to improve our Tweet function. The idea is, instead of creating an object and returning that, we expect a new object (referenced by this) and modify its properties.

我们可以利用this关键字来改善我们的Tweet功能。 这个想法是,我们期望创建一个新对象(由this引用)并修改其属性,而不是创建并返回该对象。

function Tweet(id, username, content, parent = null) {
  this.id = id;
  this.username = username;
  this.content = content;
  this.getUrl = function() {
      return 'https://twitter.com/' + this.username + '/' + this.id;
  };
  this.isComment = function() {
      return parent !== null;
    }
  };
}
var t = new Tweet(1, '@neilkakkar', 'How not to be afraid of JS anymore');

The new keyword creates a brand new empty object, and then calls the function specified, with this set to the new object. Our modified function does not return a value but merely modifies the this object. new also returns the this object, once the function is called on it. This is what we wanted. new also does some extra stuff which we want — like setting up the prototype chain — but we will get into that in a little bit.

new关键字创建一个全新的空对象,然后调用指定的函数,并将this设置为新对象。 我们修改后的函数不会返回值,而只会修改this对象。 调用this函数后, new也会返回this对象。 这就是我们想要的。 new还提供了一些我们想要的其他东西-例如建立原型链-但我们将稍作介绍。

Such functions, that are designed to be called by new, are called constructor functions. By convention, these functions are capitalized (as a reminder to call them with new).

这些设计为由new调用的函数称为构造函数 。 按照惯例,这些函数使用大写字母(提醒您使用new调用它们)。

Since we get a new object every time we call Tweet, we have two function objects (getUrl and isComment) created every time we call Tweet. A better way is to write these functions outside the constructor scope — and pass a reference.

因为每次我们打电话时得到一个新的对象Tweet ,我们有两个函数对象( getUrlisComment )创建的每个我们称之为时间Tweet 。 更好的方法是在构造函数范围之外编写这些函数,并传递一个引用。

If you’re coming from an OOP background, even this might not seem good enough. You don’t want this function to be used anywhere but for this Tweet object. You don’t want to dirty your global function list. This is where JavaScript’s “inheritance” comes in.

如果您来自OOP背景,那么这似乎还不够好。 您不希望将此函数用于此Tweet对象。 您不想弄脏全局函数列表。 这就是JavaScript的“继承性”出现的地方。

原型 (Prototype)

Tweet.prototype is an object shared by all instances of Tweet. It forms part of a lookup chain (that has a special name, “prototype chain”): any time you access a property of Tweet that isn’t set, JavaScript will check Tweet.prototype to see if that property exists there.

Tweet.prototype是由Tweet的所有实例共享的对象。 它构成了查找链的一部分(有一个特殊的名称,即“原型链”):每当您访问未设置的Tweet属性时,JavaScript都会检查Tweet.prototype以查看该属性是否存在。

As a result, anything assigned to Tweet.prototype becomes available to all instances of that constructor via the this object.

结果,分配给Tweet.prototype任何内容都可以通过this对象用于该构造函数的所有实例。

Each object has a private property (__proto__) which holds a link to another object called its prototype. That prototype object has a prototype of its own, and so on until an object is reached with null as its prototype. By definition, null has no prototype, and acts as the final link in this prototype chain.

每个对象都有一个私有属性( __proto__ ),该属性持有指向另一个称为其原型的对象的链接。 该原型对象具有自己的原型,依此类推,直到到达以null为原型的对象。 根据定义,null没有原型,并充当该原型链中的最终链接。

This is an incredibly powerful tool. JavaScript lets you modify something’s prototype at any time in your program, which means you can add extra methods to existing objects at runtime (without having to call the constructor again).

这是一个非常强大的工具。 JavaScript使您可以随时在程序中修改某些东西的原型,这意味着您可以在运行时向现有对象添加额外的方法(而不必再次调用构造函数)。

var t = new Tweet(1, '@neilkakkar', 'How not to be afraid of JS anymore');
t.getComments(); // TypeError on line 1: t.getComments is not a function
Tweet.prototype.getComments = function() {
  // example API call to Twitter API - let's say it exists as the twitterService object
  return twitterService.getComments(this.id);
};
t.getComments(); // "[ 'This is an amazing article, thank you!' , 'I love it' ]" 
// fictional comments
function.prototype与__proto__ (function.prototype vs __proto__)

You’ve probably seen both being used interchangably. They aren’t the same. Let’s clear this up.

您可能已经看到两者可以互换使用。 他们不一样。 让我们清理一下。

The function.prototype is a constructor for __proto__.

function.prototype__proto__的构造function.prototype

__proto__ is the actual prototype object available on objects.

__proto__是对象上可用的实际原型对象。

Thus, function.prototype is only available to constructor functions. You can’t access the prototype for a tweet as t.prototype, you’ll have to use t.__proto__.

因此, function.prototype仅可用于构造函数。 您不能以t.prototype访问推文的原型,而必须使用t.__proto__

But to set the prototype, you’d use Tweet.prototype.getComments() like in the above example.

但是要设置原型,您可以像上面的示例一样使用Tweet.prototype.getComments()

回顾我们对函数和类所做的工作 (A refresher of what we did with functions and classes)
  • Classes are functions. We began with a function that was creating a new object ( return {...}- using object literal syntax), then adding properties to it ( the class data ) and finally returning it.

    类是函数。 我们从一个函数开始,该函数创建一个新对象(使用对象文字语法return {...} -,然后向其添加属性(类数据),最后返回它。

  • Then come constructor functions. These assume there is a given empty object ( initialized via new ) and just add the properties to it.

    然后来构造函数。 这些假设有一个给定的空对象(通过new初始化),然后向其中添加属性。

  • Then comes the prototype chain, for methods that would be used by all objects of the class

    然后是原型链,用于class所有对象将使用的方法

Behind the scenes, this is how things work when using the class keyword.

在幕后,这是使用class关键字时事情的工作方式。

新关键字与应用 (The New Keyword and Apply)

We can now explore what happens behind the scenes with new and revisit apply() from the function prototype. We’ve already seen bind().

现在,我们可以使用函数原型中的new并重新访问apply()来探索幕后发生的事情。 我们已经看过bind()

The function of new is to create an object, pass it to the constructor function (where this object is available as this), and set up the prototype chain.

new的功能是创建一个对象,将其传递给构造函数(该对象可作为this ),并建立原型链。

apply() takes an object (the this value) and an array of arguments to be called on that object.

apply()接受一个对象( this值)和要在该对象上调用的参数数组。

Putting these two together, we get a trivial implementation of new.

将这两者放在一起,我们将获得新的琐碎实现。

function newNew(constructorFunction, ...args) {
  const thisObject = {}; // create object using object literal syntax
  constructorFunction.apply(thisObject, args); // calls constructorFunction with this set to thisObject and with given args
  // setting up prototype chain is tricky. Need a new prototype for constructorFunction
  // not the Function constructor prototype
  return thisObject;
}

关闭 (Closures)

Remember the joiner function?

还记得细木工功能吗?

let joiner = function(separator) {    // The outer function defines separator
    return function(left, right) {      
        return left + " " + separator + " " + right;    // The inner function has access to separator
    }    // This exposes the inner function to the outside world
}
let and = joiner("and");
and("red", "green"); // There's no way to change the separator for AND now; except by reassigning the function variable.
// red and green
const or = joiner("or"); // There's no way to change the separator for OR now.
or("black", "white"); 
// black or white

A function defined inside another function has access to the outer function’s variables. Once the outer function returns, common sense would dictate that its local variables no longer exist.

在另一个函数内部定义的函数可以访问外部函数的变量。 一旦外部函数返回,常识将指示其局部变量不再存在。

But they do exist — otherwise, the joiner functions wouldn’t work. What’s more, there are two different “copies” of joiner()’s local variables — one in which separator is and and the other one where separator is or. How does this work?

但是它们确实存在-否则,joiner函数将不起作用。 此外, joiner()的局部变量有两个不同的“副本”,一个是separatorand ,另一个是separatoror 。 这是如何运作的?

范围对象 (Scope Object)

Whenever JavaScript executes a function, it creates a ‘scope’ object to hold the local variables created within that function. The scope object is initialized with variables passed in as function parameters. This is similar to the global object — as new variables “show up”, they are added to the scope object.

每当JavaScript执行函数时,它都会创建一个“作用域”对象来保存在该函数中创建的局部变量。 作用域对象使用作为函数参数传入的变量进行初始化。 这类似于全局对象,因为“显示”新变量,它们被添加到范围对象。

Two key points:

两个关键点:

  • a brand new scope object is created every time a function starts executing

    每当函数开始执行时,都会创建一个全新的作用域对象
  • unlike the global object, these scope objects cannot be directly accessed from your JavaScript code. There is no mechanism for iterating over the properties of the current scope object.

    与全局对象不同,这些作用域对象不能从您JavaScript代码直接访问。 没有机制可以迭代当前作用域对象的属性。

So when joiner() is called, a scope object is created with one property: separator, which is the argument passed to joiner(). joiner() then returns the created function.

因此,在调用joiner()时,将创建一个具有一个属性的范围对象: separator ,这是传递给joiner()的参数。 然后joiner()返回创建的函数。

Normally JavaScript’s garbage collector would clean up the scope object created for joiner() at this point, but the returned function maintains a reference back to that scope object. As a result, the scope object will not be garbage-collected until there are no more references to the function object that joiner() returned.

通常,JavaScript的垃圾回收器会在此时清理为joiner()创建的作用域对象,但是返回的函数维护对该作用域对象的引用。 结果,除非没有更多的joiner()返回的对函数对象的引用,否则不会对范围对象进行垃圾回收。

Scope objects form a chain called the scope chain, similar to the prototype chain.

范围对象形成一个称为范围链的链,类似于原型链。

A closure is the combination of a function and the scope object in which it was created. Closures let you save state — as such, they can often be used in place of objects

闭包是函数和在其中创建它的作用域对象的组合。 闭包让您保存状态-这样,它们通常可以代替对象

Thus, you’re creating a closure whenever you’re creating a function inside another function.

因此,每当在另一个函数中创建一个函数时,便要创建一个闭包。

性能 (Performance)

To end this section, let’s talk a bit about performance. To optimize performance, get rid of closures not needed. Remember, the reference lives till the scope object is needed, containing all local variables and function arguments.

在本节结束时,让我们谈一些性能。 为了优化性能,请消除不需要的闭包。 请记住,引用一直存在到需要范围对象为止,该对象包含所有局部变量和函数参数。

function f(i) {
    var o = { };  // Some large object
    var a = [ ];  // Some large array
    // `a` and `o` are local variables and thus will get added to the closure object.
    //...
    //...
    // some use case for a and o
    var c = [ 1, 2, 3 ].filter(item => a.indexOf(item) > -1 || o[item]);
    a = undefined;  // Clean up before closure
    o = undefined;  // Clean up before closure
    return function () { // closure created
           return ++i; // we didn't need anything except i for this function,
           // so makes sense to delete everything else from the closure.
    };
}

执行模型 (Execution Model)

How does JavaScript run?

JavaScript如何运行?

This gif shows the different components and how they interact together. Let’s go through them.

此gif显示了不同的组件以及它们如何相互作用。 让我们来看看它们。

调用堆栈 (Call Stack)

Each function call is a frame on the stack.

每个函数调用都是堆栈中的一个框架。

This call stack is a stack of function calls to be executed in order. ( You see why it’s called a stack? )

该调用堆栈是要按顺序执行的函数调用的堆栈。 (您知道为什么将其称为堆栈吗?)

The frame contains the function arguments and local variables. This is where the scope object, and hence closure is defined!

该框架包含函数参数和局部变量。 这是作用域对象,因此定义了闭包!

The functions are popped from the stack when they return.

函数返回时会从堆栈中弹出。

Every script begins with a main() on the stack, as the function containing all other functions in the script.

每个脚本都以栈中的main()开头,该函数包含脚本中的所有其他函数。

(Heap)

Every object you create needs a place in memory to live. This place is the heap: A large unstructured region of memory.

您创建的每个对象都需要在内存中保留一个空间。 这个地方就是堆:大的非结构化内存区域。

If you’re coming from C++ land, heap is where things go when constructed using new in C++.

如果您来自C ++领域,那么在C ++中使用new进行构造时,堆就在哪里。

Web API和事件 (Web APIs and Events)

Web APIs are low level functions present in the JavaScript runtime to interact with the OS. They are implemented by the browser / host. For ex: setTimeout().

Web API是JavaScript运行时中存在的用于与OS交互的低级函数。 它们由浏览器/主机实现。 例如: setTimeout()

They are called from the stack and begin processing. The function returns at this point (thus popping the stack frame). This is what gives JavaScript the asynchronous characteristic. Almost all its basic APIs are non-blocking.

从堆栈中调用它们并开始处理。 该函数在此时返回(因此弹出堆栈框架)。 这就是赋予JavaScript异步特性的原因。 几乎所有的基本API都是非阻塞的。

Have a look at the GIF above — and this bit will become clearer.

看看上面的GIF,这一点将变得更加清晰。

These APIs generate a message. This could be an API call to fetch data, in which case the message is the data. This could be setTimeout(), where the message is empty. This could be an event on a DOM button like onClick, where the message is information stored in the button.

这些API会生成一条消息。 这可能是用于fetch数据的API调用,在这种情况下,消息就是数据。 可以是setTimeout() ,其中消息为空。 这可能是DOM按钮(如onClick上的事件,其中消息是按钮中存储的信息。

The APIs send these messages to the callback queue. They have a callback function which is attached to the message. This callback is received from the call stack (something we provide when calling the API).

API将这些消息发送到回调队列。 它们具有附加到消息的回调函数。 该回调是从调用栈(调用API时提供的东西)接收的。

In web browsers, messages are added anytime an event occurs and there is an event listener attached to it. If there is no listener, the event is lost. So a click on an element with a click event handler will add a message — likewise with any other event.

在Web浏览器中,只要事件发生且附加了事件侦听器,便会添加消息。 如果没有侦听器,则事件将丢失。 因此,使用click事件处理程序单击元素将添加一条消息-与其他任何事件一样。

回调队列 (Callback queue)

This is a queue containing all tasks that have finished processing. It has a queue of messages with callback functions for each message.

这是一个队列,其中包含所有已完成处理的任务。 它有一个消息队列,每个消息都有回调函数。

To process a message, the callback function is called with the message as input — but the queue can’t do this, it’s just a message queue. This processing is achieved via the Event Loop.

要处理消息,将以消息作为输入来调用回调函数-但是队列无法做到这一点,它只是消息队列。 此处理是通过事件循环实现的。

Fun-fact: This queue is commonly known as the macrotask queue. There’s a little microtask queue lurking behind too. Not a lot of people know about this — but it comes into play when dealing with Promises. A story for a future article, perhaps? (Wow, JS is huge, isn’t it?)

事实 :此队列通常称为宏任务队列。 后面还有一些微任务队列。 没有多少人知道这一点,但是它在处理Promises时起作用。 可能会在将来的文章中讲一个故事吗? (哇,JS很大,不是吗?)

事件循环 (Event Loop)

To call the callbacks in the callback queue, we need to bring them back on the call stack. That’s the only way a function is called.

要在回调队列中调用回调,我们需要将它们重新带回调用堆栈。 这是调用函数的唯一方法。

The Event Loop handles this bit. It’s a running loop that checks if the call stack is empty on every loop.

事件循环处理此位。 这是一个运行中的循环,用于检查每个循环中调用堆栈是否为空。

Once the call stack is empty, the event loop takes the first element from the callback queue and transfers the callback to the call stack.

一旦调用堆栈为空,事件循环就会从回调队列中获取第一个元素,并将回调传递到调用堆栈。

运行完成 (Run-to-completion)

In the event loop, every message runs to completion. This means, no new message is added to the call stack while the current message is executing.

在事件循环中,每条消息都会运行到完成。 这意味着在执行当前消息时,不会将新消息添加到调用堆栈中。

执行模型刷新器 (Execution Model Refresher)

Alright, we have covered a lot here. Some code follows, but before that I want to make sure things are clear.

好吧,我们在这里介绍了很多内容。 下面是一些代码,但是在此之前,我想确保一切都清楚。

  1. Once you execute a script, the main() function is added to the call stack.

    执行脚本后, main()函数将添加到调用堆栈中。

  2. As functions are called from the script, they are added to the call stack. Popped when returned.

    从脚本调用函数时,会将它们添加到调用堆栈中。 返回时弹出。
  3. The scope objects are added with the functions to the call stack.

    作用域对象随函数一起添加到调用堆栈中。
  4. Some functions may also have a processing component — which is handled by APIs. These APIs return a message and callback.

    某些功能可能还具有处理组件-由API处理。 这些API返回消息和回调。
  5. The messages are added to the callback queue.

    消息将添加到回调队列。
  6. The event loop transfers messages from the callback queue to the call stack only when the call stack is empty ( i.e main() is popped too)

    仅当调用堆栈为空时(即也弹出main() ,事件循环才将消息从回调队列传输到调用堆栈。

  7. Every message runs to completion (direct consequence of new messages being added only when the stack is empty)

    每条消息都会运行到完成(仅当堆栈为空时才添加新消息的直接结果)

With this refresher in mind, let’s apply it. setTimeout( callback, t) is a function (API) as defined above, which takes a callback and adds a message to the callback queue after t seconds.

考虑到这一点,让我们应用它。 setTimeout( callback, t)是上面定义的函数(API),它接受回调并将消息在t秒后添加到回调队列中。

So, what would be the print order below?

那么,下面的打印顺序是什么?

console.log('1');
setTimeout( () => console.log(2), 0) // t = 0;
console.log('3');

..

..

.

If you guessed 1 2 3, let’s go through the example.

如果您猜对了1 2 3 ,我们来看一下这个例子。

Initially, we have main() on the call stack. Then we move through the script.

最初,我们在调用堆栈上具有main() 。 然后我们遍历脚本。

We see console.log(1) — that gets on the call stack, prints 1 and is popped.

我们看到console.log(1) -进入调用堆栈,打印1并弹出。

We see setTimeout() — that goes on the call stack, passes to the Web API and is popped.

我们看到setTimeout() ,它位于调用堆栈上,传递给Web API并被弹出。

At the same time, since the timeout was for 0 seconds, the callback is passed to the callback queue.

同时,由于超时时间为0秒,因此回调将传递到回调队列。

We see console.log(3) — that gets on the call stack, prints 3 and is popped.

我们看到console.log(3) -进入调用堆栈,打印3并弹出。

The script ends, so main() is popped.

脚本结束,因此弹出main()

Now the call stack is empty, so the setTimeout() callback is transferred to the call stack.

现在,调用堆栈为空,因此setTimeout()回调已转移到调用堆栈。

That is, we have () => console.log(2) on the call stack. This is called with the null message.

也就是说,在调用堆栈上有() => console.log (2)。 这就是所谓的与T he n ULL消息。

Hence, the order is 1 3 2.

因此,顺序为1 3 2

This is the zero delay gotcha — a handy idea to remind yourself of how the event loop works.

这是零延迟陷阱-一个方便的想法,可以提醒自己事件循环的工作原理。

This seems like a good place to stop for now. I hope this article has helped you start to get a better understanding of JavaScript! :)

现在看来,这是个不错的地方。 希望本文能帮助您开始更好地了解JavaScript! :)

References:

参考文献:

[1] Reintroduction to Javascript[2] MDN general docs

[1] Java脚本重新介绍 [2] MDN常规文档

Here is Part 2 on my blog.

这是我博客的第2部分

Other stories in this series:

本系列中的其他故事:

How not to be afraid of GIT anymore

如何不再害怕GIT

How not to be afraid of Vim anymore

如何不再害怕Vim

How not to be afraid of Python anymore

如何不再害怕Python

Read more of my articles on neilkakkar.com.

neilkakkar.com上阅读我的更多文章。

翻译自: https://www.freecodecamp.org/news/how-not-to-be-afraid-of-javascript-anymore-c40780dc071/

javascript

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值