有则改之无则加勉
。 function sayHi(){
console.log(name);
console.log(age);
var name = "Tom";
let age = 18;
}
sayHi();
答案:D
在函数中,我们首先使用
var
关键字声明了name
变量。这意味着变量在创建阶段会被提升(JavaScript
会在创建变量创建阶段为其分配内存空间),默认值为undefined
,直到我们实际执行到使用该变量的行。 我们还没有为name
变量赋值,所以它仍然保持undefined
的值。
使用
let
关键字(和const
)声明的变量也会存在变量提升,但与var
不同,初始化没有被提升。 在我们声明(初始化)它们之前,它们是不可访问的。 这被称为“暂时死区”。 当我们在声明变量之前尝试访问变量时,JavaScript
会抛出一个ReferenceError
。
关于let
的是否存在变量提升,我们何以用下面的例子来验证:
let name = 'ConardLi'
{
console.log(name) // Uncaught ReferenceError: name is not defined
let name = 'code秘密花园'
}
let
变量如果不存在变量提升,console.log(name)
就会输出ConardLi
,结果却抛出了ReferenceError
,那么这很好的说明了,let
也存在变量提升,但是它存在一个“暂时死区”,在变量未初始化或赋值前不允许访问。
变量的赋值可以分为三个阶段:
undefined
关于let
、var
和function
:
let
的「创建」过程被提升了,但是初始化没有提升。var
的「创建」和「初始化」都被提升了。function
的「创建」「初始化」和「赋值」都被提升了。 for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1);
}
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1);
}
答案:C
由于
JavaScript
中的事件执行机制,setTimeout
函数真正被执行时,循环已经走完。 由于第一个循环中的变量i
是使用var
关键字声明的,因此该值是全局的。 在循环期间,我们每次使用一元运算符++
都会将i
的值增加1
。 因此在第一个例子中,当调用setTimeout
函数时,i
已经被赋值为3
。
在第二个循环中,使用
let
关键字声明变量i
:使用let
(和const
)关键字声明的变量是具有块作用域的(块是{}
之间的任何东西)。 在每次迭代期间,i
将被创建为一个新值,并且每个值都会存在于循环内的块级作用域。
const shape = {
radius: 10,
diameter() {
return this.radius * 2;
},
perimeter: () => 2 * Math.PI * this.radius
};
shape.diameter();
shape.perimeter();
答案:B
请注意,
diameter
是普通函数,而perimeter
是箭头函数。
对于箭头函数,
this
关键字指向是它所在上下文(定义时的位置)的环境,与普通函数不同! 这意味着当我们调用perimeter
时,它不是指向shape
对象,而是指其定义时的环境(window)。没有值radius
属性,返回undefined
。
+true;
!"Lydia";
答案:A
一元加号会尝试将
boolean
类型转换为数字类型。true
被转换为1
,false
被转换为0
。
字符串
'Lydia'
是一个真值。 我们实际上要问的是“这个真值是假的吗?”。 这会返回false
。
const bird = {
size: "small"
};
const mouse = {
name: "Mickey",
small: true
};
答案:A
在
JavaScript
中,所有对象键都是字符串(除了Symbol
)。尽管有时我们可能不会给定字符串类型,但它们总是被转换为字符串。
JavaScript
解释语句。当我们使用方括号表示法时,它会看到第一个左括号[
,然后继续,直到找到右括号]
。只有在那个时候,它才会对这个语句求值。
mouse [bird.size]
:首先它会对bird.size
求值,得到small
。mouse [“small”]
返回true
。
但是,使用点表示法,这不会发生。
mouse
没有名为bird
的键,这意味着mouse.bird
是undefined
。 然后,我们使用点符号来询问size:mouse.bird.size
。 由于mouse.bird
是undefined
,我们实际上是在询问undefined.size
。 这是无效的,并将抛出Cannot read property "size" of undefined
。
let c = { greeting: "Hey!" };
let d;
d = c;
c.greeting = "Hello";
console.log(d.greeting);
答案:A
在
JavaScript
中,当设置它们彼此相等时,所有对象都通过引用进行交互。
首先,变量c
为对象保存一个值。 之后,我们将d
指定为c
与对象相同的引用。
更改一个对象时,可以更改所有对象。
let a = 3;
let b = new Number(3);
let c = 3;
console.log(a == b);
console.log(a === b);
console.log(b === c);
答案:C
new Number()
是一个内置的函数构造函数。 虽然它看起来像一个数字,但它并不是一个真正的数字:它有一堆额外的功能,是一个对象。
当我们使用
==
运算符时,它只检查它是否具有相同的值。 他们都有3的值,所以它返回true
。
注
: ==
会引发隐式类型转换,右侧的对象类型会自动拆箱为Number
类型。
然而,当我们使用
===
操作符时,类型和值都需要相等,new Number()
不是一个数字,是一个对象类型。两者都返回false
。
class Chameleon {
static colorChange(newColor) {
this.newColor = newColor;
}
constructor({ newColor = "green" } = {}) {
this.newColor = newColor;
}
}
const freddie = new Chameleon({ newColor: "purple" });
freddie.colorChange("orange");
答案:D
colorChange
方法是静态的。 静态方法仅在创建它们的构造函数中存在,并且不能传递给任何子级。 由于freddie
是一个子级对象,函数不会传递,所以在freddie
实例上不存在freddie
方法:抛出TypeError
。
let greeting;
greetign = {}; // Typo!
console.log(greetign);
答案:A
控制台会输出空对象,因为我们刚刚在全局对象上创建了一个空对象! 当我们错误地将
greeting
输入为greetign
时,JS解释器实际上在浏览器中将其视为global.greetign = {}
(或window.greetign = {}
)。
为了避免这种情况,我们可以使用
“use strict”
。 这可以确保在将变量赋值之前必须声明变量。
function bark() {
console.log("Woof!");
}
bark.animal = "dog";
答案:A
这在
JavaScript
中是可能的,因为函数也是对象!(原始类型之外的所有东西都是对象)
函数是一种特殊类型的对象。您自己编写的代码并不是实际的函数。 该函数是具有属性的对象,此属性是可调用的。
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
const member = new Person("Lydia", "Hallie");
Person.getFullName = () => this.firstName + this.lastName;
console.log(member.getFullName());
答案:A
您不能像使用常规对象那样向构造函数添加属性。 如果要一次向所有对象添加功能,则必须使用原型。 所以在这种情况下应该这样写:
Person.prototype.getFullName = function () {
return `${this.firstName} ${this.lastName}`;
}
这样会使
member.getFullName()
是可用的,为什么样做是对的? 假设我们将此方法添加到构造函数本身。 也许不是每个Person
实例都需要这种方法。这会浪费大量内存空间,因为它们仍然具有该属性,这占用了每个实例的内存空间。 相反,如果我们只将它添加到原型中,我们只需将它放在内存中的一个位置,但它们都可以访问它!
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
const lydia = new Person("Lydia", "Hallie");
const sarah = Person("Sarah", "Smith");
console.log(lydia);
console.log(sarah);
答案:A
对于
sarah
,我们没有使用new
关键字。 使用new
时,它指的是我们创建的新空对象。 但是,如果你不添加new
它指的是全局对象!
我们指定了
this.firstName
等于'Sarah
和this.lastName
等于Smith
。 我们实际做的是定义global.firstName ='Sarah'和global.lastName ='Smith
。sarah
本身的返回值是undefined
。
答案:D
答案:B
除
基础对象
外,所有对象都有原型。 基础对象可以访问某些方法和属性,例如.toString
。 这就是您可以使用内置JavaScript
方法的原因! 所有这些方法都可以在原型上找到。 虽然JavaScript
无法直接在您的对象上找到它,但它会沿着原型链向下寻找并在那里找到它,这使您可以访问它。
注
:基础对象指原型链终点的对象。基础对象的原型是null。
function sum(a, b) {
return a + b;
}
sum(1, "2");
答案:C
JavaScript
是一种动态类型语言
:我们没有指定某些变量的类型。 在您不知情的情况下,值可以自动转换为另一种类型,称为隐式类型转换。强制
从一种类型转换为另一种类型。
在此示例中,
JavaScript
将数字1
转换为字符串,以使函数有意义并返回值。 在让数字类型(1
)和字符串类型('2'
)相加时,该数字被视为字符串。 我们可以连接像“Hello”+“World”
这样的字符串,所以这里发生的是“1”+“2”
返回“12”
。
let number = 0;
console.log(number++);
console.log(++number);
console.log(number);
答案:C
后缀一元运算符++
:
0
)前缀一元运算符++
:
2
)2
)所以返回0 2 2
。
function getPersonInfo(one, two, three) {
console.log(one);
console.log(two);
console.log(three);
}
const person = "Lydia";
const age = 21;
getPersonInfo`${person} is ${age} years old`;
答案:B
如果使用标记的模板字符串,则第一个参数的值始终是字符串值的数组。 其余参数获取传递到模板字符串中的表达式的值!
function checkAge(data) {
if (data === { age: 18 }) {
console.log("You are an adult!");
} else if (data == { age: 18 }) {
console.log("You are still an adult.");
} else {
console.log(`Hmm.. You don't have an age I guess`);
}
}
checkAge({ age: 18 });
答案:C
在比较相等性,原始类型通过它们的值进行比较,而对象通过它们的引用进行比较。
JavaScript
检查对象是否具有对内存中相同位置的引用。
我们作为参数传递的对象和我们用于检查相等性的对象在内存中位于不同位置,所以它们的引用是不同的。
这就是为什么
{ age: 18 } === { age: 18 }
和{ age: 18 } == { age: 18 }
返回false
的原因。
function getAge(...args) {
console.log(typeof args);
}
getAge(21);
答案:C
扩展运算符(
... args
)返回一个带参数的数组。 数组是一个对象,因此typeof args
返回object
。
function getAge() {
"use strict";
age = 21;
console.log(age);
}
getAge();
答案:C
使用
“use strict”
,可以确保不会意外地声明全局变量。 我们从未声明变量age
,因为我们使用"use strict"
,它会引发一个ReferenceError
。 如果我们不使用“use strict”,它就会起作用,因为属性age
会被添加到全局对象中。
const sum = eval("10*10+5");
答案:A
eval
会为字符串传递的代码求值。 如果它是一个表达式,就像在这种情况下一样,它会计算表达式。 表达式为10 * 10 + 5
计算得到105
。
sessionStorage.setItem("cool_secret", 123);
答案:B
关闭选项卡后,将删除存储在
sessionStorage
中的数据。
如果使用
localStorage
,数据将永远存在,除非例如调用localStorage.clear()
。
var num = 8;
var num = 10;
console.log(num);
答案:B
使用
var
关键字,您可以用相同的名称声明多个变量。然后变量将保存最新的值。
您不能使用
let
或const
来实现这一点,因为它们是块作用域的。
const obj = { 1: "a", 2: "b", 3: "c" };
const set = new Set([1, 2, 3, 4, 5]);
obj.hasOwnProperty("1");
obj.hasOwnProperty(1);
set.has("1");
set.has(1);
答案:C
所有对象键(不包括
Symbols
)都会被存储为字符串,即使你没有给定字符串类型的键。 这就是为什么obj.hasOwnProperty('1')
也返回true
。
上面的说法不适用于
Set
。 在我们的Set
中没有“1”
:set.has('1')
返回false
。 它有数字类型1
,set.has(1)
返回true
。
const obj = { a: "one", b: "two", a: "three" };
console.log(obj);
答案:C
如果对象有两个具有相同名称的键,则将替前面的键。它仍将处于第一个位置,但具有最后指定的值。
答案:A
基本执行上下文是全局执行上下文:它是代码中随处可访问的内容。
for (let i = 1; i < 5; i++) {
if (i === 3) continue;
console.log(i);
}
答案:C
如果某个条件返回
true
,则continue
语句跳过迭代。
String.prototype.giveLydiaPizza = () => {
return "Just give Lydia pizza already!";
};
const name = "Lydia";
name.giveLydiaPizza();
答案:A
String
是一个内置的构造函数,我们可以为它添加属性。 我刚给它的原型添加了一个方法。 原始类型的字符串自动转换为字符串对象,由字符串原型函数生成。 因此,所有字符串(字符串对象)都可以访问该方法!
当使用基本类型的字符串调用giveLydiaPizza时,实际上发生了下面的过程:
const a = {};
const b = { key: "b" };
const c = { key: "c" };
a[b] = 123;
a[c] = 456;
console.log(a[b]);
答案:B
对象键自动转换为字符串。我们试图将一个对象设置为对象a的键,其值为
123
。
但是,当对象自动转换为字符串化时,它变成了
[Object object]
。 所以我们在这里说的是a["Object object"] = 123
。 然后,我们可以尝试再次做同样的事情。 c对象同样会发生隐式类型转换。那么,a["Object object"] = 456
。
然后,我们打印
a[b]
,它实际上是a["Object object"]
。 我们将其设置为456
,因此返回456
。
const foo = () => console.log("First");
const bar = () => setTimeout(() => console.log("Second"));
const baz = () => console.log("Third");
bar();
foo();
baz();
答案:B
我们有一个setTimeout
函数并首先调用它。 然而却最后打印了它。
这是因为在浏览器中,我们不只有运行时引擎,我们还有一个叫做WebAPI
的东西。WebAPI
为我们提供了setTimeout
函数,例如DOM
。
将callback
推送到WebAPI
后,setTimeout
函数本身(但不是回调!)从堆栈中弹出。
现在,调用foo
,并打印First
foo
从堆栈弹出,baz
被调用,并打印Third
。
WebAPI
不能只是在准备就绪时将内容添加到堆栈中。 相反,它将回调函数推送到一个称为任务队列
的东西。
这是事件循环开始工作的地方。 事件循环
查看堆栈和任务队列。 如果堆栈为空,则会占用队列中的第一个内容并将其推送到堆栈中。
bar
被调用,Second
被打印,它从栈中弹出。
<div onclick="console.log('first div')">
<div onclick="console.log('second div')">
<button onclick="console.log('button')">
Click!
button>
div>
div>
答案:C
导致事件的最深嵌套元素是事件的目标。 你可以通过
event.stopPropagation
停止冒泡
<div onclick="console.log('div')">
<p onclick="console.log('p')">
Click here!
div>
答案:A
如果我们单击
p
,我们会看到两个日志:p
和div
。在事件传播期间,有三个阶段:捕获,目标和冒泡。 默认情况下,事件处理程序在冒泡阶段执行(除非您将useCapture
设置为true
)。 它从最深的嵌套元素向外延伸。
const person = { name: "Lydia" };
function sayHi(age) {
console.log(`${this.name} is ${age}`);
}
sayHi.call(person, 21);
sayHi.bind(person, 21);
答案:D
使用两者,我们可以传递我们想要
this
关键字引用的对象。 但是,.call
方法会立即执行!
.bind
方法会返回函数的拷贝值,但带有绑定的上下文! 它不会立即执行.
function sayHi() {
return (() => 0)();
}
typeof sayHi();
答案:B
sayHi
函数返回立即调用的函数(IIFE
)的返回值。 该函数返回0,类型为数字
。
只有7种内置类型:
null,undefined,boolean,number,string,object和symbol
。function
不是一个类型,因为函数是对象,它的类型是object
。
0;
new Number(0);
("");
(" ");
new Boolean(false);
undefined;
答案:A
JavaScript
中只有6个假值:
函数构造函数,如new Number
和new Boolean
都是真值。
console.log(typeof typeof 1);
答案:B
typeof 1
返回"number"
.
typeof "number"
返回"string"
const numbers = [1, 2, 3];
numbers[10] = 11;
console.log(numbers);
答案:C
When you set a value to an element in an array that exceeds the length of the array, JavaScript creates something called “empty slots”. These actually have the value of
undefined
, but you will see something like:
当你为数组中的元素设置一个超过数组长度的值时,
JavaScript
会创建一个名为“空插槽”的东西。 这些位置的值实际上是undefined
,但你会看到类似的东西:
[1, 2, 3, 7 x empty, 11]
这取决于你运行它的位置(每个浏览器有可能不同)。
(() => {
let x, y;
try {
throw new Error();
} catch (x) {
(x = 1), (y = 2);
console.log(x);
}
console.log(x);
console.log(y);
})();
答案:A
catch
块接收参数x
。当我们传递参数时,这与变量的x
不同。这个变量x
是属于catch
作用域的。
之后,我们将这个块级作用域的变量设置为
1
,并设置变量y
的值。 现在,我们打印块级作用域的变量x
,它等于1
。
在
catch
块之外,x
仍然是undefined
,而y
是2
。 当我们想在catch
块之外的console.log(x)
时,它返回undefined
,而y
返回2
。
答案:A
JavaScript
只有原始类型和对象。
原始类型是
boolean,null,undefined,bigint,number,string和symbol
。
[[0, 1], [2, 3]].reduce(
(acc, cur) => {
return acc.concat(cur);
},
[1, 2]
);
答案:C
[1,2]
是我们的初始值。 这是我们开始执行reduce
函数的初始值,以及第一个acc
的值。 在第一轮中,acc
是[1,2]
,cur
是[0,1]
。 我们将它们连接起来,结果是[1,2,0,1]
。
然后,
acc
的值为[1,2,0,1]
,cur
的值为[2,3]
。 我们将它们连接起来,得到[1,2,0,1,2,3]
。
!!null;
!!"";
!!1;
答案:B
null
是假值。!null
返回true
。!true
返回false
。
""
是假值。!""
返回true
。!true
返回false
。
1
是真值。!1
返回false
。!false
返回true
。
setInterval
方法的返回值什么? setInterval(() => console.log("Hi"), 1000);
答案:A
它返回一个唯一的
id
。 此id
可用于使用clearInterval()
函数清除该定时器
[..."Lydia"];
答案:A
字符串是可迭代的。 扩展运算符将迭代的每个字符映射到一个元素。
function* generator(i){
yield i;
yield i * 2;
}
const gen = generator(10);
console.log(gen.next().value);
console.log(gen.next().value);
答案:C
一般的函数在执行之后是不能中途停下的。但是,生成器函数却可以中途“停下”,之后可以再从停下的地方继续。当生成器遇到
yield
关键字的时候,会生成yield
后面的值。注意,生成器在这种情况下不 返回(return )值,而是 生成 (yield)值。
首先,我们用
10
作为参数i
来初始化生成器函数。然后使用next()
方法一步步执行生成器。第一次执行生成器的时候,i
的值为10
,遇到第一个yield
关键字,它要生成i
的值。此时,生成器“暂停”,生成了10
。
然后,我们再执行
next()
方法。生成器会从刚才暂停的地方继续,这个时候i
还是10
。于是我们走到了第二个yield
关键字处,这时候需要生成的值是i*2
,i
为10
,那么此时生成的值便是20
。所以这道题的最终结果是10,20
。
const firstPromise = new Promise((res,rej) => {
setTimeout(res,500,'one');
})
const secondPromise = new Promise((res,rej) => {
setTimeout(res,100,'two')
})
Promise.race([firstPromise,secondPromise]).then(res => console.log(res))
答案:B
当我们向
Promise.race
方法中传入多个Promise
时,会进行优先
解析。在这个例子中,我们用setTimeout
给firstPromise
和secondPromise
分别设定了500ms和100ms的定时器。这意味着secondPromise
会首先解析出字符串two
。那么此时res
参数即为two
,是为输出结果。
const person = {
name:"Lydia",
age:21
};
for(const item in person){
console.log(item);
}
答案:B
在
for-in
循环中,我们可以通过对象的key
来进行迭代,也就是这里的name
和age
。在底层,对象的key都是字符串(如果他们不是Symbol的话)。在每次循环中,我们将item
设定为当前遍历到的key.所以一开始,item
是name
,之后item
输出的则是age
。
console.log(3 + 4 + "5")
答案:B
当所有运算符的 优先级 相同时,计算表达式需要确定运算符的结合顺序,即从右到左还是从左往右。在这个例子中,我们只有一类运算符 +,对于加法来说,结合顺序就是从左到右。
3+4
首先计算,得到数字7
.
由于类型的强制转换,
7+'5'
的结果是"75"
. JavaScript将7
转换成了字符串,可以参考问题15.我们可以用+
号把两个字符串连接起来。"7"+"5"
就得到了"75"
.
[1,2,3].map(num => {
if(typeof num === "number") return;
return num * 2;
})
答案:C
对数组进行映射的时候,
num
就是当前循环到的元素. 在这个例子中,所有的映射都是number类型,所以if中的判断typeofnum==="number"
结果都是true
.map函数创建了新数组并且将函数的返回值插入数组。
但是,没有任何值返回。当函数没有返回任何值时,即默认返回
undefined
对数组中的每一个元素来说,函数块都得到了这个返回值,所以结果中每一个元素都是undefined
.
function getInfo(member,year){
member.name = "Lydia";
year = "1998";
}
const person = { name:"Sarah" };
const birthYear = "1997";
getInfo(person,birthYear);
console.log(person,birthYear);
答案:A
普通参数都是 值 传递的,而对象则不同,是 引用 传递。所以说,
birthYear
是值传递,因为他是个字符串而不是对象。当我们对参数进行值传递时,会创建一份该值的 复制 。(可以参考问题46)
变量
birthYear
有一个对"1997"
的引用,而传入的参数也有一个对"1997"
的引用,但二者的引用并不相同。当我们通过给year
赋值"1998"
来更新year
的值的时候我们只是更新了year
(的引用)。此时birthYear
仍然是"1997"
.
而
person
是个对象。参数member
引用与之 相同的 对象。当我们修改member
所引用对象的属性时,person
的相应属性也被修改了,因为他们引用了相同的对象.person
的name
属性也变成了"Lydia"
.
function greeting(){
throw "Hello world!";
}
function sayHi(){
try{
const data = greeting();
console.log("It worked",data);
}catch(e){
console.log("Oh no an error!",e)
}
}
sayHi()
答案:D
通过
throw
语句,我么可以创建自定义错误。而通过它,我们可以抛出异常。异常可以是一个字符串
, 一个数字
, 一个布尔类型
或者是一个对象
。在本例中,我们的异常是字符串'Hello world'
.
通过
catch
语句,我们可以设定当try
语句块中抛出异常后应该做什么处理。在本例中抛出的异常是字符串'Hello world'
,e
就是这个字符串,因此被输出。最终结果就是'Oh an error: Hello world'
.