javascript面试
by Gustavo Azevedo
通过古斯塔沃·阿兹维多(Gustavo Azevedo)
JavaScript is the most popular programming language and has been since 2014, according to Stack Overflow Survey. It is no wonder that over 1/3rd of all developer jobs require some JavaScript knowledge. So, if you plan to work as a developer in the near future, you should be familiar with this extremely popular language.
根据Stack Overflow Survey的调查 ,JavaScript是自2014年以来最受欢迎的编程语言。 难怪所有开发人员工作的1/3以上都需要一些JavaScript知识。 因此,如果您打算在不久的将来成为一名开发人员,则应该熟悉这种非常流行的语言。
The post’s purpose is to bring together all JavaScript concepts that are frequently brought up in developer interviews. It was written so you can review everything you need to know about JavaScript in a single place.
这篇文章的目的是将开发人员访谈中经常提到的所有JavaScript概念整合在一起。 它的编写使您可以在一个地方查看关于JavaScript所需的所有知识。
There are 7 built-in types: null
, undefined
, boolean
, number
, string
, object
and symbol
(ES6).
有7种内置类型: null
, undefined
, boolean
, number
, string
, object
和symbol
(ES6)。
All of these are types are called primitives, except for object
.
除object
以外,所有这些类型都称为基本object
。
typeof 0 // number
typeof true // boolean
typeof 'Hello' // string
typeof Math // object
typeof null // object !!
typeof Symbol('Hi') // symbol (New ES6)
Null vs. Undefined
空值与未定义
Undefined is the absence of a definition. It is used as the default value for uninitialized variables, function arguments that were not provided and missing properties of objects. Functions return undefined
when nothing has been explicitly returned.
未定义是缺少定义。 它用作未初始化变量,未提供的函数自变量以及缺少对象属性的默认值。 当没有明确返回任何内容时,函数将返回undefined
状态。
Null is the absence of a value. It is an assignment value that can be assigned to a variable as a representation of ‘no-value’.
Null是缺少值。 它是一个分配值,可以将其分配给变量以表示“无值”。
Implicit coercion
内隐强制
Take a look at the following example:
看下面的例子:
var name = 'Joey';
if (name) {
console.log(name + " doesn't share food!") // Joey doesn’t share food!
}
In this case, the string variable name
is coerced to true and you have ‘Joey doesn’t share food!’ printed in our console. But how do you know what will be coerced to true and what will be coerced to false?
在这种情况下,字符串变量name
被强制为true,并且您拥有“ Joey不分享食物!” 打印在我们的控制台中。 但是,您怎么知道什么将被强制为真,什么将被强制为假?
Falsy values are values that will be coerced to false
when forced a boolean coercion on it.
伪造的值是在对其强制执行布尔强制时将被强制强制为false
的值。
Falsy values: ""
, 0
, null
, undefined
, NaN
, false
.
伪造的值: ""
, 0
, null
, undefined
, NaN
, false
。
Anything not explicitly on the falsy list is truthy — boolean coerced to true.
虚假清单上没有明确列出的所有内容都是真实的- 布尔值强制为true 。
Boolean(null) // false
Boolean('hello') // true
Boolean('0') // true
Boolean(' ') // true
Boolean([]) // true
Boolean(function(){}) // true
Yes. You read it right. Empty arrays, objects and functions are boolean coerced to true!
是。 您没看错。 空数组,对象和函数被布尔强制为true!
String & Number coercion
字符串和数字强制
The first thing you need to be aware of is the +
operator. This is a tricky operator because it works for both number addition and string concatenation.
您需要了解的第一件事是+
运算符。 这是一个棘手的运算符,因为它适用于数字加法和字符串连接。
But, the *, / , and -
operators are exclusive for numeric operations. When these operators are used with a string, it forces the string to be coerced to a number.
但是,*,/和-
运算符是数字运算专用的。 当这些运算符与字符串一起使用时,它将强制将字符串强制转换为数字。
1 + "2" = "12"
"" + 1 + 0 = "10"
"" - 1 + 0 = -1
"-9\n" + 5 = "-9\n5"
"-9\n" - 5 = -14
"2" * "3" = 6
4 + 5 + "px" = "9px"
"$" + 4 + 5 = "$45"
"4" - 2 = 2
"4px" - 2 = NaN
null + 1 = 1
undefined + 1 = NaN
== vs. ===
==与===
It is widely spread that ==
checks for equality and ===
checks for equality and type. Well, that is a misconception.
==
检查相等性和===
检查相等性和类型已广为流传。 好吧,这是一个误解。
In fact, == checks for equality with coercion and === checks for equality without coercion — strict equality.
实际上,==检查带有强制的相等性,而===检查没有强制的相等性 - 严格相等 。
2 == '2' // True
2 === '2' // False
undefined == null // True
undefined === null // False
Coercion can be tricky. Take a look at the following code:
强制可能很棘手。 看下面的代码:
What would you expect for the following comparison?console.log(a == b);
(1)
您希望进行以下比较吗? console.log(a == b);
(1)
This comparison actually returns True. Why?What really happens under the hood is that if you are comparing a boolean
with something other than a boolean
, JavaScript coerces that boolean
to a number
and compares. (2)
该比较实际上返回True。 为什么呢?实际上,如果您将boolean
与boolean
以外的其他boolean
进行比较,则JavaScript强制将boolean
转换为number
并进行比较。 (2)
This comparison is now between a number
and a string
. JavaScript now coerces that string
to a number
and compares both numbers. (3)
现在,此比较是在number
和string
。 JavaScript现在将string
强制转换为number
并比较两个数字。 (3)
In this case, the final comparison 0 == 0
is True.
在这种情况下,最终比较0 == 0
为True。
'0' == false (1)
'0' == 0 (2)
0 == 0 (3)
For a fully comprehension on how such comparisons are performed, you can check ES5 documentation here.
要全面了解如何进行此类比较,可以在此处查看ES5文档。
For a cheat sheet, you can click here.
有关备忘单,请单击此处 。
Some tricky comparisons to look out for:
需要进行一些棘手的比较:
false == "" // true
false == [] // true
false == {} // false
"" == 0 // true
"" == [] // true
"" == {} // false
0 == [] // true
0 == {} // false
0 == null // false
Simple values (also known as primitives) are always assigned by value-copy: null
, undefined
, boolean
, number
, string
and ES6 symbol
.
简单值(也称为基元)始终由value-copy分配: null
, undefined
, boolean
, number
, string
和ES6 symbol
。
Compound values always create a copy of the reference on assignment: objects, which includes arrays, and functions.
复合值总是在赋值对象(包括数组和函数)上创建引用的副本。
var a = 2; // 'a' hold a copy of the value 2.
var b = a; // 'b' is always a copy of the value in 'a'
b++;
console.log(a); // 2
console.log(b); // 3
var c = [1,2,3];
var d = c; // 'd' is a reference to the shared value
d.push( 4 ); // Mutates the referenced value (object)
console.log(c); // [1,2,3,4]
console.log(d); // [1,2,3,4]
/* Compound values are equal by reference */
var e = [1,2,3,4];
console.log(c === d); // true
console.log(c === e); // false
To copy a compound value by value, you need to make a copy of it. The reference does not point to the original value.
要通过值复制一个复合值,你需要 它的副本。 该引用未指向原始值。
Scope refers to the execution context. It defines the accessibility of variables and functions in the code.
范围是指执行上下文。 它定义了代码中变量和函数的可访问性。
Global Scope is the outermost scope. Variables declared outside a function are in the global scope and can be accessed in any other scope. In a browser, the window object is the global scope.
全局范围是最外部的范围。 在函数外部声明的变量在全局范围内,并且可以在任何其他范围内访问。 在浏览器中,窗口对象是全局范围。
Local Scope is a scope nested inside another function scope. Variables declared in a local scope are accessible within this scope as well as in any inner scopes.
局部作用域是嵌套在另一个函数作用域内的作用域。 在本地范围内声明的变量可以在此范围内以及任何内部范围内访问。
function outer() {
let a = 1;
function inner() {
let b = 2;
function innermost() {
let c = 3;
console.log(a, b, c); // 1 2 3
}
innermost();
console.log(a, b); // 1 2 — 'c' is not defined
}
inner();
console.log(a); // 1 — 'b' and 'c' are not defined
}
outer();
You may think of Scopes as a series of doors decreasing in size (from biggest to smallest). A short person that fits through the smallest door — innermost scope — also fits through any bigger doors — outer scopes.
您可能会想到“作用域”是一系列尺寸不断减小(从最大到最小)的门。 矮个子适合最小的门( 最里面的范围) ,也适合任何更大的门( 外部的范围) 。
A tall person that gets stuck on the third door, for example, will have access to all previous doors — outer scopes — but not any further doors — inner scopes.
例如,一个高个子的人被卡在第三扇门上,将可以使用以前的所有门- 外部观察镜 -但不能再接触其他任何门- 内部观察镜 。
The behavior of “moving” var
and function
declarations to the top of their respective scopes during the compilation phase is called hoisting.
在编译阶段将var
和function
声明“移动”到它们各自作用域的顶部的行为称为提升 。
Function declarations are completely hoisted. This means that a declared function can be called before it is defined.
函数声明已完全悬挂。 这意味着可以在定义函数之前调用已声明的函数。
console.log(toSquare(3)); // 9
function toSquare(n){
return n*n;
}
Variables are partially hoisted. var
declarations are hoisted but not its assignments.
变量已部分提升。 悬挂了var
声明,但未悬挂其赋值。
let
and const
are not hoisted.
let
和const
不被吊起。
{ /* Original code */
console.log(i); // undefined
var i = 10
console.log(i); // 10
}
{ /* Compilation phase */
var i;
console.log(i); // undefined
i = 10
console.log(i); // 10
}
// ES6 let & const
{
console.log(i); // ReferenceError: i is not defined
const i = 10
console.log(i); // 10
}
{
console.log(i); // ReferenceError: i is not defined
let i = 10
console.log(i); // 10
}
Function Expression
函数表达式
A Function Expression is created when the execution reaches it and is usable from then on — it is not hoisted.
函数表达式会在执行到达时创建,并且从那时起就可以使用-它不会被吊起。
var sum = function(a, b) {
return a + b;
}
Function Declaration
功能声明
A Function Declaration can be called both before and after it was defined — it is hoisted.
函数声明可以在定义之前和之后都被调用-它被吊起。
function sum(a, b) {
return a + b;
}
Before ES6, it was only possible to declare a variable using var
. Variables and functions declared inside another function cannot be accessed by any of the enclosing scopes — they are function-scoped.
在ES6之前,只能使用var
声明变量。 在任何封闭的作用域中都无法访问在另一个函数内部声明的变量和函数,它们是函数作用域的。
Variables declared inside a block-scope, such as if
statements and for
loops, can be accessed from outside of the opening and closing curly braces of the block.
可以从块的开头和结尾花括号之外访问在块范围内声明的变量,例如if
语句和for
循环。
Note: An undeclared variable — assignment without var
, let
or const
— creates a var
variable in global scope.
注意 :未声明的变量(不带var
, let
或const
赋值)会在全局范围内创建var
变量。
function greeting() {
console.log(s) // undefined
if(true) {
var s = 'Hi';
undeclaredVar = 'I am automatically created in global scope';
}
console.log(s) // 'Hi'
}
console.log(s); // Error — ReferenceError: s is not defined
greeting();
console.log(undeclaredVar) // 'I am automatically created in global scope'
ES6 let
and const
are new. They are not hoisted and block-scoped alternatives for variable declaration. This means that a pair of curly braces define a scope in which variables declared with either let or const are confined in.
ES6 let
和const
是新的。 它们不是变量声明的悬挂式和块作用域替代方案。 这意味着一对花括号定义了一个范围,其中用let或const声明的变量被限制在其中。
let g1 = 'global 1'
let g2 = 'global 2'
{ /* Creating a new block scope */
g1 = 'new global 1'
let g2 = 'local global 2'
console.log(g1) // 'new global 1'
console.log(g2) // 'local global 2'
console.log(g3) // ReferenceError: g3 is not defined
let g3 = 'I am not hoisted';
}
console.log(g1) // 'new global 1'
console.log(g2) // 'global 2'
A common misconception is that const
is immutable. It cannot be reassigned, but its properties can be changed!
一个常见的误解是const
是不可变的。 不能重新分配它,但是可以更改其属性!
const tryMe = 'initial assignment';
tryMe = 'this has been reassigned'; // TypeError: Assignment to constant variable.
// You cannot reassign but you can change it…
const array = ['Ted', 'is', 'awesome!'];
array[0] = 'Barney';
array[3] = 'Suit up!';
console.log(array); // [“Barney”, “is”, “awesome!”, “Suit up!”]
const airplane = {};
airplane.wings = 2;
airplane.passengers = 200;
console.log(airplane); // {passengers: 200, wings: 2}
A closure is the combination of a function and the lexical environment from which it was declared. Closure allows a function to access variables from an enclosing scope — environment — even after it leaves the scope in which it was declared.
闭包是函数和声明它的词法环境的组合。 闭包允许函数从封闭的范围( 环境)访问变量,即使该函数离开声明它的范围也是如此。
function sayHi(name){
var message = `Hi ${name}!`;
function greeting() {
console.log(message)
}
return greeting
}
var sayHiToJon = sayHi('Jon');
console.log(sayHiToJon) // ƒ() { console.log(message) }
console.log(sayHiToJon()) // 'Hi Jon!'
The above example covers the two things you need to know about closures:
上面的示例涵盖了关于闭包需要了解的两件事:
Refers to variables in outer scope.
指外部作用域中的变量。
The returned function access the
返回的函数访问
message
variable from the enclosing scope.
来自封闭范围的message
变量。
It can refer to outer scope variables even after the outer function has returned.
即使在外部函数返回之后,它也可以引用外部范围变量。
It can refer to outer scope variables even after the outer function has returned. sayHiToJon
is a reference to the greeting
function, created when sayHi
was run. The greeting
function maintains a reference to its outer scope — environment — in which message
exists.
即使在外部函数返回之后,它也可以引用外部范围变量。 sayHiToJon
是对greeting
功能的引用,该greeting
功能是在运行sayHi
时创建的。 greeting
函数保留对其外部范围( 环境)的引用 —其中存在message
。
One of the main benefits of closures is that it allows data encapsulation. This refers to the idea that some data should not be directly exposed. The following example illustrates that.
闭包的主要好处之一是它允许数据封装 。 这是指不应直接公开某些数据的想法。 以下示例说明了这一点。
By the time elementary
is created, the outer function has already returned. This means that the staff
variable only exists inside the closure and it cannot be accessed otherwise.
在elementary
创建时,外部函数已经返回。 这意味着staff
变量仅存在于闭包内部,否则无法访问。
function SpringfieldSchool() {
let staff = ['Seymour Skinner', 'Edna Krabappel'];
return {
getStaff: function() { console.log(staff) },
addStaff: function(name) { staff.push(name) }
}
}
let elementary = SpringfieldSchool()
console.log(elementary) // { getStaff: ƒ, addStaff: ƒ }
console.log(staff) // ReferenceError: staff is not defined
/* Closure allows access to the staff variable */
elementary.getStaff() // ["Seymour Skinner", "Edna Krabappel"]
elementary.addStaff('Otto Mann')
elementary.getStaff() // ["Seymour Skinner", "Edna Krabappel", "Otto Mann"]
Let’s go deeper into closures by solving one of the most common interview problems on this subject:What is wrong with the following code and how would you fix it?
让我们通过解决有关此主题的最常见面试问题之一来更深入地了解闭包:以下代码有什么问题,您将如何解决?
const arr = [10, 12, 15, 21];
for (var i = 0; i < arr.length; i++) {
setTimeout(function() {
console.log(`The value ${arr[i]} is at index: ${i}`);
}, (i+1) * 1000);
}
Considering the above code, the console will display four identical messages "The value undefined is at index: 4"
. This happens because each function executed within the loop will be executed after the whole loop has completed, referencing to the last value stored in i
, which was 4.
考虑以上代码,控制台将显示四个相同的消息"The value undefined is at index: 4"
。 发生这种情况是因为循环中执行的每个函数都将在整个循环完成后执行,并参考i
存储的最后一个值( i
4)。
This problem can be solved by using IIFE, which creates a unique scope for each iteration and storing each value within its scope.
可以通过使用IIFE解决此问题,该IIFE为每次迭代创建一个唯一的范围并将每个值存储在其范围内。
const arr = [10, 12, 15, 21];
for (var i = 0; i < arr.length; i++) {
(function(j) {
setTimeout(function() {
console.log(`The value ${arr[j]} is at index: ${j}`);
}, j * 1000);
})(i)
}
Another solution would be declaring the i
variable with let
, which creates the same result.
另一种解决方案是使用let
声明i
变量,从而创建相同的结果。
const arr = [10, 12, 15, 21];
for (let i = 0; i < arr.length; i++) {
setTimeout(function() {
console.log(`The value ${arr[i]} is at index: ${i}`);
}, (i) * 1000);
}
An IIFE is a function expression that is called immediately after you define it. It is usually used when you want to create a new variable scope.
IIFE是定义后立即调用的函数表达式。 通常在要创建新的变量作用域时使用。
The (surrounding parenthesis) prevents from treating it as a function declaration.
(左括号)防止将其视为函数声明。
The final parenthesis() are executing the function expression.
最后的括号()正在执行函数表达式。
On IIFE you are calling the function exactly when you are defining it.
在IIFE上,您在定义函数时正好在调用该函数。
var result = [];
for (var i=0; i < 5; i++) {
result.push( function() { return i } );
}
console.log( result[1]() ); // 5
console.log( result[3]() ); // 5
result = [];
for (var i=0; i < 5; i++) {
(function () {
var j = i; // copy current value of i
result.push( function() { return j } );
})();
}
console.log( result[1]() ); // 1
console.log( result[3]() ); // 3
Using IIFE:
使用IIFE:
Context is often confused as the same thing as Scope. To clear things up, lets keep the following in mind:Context is most often determined by how a function is invoked. It always refers to the value of this
in a particular part of your code.Scope refers to the visibility of variables.
上下文通常与Scope混淆。 为了使事情变得清晰,请牢记以下几点: 上下文通常由函数的调用方式决定。 它总是指的价值this
在代码中的特定部分。 范围是指变量的可见性。
All of these three methods are used to attach this
into function and the difference is in the function invocation.
所有这三种方法都用于附加this
转换为函数,区别在于函数调用。
.call()
invokes the function immediately and requires you to pass in arguments as a list (one by one).
.call()
立即调用该函数,并要求您将参数作为列表传递(一个一个地传递)。
.apply()
invokes the function immediately and allows you to pass in arguments as an array.
.apply()
立即调用该函数,并允许您将参数作为数组传递。
.call()
and .apply()
are mostly equivalent and are used to borrow a method from an object. Choosing which one to use depends on which one is easier to pass the arguments in. Just decide whether it’s easier to pass in an array or a comma separated list of arguments.
.apply()
.call()
和.apply()
大部分是等效的,用于从对象中借用方法。 选择使用哪个取决于哪个更容易传递参数。只需确定是更容易传递数组还是以逗号分隔的参数列表即可。
Quick tip: Apply for Array — Call for Comma.
快速提示: 一个 pply对于A rray - C中的所有对C OMMA。
const Snow = {surename: 'Snow'}
const char = {
surename: 'Stark',
knows: function(arg, name) {
console.log(`You know ${arg}, ${name} ${this.surename}`);
}
}
char.knows('something', 'Bran'); // You know something, Bran Stark
char.knows.call(Snow, 'nothing', 'Jon'); // You know nothing, Jon Snow
char.knows.apply(Snow, ['nothing', 'Jon']); // You know nothing, Jon Snow
Note: If you pass in an array as one of the arguments on a call function, it will treat that entire array as a single element. ES6 allows us to spread an array as arguments with the call function.
注意 :如果将数组作为调用函数的参数之一传递,它将把整个数组视为单个元素。 ES6允许我们使用call函数将数组作为参数传播。
char.knows.call(Snow, ...["nothing", "Jon"]); // You know nothing, Jon Snow
.bind()
returns a new function, with a certain context and parameters. It is usually used when you want a function to be called later with a certain context.
.bind()
返回具有特定上下文和参数的新函数。 通常,当您希望稍后在特定上下文中调用函数时,可以使用它。
That is possible thanks to its ability to maintain a given context for calling the original function. This is useful for asynchronous callbacks and events.
由于它具有维护给定上下文以调用原始函数的能力,因此这是可能的。 这对于异步回调和事件很有用。
.bind()
works like the call function. It requires you to pass in the arguments one by one separated by a comma.
.bind()
作用类似于调用函数。 它要求您以逗号分隔的参数一一传递。
const Snow = {surename: 'Snow'}
const char = {
surename: 'Stark',
knows: function(arg, name) {
console.log(`You know ${arg}, ${name} ${this.surename}`);}
}
const whoKnowsNothing = char.knows.bind(Snow, 'nothing');
whoKnowsNothing('Jon'); // You know nothing, Jon Snow
Understanding the keyword this
in JavaScript, and what it is referring to, can be quite complicated at times.
了解关键字this
JavaScript中的内容及其所指有时可能会非常复杂。
The value of this
is usually determined by a functions execution context. Execution context simply means how a function is called.
值this
通常由函数执行上下文决定。 执行上下文仅表示如何调用函数。
The keyword this
acts as a placeholder, and will refer to whichever object called that method when the method is actually used.
关键字this
充当占位符,并在实际使用该方法时引用该方法所引用的任何对象。
The following list is the ordered rules for determining this. Stop at the first one that applies:
以下列表是确定此顺序的有序规则。 在第一个适用的位置停止:
new
binding — When using the new
keyword to call a function, this
is the newly constructed object.
new
绑定 —使用new
关键字调用函数时, this
是新构造的对象。
function Person(name, age) {
this.name = name;
this.age =age;
console.log(this);
}
const Rachel = new Person('Rachel', 30); // { age: 30, name: 'Rachel' }
Explicit binding — When call or apply are used to call a function, this
is the object that is passed in as the argument.
显式绑定 -当使用call或apply来调用函数时, this
是作为参数传入的对象。
Explicit binding — When call or apply are used to call a function, this
is the object that is passed in as the argument.Note: .bind()
works a little bit differently. It creates a new function that will call the original one with the object that was bound to it.
显式绑定 -当使用call或apply调用函数时, this
是作为参数传入的对象。 注意 : .bind()
工作方式略有不同。 它创建了一个新函数,该函数将使用绑定到其上的对象调用原始函数。
function fn() {
console.log(this);
}
var agent = {id: '007'};
fn.call(agent); // { id: '007' }
fn.apply(agent); // { id: '007' }
var boundFn = fn.bind(agent);
boundFn(); // { id: '007' }
Implicit binding — When a function is called with a context (the containing object), this
is the object that the function is a property of.
隐式绑定 —当使用上下文(包含对象)调用函数时, this
是该函数是其属性的对象。
This means that a function is being called as a method.
这意味着一个函数被称为方法。
var building = {
floors: 5,
printThis: function() {
console.log(this);
}
}
building.printThis(); // { floors: 5, printThis: function() {…} }
Default binding — If none of the above rules applies, this
is the global object (in a browser, it’s the window object).
默认绑定 —如果以上规则均不适用,则this
是全局对象(在浏览器中是窗口对象)。
This happens when a function is called as a standalone function.
当一个函数被称为独立函数时,就会发生这种情况。
A function that is not declared as a method automatically becomes a property of the global object.
未声明为方法的函数将自动成为全局对象的属性。
function printWindow() {
console.log(this)
}
printWindow(); // window object
Note: This also happens when a standalone function is called from within an outer function scope.
注意 :当从外部函数范围内调用独立函数时,也会发生这种情况。
function Dinosaur(name) {
this.name = name;
var self = this;
inner();
function inner() {
alert(this); // window object — the function has overwritten the 'this' context
console.log(self); // {name: 'Dino'} — referencing the stored value from the outer context
}
}
var myDinosaur = new Dinosaur('Dino');
Lexical this — When a function is called with an arrow function =>
, this
receives the this
value of its surrounding scope at the time it’s created. this
keeps the value from its original context.
Lexical this —用箭头函数=>
调用函数时, this
函数在创建时会收到其周围范围的this
值。 this
使值保持其原始上下文。
function Cat(name) {
this.name = name;
console.log(this); // { name: 'Garfield' }
( () => console.log(this) )(); // { name: 'Garfield' }
}
var myCat = new Cat('Garfield');
JavaScript is executed in strict mode by using the “use strict”
directive. Strict mode tightens the rules for parsing and error handling on your code.
通过使用“use strict”
指令以严格模式执行JavaScript。 严格模式加强了代码解析和错误处理的规则。
Some of its benefits are:
它的一些好处是:
Makes debugging easier — Code errors that would otherwise have been ignored will now generate errors, such as assigning to non-writable global or property.
使调试更加容易 -原本可以被忽略的代码错误现在将生成错误,例如分配给不可写的全局或属性。
Prevents accidental global variables — Assigning a value to an undeclared variable will now throw an error.
防止意外的全局变量 -将值分配给未声明的变量现在将引发错误。
Prevents invalid use of delete — Attempts to delete variables, functions and undeletable properties will now throw an error.
防止无效使用delete —现在尝试删除变量,函数和不可删除的属性将引发错误。
Prevents duplicate property names or parameter values — Duplicated named property in an object or argument in a function will now throw an error. (This is no longer the case in ES6)
防止重复的属性名称或参数值 -对象或函数中的参数中重复的命名属性现在将引发错误。 (ES6中不再是这种情况)
Makes eval() safer — Variables and functions declared inside an eval()
statement are not created in the surrounding scope.
使eval()更安全 —在eval()
语句内声明的变量和函数不在周围的范围内创建。
“Secures” JavaScript eliminating this coercion — Referencing a this
value of null or undefined is not coerced to the global object. This means that in browsers it’s no longer possible to reference the window object using this
inside a function.
“保护” JavaScript消除了这种强制性 -引用this
值为null或undefined不会强制到全局对象。 这意味着在浏览器中,不再可以在函数内部使用this
对象引用窗口对象。
The new
keyword invokes a function in a special way. Functions invoked using the new
keyword are called constructor functions.
new
关键字以特殊方式调用功能。 使用new
关键字调用的函数称为构造函数 。
So what does the new
keyword actually do?
那么, new
关键字实际上是做什么的呢?
Sets the object’s prototype to be the prototype of the constructor function.
设置对象的原型 成为原型 构造函数的名称 。
Executes the constructor function with this
as the newly created object.
将this
作为新创建的对象执行构造函数。
// In order to better understand what happens under the hood, lets build the new keyword
function myNew(constructor, ...arguments) {
var obj = {}
Object.setPrototypeOf(obj, constructor.prototype);
return constructor.apply(obj, arguments) || obj
}
What is the difference between invoking a function with the new
keyword and without it?
使用new
关键字和不使用new
关键字调用函数之间有什么区别?
function Bird() {
this.wings = 2;
}
/* invoking as a normal function */
let fakeBird = Bird();
console.log(fakeBird); // undefined
/* invoking as a constructor function */
let realBird= new Bird();
console.log(realBird) // { wings: 2 }
Prototype is one of the most confusing concepts in JavaScript and one of the reason for that is because there are two different contexts in which the word prototype is used.
原型是JavaScript中最令人困惑的概念之一,其原因之一是因为在两个不同的上下文中使用了原型一词。
Prototype relationship
原型关系
Each object has a
每个对象都有一个
prototype object, from which it inherits all of its prototype’s properties.
原型 对象,从该对象继承其原型的所有属性。
prototype object, from which it inherits all of its prototype’s properties..__proto__
is a non-standard mechanism (available in ES6) for retrieving the prototype of an object (*). It points to the object’s “parent” — the object’s prototype.
原型 对象,从该对象继承其原型的所有属性。 .__proto__
是用于检索原型的非标准机制(ES6中可用) 对象(*)的大小 。 它指向对象的“父母”- 对象的原型 。
All normal objects also inherit a
所有普通对象也都继承了
.constructor
property that points to the constructor of the object. Whenever an object is created from a constructor function, the .__proto__
property links that object to the .prototype
property of the constructor function used to create it.
指向对象构造函数的.constructor
属性。 每当从构造函数创建对象时, .__proto__
属性都会将该对象链接到用于创建该对象的构造函数的.prototype
属性。
.constructor
property that points to the constructor of the object. Whenever an object is created from a constructor function, the .__proto__
property links that object to the .prototype
property of the constructor function used to create it.(*) Object.getPrototypeOf()
is the standard ES5 function for retrieving the prototype of an object.
指向对象构造函数的.constructor
属性。 每当从构造函数创建对象时, .__proto__
属性都会将该对象链接到用于创建该对象的构造函数的.prototype
属性。 (*) Object.getPrototypeOf()
是用于检索对象原型的标准ES5函数。
Prototype property
原型属性
Every function has a
每个功能都有一个
.prototype
property.
.prototype
属性。
It references to an object used to attach properties that will be inherited by objects further down the prototype chain. This object contains, by default, a
它引用了一个对象,该对象用于附加属性,这些属性将被原型链中更远的对象继承。 默认情况下,此对象包含
.constructor
property that points to the original constructor function.
指向原始构造函数的.constructor
属性。
Every object created with a constructor function inherits a constructor property that points back to that function.
使用构造函数创建的每个对象都继承一个指向该函数的构造函数属性。
function Dog(breed, name){
this.breed = breed,
this.name = name
}
Dog.prototype.describe = function() {
console.log(`${this.name} is a ${this.breed}`)
}
const rusty = new Dog('Beagle', 'Rusty');
/* .prototype property points to an object which has constructor and attached
properties to be inherited by objects created by this constructor. */
console.log(Dog.prototype) // { describe: ƒ , constructor: ƒ }
/* Object created from Dog constructor function */
console.log(rusty) // { breed: "Beagle", name: "Rusty" }
/* Object inherited properties from constructor function's prototype */
console.log(rusty.describe()) // "Rusty is a Beagle"
/* .__proto__ property points to the .prototype property of the constructor function */
console.log(rusty.__proto__) // { describe: ƒ , constructor: ƒ }
/* .constructor property points to the constructor of the object */
console.log(rusty.constructor) // ƒ Dog(breed, name) { ... }
The prototype chain is a series of links between objects that reference one another.
原型链是相互引用的对象之间的一系列链接。
When looking for a property in an object, JavaScript engine will first try to access that property on the object itself.
在对象中查找属性时,JavaScript引擎将首先尝试在对象本身上访问该属性。
If it is not found, the JavaScript engine will look for that property on the object it inherited its properties from — the object’s prototype.
如果未找到,JavaScript引擎将在继承其属性的对象(对象的prototype)上寻找该属性。
The engine will traverse up the chain looking for that property and return the first one it finds.
引擎将遍历链寻找该属性,并返回它找到的第一个属性。
The last object in the chain is the built-in Object.prototype
, which has null
as its prototype. Once the engine reaches this object, it returns undefined
.
链中的最后一个对象是内置的Object.prototype
,其原型为null
。 引擎到达此对象后,它将返回undefined
。
Objects have own properties and inherited properties.
对象具有自己的属性和继承的属性。
Own properties are properties that were defined on the object.
自己的属性是在对象上定义的属性。
Inherited properties were inherited through prototype chain.
继承的属性是通过原型链继承的。
function Car() { }
Car.prototype.wheels = 4;
Car.prototype.airbags = 1;
var myCar = new Car();
myCar.color = 'black';
/* Check for Property including Prototype Chain: */
console.log('airbags' in myCar) // true
console.log(myCar.wheels) // 4
console.log(myCar.year) // undefined
/* Check for Own Property: */
console.log(myCar.hasOwnProperty('airbags')) // false — Inherited
console.log(myCar.hasOwnProperty('color')) // true
Object.create(obj) — Creates a new object with the specified prototype object and properties.
Object.create( obj ) —使用指定的原型创建一个新对象 对象和属性。
var dog = { legs: 4 };
var myDog = Object.create(dog);
console.log(myDog.hasOwnProperty('legs')) // false
console.log(myDog.legs) // 4
console.log(myDog.__proto__ === dog) // true
An inherited property is a copy by reference of the prototype object’s property from which it inherited that property.
继承的属性是通过引用原型对象的 从中继承该属性的属性。
If an object’s property is mutated on the prototype, objects which inherited that property will share the same mutation. But if the property is replaced, the change will not be shared.
如果对象的属性在原型上发生了变异,则继承该属性的对象将共享相同的变异。 但是,如果替换了该属性,则更改将不会共享。
var objProt = { text: 'original' };
var objAttachedToProt = Object.create(objProt);
console.log(objAttachedToProt.text) // original
objProt.text = 'prototype property changed';
console.log(objAttachedToProt.text) // prototype property changed
objProt = { text: 'replacing property' };
console.log(objAttachedToProt.text) // prototype property changed
In classical inheritance, objects inherit from classes — like a blueprint or a description of the object to be created — and create sub-class relationships. These objects are created via constructor functions using the new keyword.
在经典继承中,对象从类继承(例如,蓝图或要创建的对象的描述),并创建子类关系。 这些对象是通过构造函数使用new关键字创建的。
The downside of classical inheritance is that it causes:
古典继承的弊端在于:
And the so famous gorilla/banana problem — “What you wanted was a banana, what you got was a gorilla holding the banana, and the entire jungle.”
还有一个著名的大猩猩/香蕉问题- “您想要的是一根香蕉,得到的是一只盛有香蕉的大猩猩,以及整个丛林。”
In prototypal inheritance, objects inherit directly from other objects. Objects are typically created via Object.create()
, object literals or factory functions.
在原型继承中,对象直接从其他对象继承。 对象通常是通过Object.create()
,对象文字或工厂函数创建的。
There are three different kinds of prototypal inheritance:
原型继承有三种不同类型:
Prototype delegation — A delegate prototype is an object which is used as a model for another object. When you inherit from a delegate prototype, the new object gets a reference to the prototype and its properties.
原型委托 -委托原型是一个对象,用作另一个对象的模型。 当您从委托原型继承时,新对象将获得对该原型及其属性的引用。
This process is usually accomplished by using
此过程通常通过使用
Object.create()
.
Object.create()
。
Concatenative inheritance — The process of inheriting properties from one object to another by copying the object’s prototype properties, without retaining a reference between them.
串联继承 -通过复制对象的原型属性而不在它们之间保留引用的方式,将属性从一个对象继承到另一个对象。
This process is usually accomplished by using
此过程通常通过使用
Object.assign()
.
Object.assign()
。
Functional inheritance — This process makes use of a factory function(*) to create an object, and then adds new properties directly to the created object.
函数继承 —此过程利用工厂函数(*)创建对象,然后将新属性直接添加到创建的对象。
This process has the benefit of allowing data encapsulation via closure.
此过程的好处是允许通过闭包进行数据封装。
(*)Factory function is a function that is not a class or constructor that returns an object without using the new
keyword.
(*)Factory函数是一个不是类或构造函数的函数,它不使用new
关键字就返回对象。
const person = function(name) {
const message = `Hello! My name is ${name}`;
return { greeting: () => console.log(message) }
}
const will = person("Will");
will.greeting(); // Hello! My name is Will
You can find a complete article on this topic by Eric Elliott here.
您可以在此处找到Eric Elliott撰写的有关此主题的完整文章。
Many developers agree that class inheritance should be avoided in most cases. In this pattern you design your types regarding what they are, which makes it a very strict pattern.
许多开发人员同意在大多数情况下应避免类继承。 在这个模式中你设计有关它们是什么,这使得它非常严格的模式你的类型。
Composition, on the other hand, you design your types regarding what they do, which makes it more flexible and reusable.
组成,在另一方面,你设计对于他们在做什么,这使得它更灵活的和可重复使用的类型。
Here is a nice video on this topic by Mattias Petter Johansson
这是Mattias Petter Johansson在这个主题上的精彩视频
JavaScript is a single-threaded programming language. This means that the JavaScript engine can only process a piece of code at a time. One of its main consequences is that when JavaScript encounters a piece of code that takes a long time to process, it will block all code after that from running.
JavaScript是一种单线程编程语言。 这意味着JavaScript引擎一次只能处理一段代码。 它的主要后果之一是,当JavaScript遇到一段处理时间很长的代码时,它将阻止运行该代码之后的所有代码。
JavaScript uses a data structure that stores information about active functions named Call Stack. A Call Stack is like a pile of books. Every book that goes into that pile sits on top of the previous book. The last book to go into the pile will be the first one removed from it, and the first book added to the pile will be the last one removed.
JavaScript使用一种数据结构,该结构存储有关名为Call Stack的活动函数的信息。 调用堆栈就像一堆书。 每本书都放在前一本书的顶部。 进入堆中的最后一本书将是从中删除的第一本书,添加到堆中的第一本书将是被删除的最后一本书。
The solution to executing heavy pieces of code without blocking anything is asynchronous callback functions. These functions are executed later — asynchronously.
执行大量代码而不阻塞任何内容的解决方案是异步回调函数 。 这些函数稍后会以异步方式执行。
The asynchronous process begins with an asynchronous callback functions placed into a Heap or region of memory. You can think of the Heap as an Event Manager. The Call Stack asks the Event Manager to execute a specific function only when a certain event happens. Once that event happens, the Event Manager moves the function to the Callback Queue. Note: When the Event Manager handles a function, the code after that is not blocked and JavaScript continues its execution.
异步过程始于放置在堆或内存区域中的异步回调函数。 您可以将Heap视为Event Manager 。 调用堆栈仅在特定事件发生时才要求事件管理器执行特定功能。 一旦该事件发生,事件管理器就会将该功能移至“回调队列”。 注意 :当事件管理器处理函数时,此后的代码不会被阻塞,JavaScript会继续执行。
The Event Loop handles the execution of multiple pieces of your code over time. The Event Loop monitors the Call Stack and the Callback Queue.
事件循环会随着时间的推移处理多段代码的执行。 事件循环监视呼叫堆栈和回调队列。
The Call Stack is constantly checked whether it is empty or not. When it is empty, the Callback Queue is checked if there is a function waiting to be invoked. When there is a function waiting, the first function in the queue is pushed into the Call Stack, which will run it. This checking process is called a ‘tick’ in the Event Loop.
不断检查呼叫堆栈是否为空。 如果为空,则检查“回调队列”是否存在等待调用的函数。 当有一个函数在等待时,队列中的第一个函数被推入调用栈,它将运行它。 此检查过程在事件循环中称为“滴答”。
Let’s break down the execution of the following code to understand how this process works:
让我们分解以下代码的执行,以了解此过程的工作方式:
const first = function () {
console.log('First message')
}
const second = function () {
console.log('Second message')
}
const third = function() {
console.log('Third message')
}
first();
setTimeout(second, 0);
third();
// Output:
// First message
// Third message
// Second message
first()
is added to the Call Stack.
first()
添加到调用堆栈中。
console.log("First message")
is added to the Call Stack.
console.log("First message")
已添加到调用堆栈中。
console.log("First message")
is executed and the Browser console displays “First message”.
console.log("First message")
被执行,并且浏览器控制台显示“ First message” 。
console.log("First message")
is removed from the Call Stack.
console.log("First message")
已从调用堆栈中删除。
first()
is removed from the Call Stack.
first()
从调用堆栈中删除。
setTimeout(second, 0)
is added to the Call Stack.
setTimeout(second, 0)
被添加到调用堆栈中。
setTimeout(second, 0)
is executed and handled by the Event Manager. And after 0ms the Event Manager moves second()
to the Callback Queue.
setTimeout(second, 0)
由事件管理器执行和处理。 在0毫秒后,事件管理器将second()
移至回调队列。
setTimeout(second, 0)
is now completed and removed from the Call Stack.
setTimeout(second, 0)
现在已完成,并已从调用堆栈中删除。
third()
is added to the Call Stack.
third()
被添加到调用堆栈中。
console.log("Third message")
is added to the Call Stack.
console.log("Third message")
被添加到调用堆栈中。
console.log("Third message")
is executed and the Browser console displays “Third message”.
console.log("Third message")
,浏览器控制台显示“第三条消息” 。
console.log("Third message")
is removed from the Call Stack.
console.log("Third message")
已从调用堆栈中删除。
third()
is removed from the Call Stack.
third()
从调用堆栈中删除。
Call Stack is now empty and the second()
function is waiting to be invoked in the Callback Queue.
现在,调用堆栈为空,并且second()
函数正在等待在回调队列中被调用。
The Event Loop moves second()
from the Callback Queue to the Call Stack.
事件循环将second()
从回调队列移至调用堆栈。
console.log("Second message")
is added to the Call Stack.
console.log("Second message")
被添加到调用堆栈中。
console.log("Second message")
is executed and the Browser console displays “Second message”.
console.log("Second message")
被执行,浏览器控制台显示“第二条消息” 。
console.log("Second message")
is removed from the Call Stack.
console.log("Second message")
已从调用堆栈中删除。
second()
is removed from the Call Stack.
second()
从调用堆栈中删除。
Note: The second()
function is not executed after 0ms. The time you pass in to setTimeout
function does not relate to the delay of its execution. The Event Manager will wait the given time before moving that function into the Callback Queue. Its execution will only take place on a future ‘tick’ in the Event Loop.
注意 : second()
函数不会在0ms之后执行。 你在打发时间 setTimeout
函数不涉及到其执行的延迟。 活动管理器将等待给定的时间 在将该功能移入“回调队列”之前。 它的执行将仅在事件循环中将来的“滴答”中进行。
Thanks and congratulations for reading up to this point! If you have any thoughts on this, feel free to leave a comment.
感谢并祝贺您阅读本文! 如果对此有任何想法,请随时发表评论。
You can find me on GitHub or Twitter.
javascript面试