每日一题
-
以下哪个会打印出"3"?
3.toString() //报错 Uncaught SyntaxError: Invalid or unexpected token 无效或意外的标记 3..toString() //"3" 3...toString() //报错 Uncaught SyntaxError: Unexpected token '.'
解析:
运算符优先级的问题,点运算符会被优先识别为数字常量的一部分,然后才是对象属性访问符
在 JS 中,3.1
,3.
,.3
都是合法的数字
3.toString()
会被 JS 引擎解析成(3.)toString()
报错
3..toString()
会被 JS 引擎解析成(3.).toString()
"3"
3...toString()
会被 JS 引擎解析成(3.)..toString()
报错
-
输出是什么?
const obj = { 1: 'a', 2: 'b', 3: 'c' } const set = new Set([1, 2, 3, 4, 5]) obj.hasOwnProperty('1') //true obj.hasOwnProperty(1) //true set.has('1') //false set.has(1) //true
解析:
所有对象键(不包括Symbols
)都会被存储为字符串,即使你没有给定字符串类型的键。这就是为什么obj.hasOwnProperty('1')
也返回true
。
上面的说法不适用于Set
。在我们的Set
中没有“1”
:set.has('1')
返回false
。它有数字类型1
,set.has(1)
返回true
。
扩展:
1. hasOwnProperty
hasOwnProperty()
方法返回一个布尔值,该布尔值指示对象是否具有指定的属性作为其自身的属性(而不是继承它)。
-
句法
obj.hasOwnProperty(prop)
-
用途:
-
判断自身属性是否存在
o = new Object(); o.hasOwnProperty('prop'); // returns false o.prop = 'exists'; function changeO() { o.newprop = o.prop; delete o.prop; } o.hasOwnProperty('prop'); // returns true changeO(); o.hasOwnProperty('prop'); // false
-
判断自身属性与继承属性(通过原型链继承的属性)
function foo() { this.name = 'foo' this.sayHi = function () { console.log('Say Hi') } } foo.prototype.sayGoodBy = function () { console.log('Say Good By') } let myPro = new foo() console.log(myPro.hasOwnProperty('name')) // true console.log(myPro.hasOwnProperty('toString')) // false console.log(myPro.hasOwnProperty('hasOwnProperty')) // fasle console.log(myPro.hasOwnProperty('sayHi')) // true console.log(myPro.hasOwnProperty('sayGoodBy')) // false console.log('sayGoodBy' in myPro) // true
-
遍历一个对象的所有自身属性(不包括原型继承属性)
function foo() { this.name = 'foo' this.sayHi = function () { console.log('Say Hi') } } foo.prototype.sayGoodBy = function () { console.log('Say Good By') } let myPro = new foo() for (var name in myPro) { if (myPro.hasOwnProperty(name)) { console.log('this is fog (' + name + ') for sure. Value: ' + myPro[name]); } else { console.log(name); // toString or something else } }
-
2. Set的使用方法
ES6
提供的新的数据结构,类似Array
。但是Set
中成员的值都是唯一的,没有重复的值。
Set
对象可以存储任何类型的唯一值,无论是原始值或者是对象引用。创建Set
的实例需要用到Set
构造函数,并且传入的参数必须只能是可迭代对象,例如数组,字符串。
let set2 = new Set([1, 2, 3, 2, 1]);
console.log(set2); //Set {1, 2, 3} 去重效果
-
Set
的方法和属性-
add()方法:
用于向
Set
中添加元素,返回一个新的Set
。let set1 = new Set(); set1.add(1); set1.add('xkd'); console.log(set1); // Set { 1, 'xkd' }
-
delete()方法:
用于删除
Set
中的元素,返回一个布尔值,判断是否删除成功。如果删除成功返回true
,删除失败返回false
。let set1 = new Set([1, 2, 3, 4, 5]); console.log(set1.delete(4)); // true console.log(set1); // Set { 1, 2, 3, 5 }
-
has()方法:
用于判断某个值是否为
Set
的成员,返回一个布尔值。let set1 = new Set([1, 2, 3, 4, 5]); console.log(set1.has(2)); // true console.log(set1.has(7)); // false
-
clear()方法:
用于清空
Set
中的全部成员。let set1 = new Set([1, 2, 3, 4, 5]); set1.clear(); console.log(set1); // 输出:Set {}
-
size属性:
属性返回当前
Set
元素总数。let set1 = new Set([1, 2, 3, 4, 5]); console.log(set1.size); // 输出:5
-
Set
遍历操作:
-
keys()方法:返回键名的遍历器。
let set_k = new Set(['xkd', 'Iven', 'summer']); console.log(set_k.keys()); // [Set Iterator] { 'xkd', 'Iven', 'summer' }
-
values()方法:返回键值的遍历器。
let set_v = new Set(['xkd', 'Iven', 'summer']); console.log(set_v.values()); // [Set Iterator] { 'xkd', 'Iven', 'summer' }
由于
Set
结构没有键名,只有键值,或者说键名和键值是同一个,所以keys
方法和values
方法的行为完全一致。 -
entries()方法:返回键值对的遍历器。
let set_e = new Set(['xkd', 'Iven', 'summer']); console.log(set_e.entries());//[Set Entries] { [ 'xkd', 'xkd' ],[ 'Iven', 'Iven' ], [ 'summer', 'summer' ]}
-
forEach()方法:使用回调函数遍历每个成员。
let set_f = new Set(['xkd', 'Iven', 'summer']); set_f.forEach(function(value,key){ console.log(value+":"+key); }) // 输出: // xkd:xkd // Iven:Iven // summer:summer
-
Set
对象的作用:
-
去重
let set1 = new Set([1, 2, 3, 4, 2, 4]); console.log(set1); // Set { 1, 2, 3, 4 }
-
并集
var set1 = new Set([1, 2, 3]); var set2 = new Set([7, 6, 1]); var union = new Set([...set1, ...set2]); console.log(union); // Set { 1, 2, 3, 7, 6 }
-
交集
var set1 = new Set([1, 2, 3]); var set2 = new Set([7, 6, 1]); let inter = new Set([...set1].filter(x=>set2.has(x))) //{4,5} console.log(inter); // Set { 1 }
-
差集
var set1 = new Set([1, 2, 3]); var set2 = new Set([7, 6, 1]); var diff = new Set([...set1].filter(x => !set2.has(x))); // {1} console.log(diff); // Set { 2, 3 }
-
-
Set
类型转换:-
Array
转为Set
:let arr1 = ["x", "k", "d"]; let set1 = new Set(arr1); console.log(set1); // Set { 'x', 'k', 'd' }
-
Set
转为Array
:可以通过扩展运算符...
来实现。let set1 = new Set(["x", "k", "d"]); var arr1 = [...set1]; console.log(arr1); //[ 'x', 'k', 'd' ]
-
String
转为Set
:let set1 = new Set('xkd'); console.log(set1); // Set { 'x', 'k', 'd' }
-
-
输出是什么?
const obj = { a: 'one', b: 'two', a: 'three' } console.log(obj) //{ a: "three", b: "two" }
解析:
如果对象有两个具有相同名称的键,则将替前面的键。它仍将处于第一个位置,但具有最后指定的值。
-
输出是什么?
for (let i = 1; i < 5; i++) { if (i === 3) continue console.log(i) //1 2 4 }
-
输出是什么?
const a = {} const b = { key: 'b' } const c = { key: 'c' } a[b] = 123 a[c] = 456 console.log(a[b]) //456
解析:
对象的键被自动转换为字符串。我们试图将一个对象
b
设置为对象a
的键,且相应的值为123
。然而,当字符串化一个对象时,它会变成
"[object Object]"
。因此这里说的是,a["[object Object]"] = 123
。然后,我们再一次做了同样的事情,c
是另外一个对象,这里也有隐式字符串化,于是,a["[object Object]"] = 456
。然后,我们打印
a[b]
,也就是a["[object Object]"]
。之前刚设置为456
,因此返回的是456
。
-
当点击按钮时,event.target是什么?
<div onclick="console.log('first div')"> <div onclick="console.log('second div')"> <button onclick="console.log('button')"> Click! </button> </div> </div>
解析:
button
导致事件的最深嵌套的元素是事件的 target。你可以通过
event.stopPropagation
来停止冒泡。event.target 返回触发事件的元素
event.currentTarget 返回绑定事件的元素
-
输出是什么?
const person = { name: 'Lydia' } function sayHi(age) { console.log(`${this.name} is ${age}`) } sayHi.call(person, 21) //Lydia is 21 sayHi.bind(person, 21) // function
解析:
使用这两种方法,我们都可以传递我们希望
this
关键字引用的对象。但是,.call
是立即执行的。.bind
返回函数的副本,但带有绑定上下文!它不是立即执行的。
扩展
1. call、apply、bind
-
call:
fun.call(thisArg[, arg1[, arg2[, ...]]])
它会立即执行函数,第一个参数是指定执行函数中 this 的上下文,后面的参数是执行函数需要传入的参数。
-
apply:
fun.apply(thisArg, [argsArray])
它也会立即执行函数,第一个参数是指定执行函数中 this 的上下文,第二个参数是一个数组,是传给执行函数的参数(与 call 的区别)。
-
bind:
var foo = fun.bind(thisArg[, arg1[, arg2[, ...]]]);
它不会执行函数,而是返回一个新的函数,这个新的函数被指定了
this
的上下文,后面的参数是执行函数需要传入的参数。
-
输出是什么?
console.log(typeof typeof 1) //string
解析:
typeof 1
返回
"number"。
typeof "number"返回
"string".
-
输出是什么?
function sayHi() { return (() => 0)()}typeof sayHi() // number
解析:
sayHi
方法返回的是立即执行函数(IIFE)的返回值.此立即执行函数的返回值是
0, 类型是
number
参考:只有7种内置类型:null,undefined,boolean,number,string,object, symbol 和 bigint。function 不是一种类型,函数是对象,它的类型是object。typeof sayHi()等同于 (() => 0)()
console.log((() => 0)()) //0
-
输出是什么?
[[0, 1], [2, 3]].reduce( (acc, cur) => { return acc.concat(cur) }, [1, 2] ) //[1, 2, 0, 1, 2, 3]
解析:
[1, 2]
是初始值。初始值将会作为首次调用时第一个参数
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]
扩展
reduce函数:
reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
-
语法
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
-
参数
参数 描述 function(total,currentValue, currentIndex,arr) 必需。用于执行每个数组元素的函数。 initialValue 可选。传递给函数的初始值 function(total,currentValue, currentIndex,arr)参数介绍:
参数 描述 total 必需。初始值, 或者计算结束后的返回值。 currentValue 必需。当前元素 currentIndex 可选。当前元素的索引 arr 可选。当前元素所属的数组对象。 注意:reduce() 对于空数组是不会执行回调函数的。
-
输出是什么?
function* generator(i) { yield i; yield i * 2; } const gen = generator(10); console.log(gen.next().value); // 10 console.log(gen.next().value); // 20
解析:
一般的函数在执行之后是不能中途停下的。但是,生成器函数却可以中途“停下”,之后可以再从停下的地方继续。当生成器遇到
yield
关键字的时候,会生成yield
后面的值。注意,生成器在这种情况下不 返回 (return )值,而是 生成 (yield)值。首先,我们用
10
作为参数i
来初始化生成器函数。然后使用next()
方法一步步执行生成器。第一次执行生成器的时候,i
的值为10
,遇到第一个yield
关键字,它要生成i
的值。此时,生成器“暂停”,生成了10
。然后,我们再执行
next()
方法。生成器会从刚才暂停的地方继续,这个时候i
还是10
。于是我们走到了第二个yield
关键字处,这时候需要生成的值是i*2
,i
为10
,那么此时生成的值便是20
。所以这道题的最终结果是10,20
。
12.输出的是什么?
function getInfo(member, year) {
member.name = "Lydia";
year = "1998";
}
const person = { name: "Sarah" };
const birthYear = "1997";
getInfo(person, birthYear);
console.log(person, birthYear); //{ name: "Lydia" }, "1997"
解析:
普通参数都是 值 传递的,而对象则不同,是 引用 传递。所以说,
birthYear
是值传递,因为他是个字符串而不是对象。当我们对参数进行值传递时,会创建一份该值的 复制 。(可以参考问题46)变量
birthYear
有一个对"1997"
的引用,而传入的参数也有一个对"1997"
的引用,但二者的引用并不相同。当我们通过给year
赋值"1998"
来更新year
的值的时候我们只是更新了year
(的引用)。此时birthYear
仍然是"1997"
.而
person
是个对象。参数member
引用与之 相同的 对象。当我们修改member
所引用对象的属性时,person
的相应属性也被修改了,因为他们引用了相同的对象.person
的name
属性也变成了"Lydia"
.
- 输出是什么?
class Dog {
constructor(name) {
this.name = name;
}
}
Dog.prototype.bark = function() {
console.log(`Woof I am ${this.name}`);
};
const pet = new Dog("Mara");
pet.bark(); //Woof I am Mara
delete Dog.prototype.bark;
pet.bark(); //Uncaught TypeError: pet.bark is not a function
解析:
我们可以用
delete
关键字删除对象的属性,对原型也是适用的。删除了原型的属性后,该属性在原型链上就不可用了。在本例中,函数bark
在执行了delete Dog.prototype.bark
后不可用, 然而后面的代码还在调用它。当我们尝试调用一个不存在的函数时
TypeError
异常会被抛出。在本例中就是TypeError: pet.bark is not a function
,因为pet.bark
是undefined
.
-
输出是什么?
const person = { name: "Lydia" }; Object.defineProperty(person, "age", { value: 21 }); console.log(person); //{ name: "Lydia", age: 21 } console.log(Object.keys(person)); // ["name"]
解析:
通过
defineProperty
方法,我们可以给对象添加一个新属性,或者修改已经存在的属性。而我们使用defineProperty
方法给对象添加了一个属性之后,属性默认为 不可枚举(not enumerable).Object.keys
方法仅返回对象中 可枚举(enumerable) 的属性,因此只剩下了"name"
.用
defineProperty
方法添加的属性默认不可变。你可以通过writable
,configurable
和enumerable
属性来改变这一行为。这样的话, 相比于自己添加的属性,defineProperty
方法添加的属性有了更多的控制权。
-
输出是什么?
const name = "Lydia"; age = 21; console.log(delete name); //false console.log(delete age); //true
解析:
delete
操作符返回一个布尔值:true
指删除成功,否则返回false
. 但是通过var
,const
或let
关键字声明的变量无法用delete
操作符来删除。name
变量由const
关键字声明,所以删除不成功:返回false
. 而我们设定age
等于21
时,我们实际上添加了一个名为age
的属性给全局对象。对象中的属性是可以删除的,全局对象也是如此,所以delete age
返回true
.
-
输出是什么?
const settings = { username: "lydiahallie", level: 19, health: 90 }; //第二个参数是数组的情况 const data = JSON.stringify(settings, ["level", "health"], 4); console.log(data); //"{"level":19, "health":90}" //第二个参数是函数的情况,第三个参数是缩紧空格数 function replacer(key, value) { if (typeof value === "string") { return undefined; } return value; } var foo = {foundation: "Mozilla", model: "box", week: 45, transport: "car", month: 7}; var jsonString = JSON.stringify(foo, replacer, 4); console.log(jsonString); //{ // "week": 45, // "month": 7 //}
解析:
JSON.stringify
的第二个参数是 替代者(replacer). 替代者(replacer)可以是个函数或数组,用以控制哪些值如何被转换为字符串。如果替代者(replacer)是个 数组 ,那么就只有包含在数组中的属性将会被转化为字符串。在本例中,只有名为
"level"
和"health"
的属性被包括进来,"username"
则被排除在外。data
就等于"{"level":19, "health":90}"
.而如果替代者(replacer)是个 函数,这个函数将被对象的每个属性都调用一遍。函数返回的值会成为这个属性的值,最终体现在转化后的JSON字符串中(译者注:Chrome下,经过实验,如果所有属性均返回同一个值的时候有异常,会直接将返回值作为结果输出而不会输出JSON字符串),而如果返回值为
undefined
,则该属性会被排除在外。
-
输出是什么?
let num = 10; const increaseNumber = () => num++; const increasePassedNumber = number => number++; const num1 = increaseNumber(); const num2 = increasePassedNumber(num1); console.log(num1); // 10 console.log(num2); // 10
解析:
一元操作符
++
先返回 操作值, 再累加 操作值。num1
的值是10
, 因为increaseNumber
函数首先返回num
的值,也就是10
,随后再进行num
的累加。num2
是10
因为我们将num1
传入increasePassedNumber
.number
等于10
(num1
的值。同样道理,++
先返回 操作值, 再累加 操作值。)number
是10
,所以num2
也是10
.
-
输出什么?
const value = { number: 10 }; const multiply = (x = { ...value }) => { console.log(x.number *= 2); }; multiply(); // 20 multiply(); //20 multiply(value); //20 multiply(value); //40
解析:
在ES6中,我们可以使用默认值初始化参数。如果没有给函数传参,或者传的参值为
"undefined"
,那么参数的值将是默认值。上述例子中,我们将value
对象进行了解构并传到一个新对象中,因此x
的默认值为{number:10}
。默认参数在调用时才会进行计算,每次调用函数时,都会创建一个新的对象。我们前两次调用
multiply
函数且不传递值,那么每一次x
的默认值都为{number:10}
,因此打印出该数字的乘积值为20
。第三次调用
multiply
时,我们传递了一个参数,即对象value
。*=
运算符实际上是x.number = x.number * 2
的简写,我们修改了x.number
的值,并打印出值20
。第四次,我们再次传递
value
对象。x.number
之前被修改为20
,所以x.number * = 2
打印为40
。
-
输出什么?
[1, 2, 3, 4].reduce((x, y) => console.log(x, y)); //1 2 undefined 3 undefined 4
解析:
reducer
函数接收4个参数:- Accumulator (acc) (累计器)
- Current Value (cur) (当前值)
- Current Index (idx) (当前索引)
- Source Array (src) (源数组)
reducer
函数的返回值将会分配给累计器,该返回值在数组的每个迭代中被记住,并最后成为最终的单个结果值。reducer
函数还有一个可选参数initialValue
, 该参数将作为第一次调用回调函数时的第一个参数的值。如果没有提供initialValue
,则将使用数组中的第一个元素。在上述例子,
reduce
方法接收的第一个参数(Accumulator)是x
, 第二个参数(Current Value)是y
。在第一次调用时,累加器
x
为1
,当前值“y”
为2
,打印出累加器和当前值:1
和2
。例子中我们的回调函数没有返回任何值,只是打印累加器的值和当前值。如果函数没有返回值,则默认返回
undefined
。在下一次调用时,累加器为undefined
,当前值为“3”, 因此undefined
和3
被打印出。在第四次调用时,回调函数依然没有返回值。累加器再次为
undefined
,当前值为“4”。undefined
和4
被打印出。
-
输出什么?
// index.js console.log('running index.js'); import { sum } from './sum.js'; console.log(sum(1, 2)); // sum.js console.log('running sum.js'); export const sum = (a, b) => a + b;
解析:
import
命令是编译阶段执行的,在代码运行之前。因此这意味着被导入的模块会先运行,而导入模块的文件会后执行。这是CommonJS中
require()
和import
之间的区别。使用require()
,您可以在运行代码时根据需要加载依赖项。如果我们使用require
而不是import
,running index.js
,running sum.js
,3
会被依次打印。
-
输出什么?
const name = "Lydia Hallie" console.log(name.padStart(13)) //" Lydia Hallie" console.log(name.padStart(2)) //"Lydia Hallie"
解析:
使用
padStart
方法,我们可以在字符串的开头添加填充。传递给此方法的参数是字符串的总长度(包含填充)。字符串Lydia Hallie
的长度为12
, 因此name.padStart(13)
在字符串的开头只会插入1(13 - 12 = 1
)个空格。如果传递给
padStart
方法的参数小于字符串的长度,则不会添加填充。
扩展
String.prototype.padStart()
padStart()
方法用另一个字符串填充当前字符串(如果需要的话,会重复多次),以便产生的字符串达到给定的长度。从当前字符串的左侧开始填充。
语法
str.padStart(targetLength [, padString])
参数
- targetLength:当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度,则返回当前字符串本身。
- padString(可选):填充字符串。如果字符串太长,使填充后的字符串长度超过了目标长度,则只保留最左侧的部分,其他部分会被截断。此参数的默认值为 " "(U+0020)。
返回值
- 在原字符串开头填充指定的填充字符串直到目标长度所形成的新字符串。
示例
'x'.padStart(5, 'ab') // 'ababx''x'.padStart(4, 'ab') // 'abax''xxx'.padStart(2, 'ab') // 'xxx' ----如果原字符串的长度,等于或大于指定的最小长度,则返回原字符串。'abc'.padStart(10, '0123456789') // '0123456abc'----如果用来补全的字符串与原字符串,两者的长度之和超过了指定的最小长度,则会截去超出位数的补全字符串。'x'.padStart(4) // ' x'----如果省略第二个参数,默认使用空格补全长度。
常见用途:
-
padStart()
的常见用途是为数值补全指定位数。下面代码生成 10 位的数值字符串。'1'.padStart(10, '0') // "0000000001" '12'.padStart(10, '0') // "0000000012" '123456'.padStart(10, '0') // "0000123456"
-
另一个用途是提示字符串格式。
'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12" '09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"
String.prototype.padEnd()
- 从当前字符串的末尾(右侧)开始填充。用法和String.prototype.padStart()相同。
-
输出什么?
function* startGame() { const 答案 = yield "Do you love JavaScript?"; if (答案 !== "Yes") { return "Oh wow... Guess we're gone here"; } return "JavaScript loves you back ❤️"; } const game = startGame(); console.log(game.next().value); // Do you love JavaScript? console.log(game.next("Yes").value); // JavaScript loves you back ❤️
解析:
generator
函数在遇到
yield关键字时会“暂停”其执行。首先,我们需要让函数产生字符串
Do you love JavaScript?,这可以通过调用
game.next().value来完成。上述函数的第一行就有一个
yield关键字,那么运行立即停止了,
yield表达式本身没有返回值,或者说总是返回
undefined, 这意味着此时变量
答案为
undefinednext
方法可以带一个参数,该参数会被当作上一个yield
表达式的返回值。当我们调用game.next("Yes").value
时,先前的yield
的返回值将被替换为传递给next()
函数的参数"Yes"
。此时变量答案
被赋值为"Yes"
,if
语句返回false
,所以JavaScript loves you back ❤️
被打印。
-
输出什么?
console.log(String.raw`Hello\nworld`);
解析:
String.raw
函数是用来获取一个模板字符串的原始字符串的,它返回一个字符串,其中忽略了转义符(\n
,\v
,\t
等)。但反斜杠可能造成问题,因为你可能会遇到下面这种类似情况:const path = `C:\Documents\Projects\table.html` String.raw`${path}`
这将导致:
"C:DocumentsProjects able.html"
直接使用
String.raw
String.raw`C:\Documents\Projects\table.html`
它会忽略转义字符并打印:
C:\Documents\Projects\table.html
上述情况,字符串是
Hello\nworld
被打印出。
-
输出什么?
async function getData() { return await Promise.resolve("I made it!"); } const data = getData(); console.log(data); //Promise {<pending>}
解析:
异步函数始终返回一个promise。
await
仍然需要等待promise的解决:当我们调用getData()
并将其赋值给data
,此时data
为getData
方法返回的一个挂起的promise,该promise并没有解决。如果我们想要访问已解决的值
"I made it!"
,可以在data
上使用.then()
方法:data.then(res => console.log(res))
这样将打印
"I made it!"
-
输出什么?
const myLifeSummedUp = ["☕", "💻", "🍷", "🍫"]; for(let item in myLifeSummedUp){ console.log(item); } // 0,1,2,3 for(let item of myLifeSummedUp){ console.log(item) }//"☕", "💻", "🍷", "🍫"
解析:
记忆技巧:in i开头所以是 index of o开头所以是 object.
通过for-in循环,我们可以遍历一个对象自有的、继承的、可枚举的、非Symbol的属性。在数组中,可美剧属性是数组元素的“键”,即他们的索引。类似于下面这个对象:
{0: "☕", 1: "💻", 2: "🍷", 3: "🍫"}
其中键则是可枚举属性,因此0,1,2,3被记录。
通过for-of循环,我们可以迭代可迭代的对象(包括
Array
,Map
,Set
,String
,arguments
等)。当我们迭代数组时,在每次迭代中,不同属性的值将被分配给变量item,因此"☕", "💻", "🍷", "🍫"被打印。
prototype
、__proto__
、constructor
的区别和联系:
代码:
function Foo() {...};
let f1 = new Foo();
-
需要牢记亮点:
-
__proto__
和constructor
属性是对象独有的. -
prototype
是函数独有的,因为函数也是对象,故函数也有__proto__
,constructor
属性
-
__proto__
属性的作用就是当访问一个对象的属性时,如果对象内部不存在该属性,那么就会去他的__prpto__
属性所指向的那个对象中(父对象)去找,一直找,直到找到或者直到__proto__
属性的终点null
,再往上找就相当于在null
上取值,会报错。通过__proto__
属性将对象连接起来的这条链路即我们所谓的原型链。 -
prototype
属性的作用就是让该函数所实例化的对象们都可以找到公用的属性和方法,即f1.__proto__ === Foo.prototype
。 -
constructor
属性的含义就是指向改对象的构造函数,所有函数(此时看成对象了)最终的构造函数都指向Function
.
-
-
输出什么?
function checkAge(age) { if (age < 18) { const message = "Sorry, you're too young." } else { const message = "Yay! You're old enough!" } return message}console.log(checkAge(21)) //ReferenceError: message is not defined
解析:
const
和let
声明的变量是具有块级作用域的,块是大括号({}
)之间的任何东西, 即上述情况if / else
语句的花括号。由于块级作用域,我们无法在声明的块之外引用变量,因此抛出ReferenceError
。
-
输出什么?
console.log("I want pizza"[0]) //"I"
解析:
可以使用方括号表示法获取字符串中特定索引的字符,字符串中的第一个字符具有索引0,依此类推。在这种情况下,我们想要得到索引为0的元素,字符
'I'
被记录。请注意,IE7及更低版本不支持此方法。在这种情况下,应该使用
.charAt()
-
输出什么?
// module.js export default () => "Hello world" export const name = "Lydia" // index.js import * as data from "./module" console.log(data) //{ default: function default(), name: "Lydia" }
解析:
使用
import * as name
语法,我们将module.js
文件中所有export
导入到index.js
文件中,并且创建了一个名为data
的新对象。在module.js
文件中,有两个导出:默认导出和命名导出。默认导出是一个返回字符串“Hello World”的函数,命名导出是一个名为name
的变量,其值为字符串“Lydia”
。data
对象具有默认导出的default
属性,其他属性具有指定exports的名称及其对应的值。
import * as name
用法import * as obj from,这种写法是把所有的输出包裹到obj对象里。如下:
-
示例1:
// module.js export function fn1(data){ console.log(1) } export function fn2(data){ console.log(2) } //index.js import * as Fn from './index.js' Fn.fn1() // 1 Fn.fn2() // 2
-
示例二
// test.js let myName = "Jon"; let myAge = 18; let myfn = function(){ return "我是"+myName+"!今年"+myAge+"岁了" } export { myName as name, myAge as age, myfn as fn } //index.js import {fn,age,name} from "./test.js"; console.log(fn()); //我是Jon!今年19岁了 console.log(age); //19 console.log(name); //Jon //或者 import * as info from "./test.js"; //通过*来批量接收,as 来指定接收的名字 console.log(info.fn()); //我是Jon!今年18岁了 console.log(info.age); //18 console.log(info.name); //Jon
-
示例三
重命名export和import,如果导入的多个文件中,变量名字相同,即会产生命名冲突的问题,为了解决该问题,ES6为提供了重命名的方法,当你在导入名称时可以这样做。
/*************test1.js*****************/ export let myName = "我来自test1.js"; /*************test2.js*****************/ export let myName = "我来自test2.js"; /*************index.js****************/ import {myName as name1} from "./test1.js"; import {myName as name2} from "./test2.js"; console.log(name1); //我来自test1.js console.log(name2); //我来自test2.js
-
输出什么?
class Person { constructor(name) { this.name = name } } const member = new Person("John") console.log(typeof member) //object
解析:
类是构造函数的语法糖,如果用构造函数的方式来重写
Person
类则将是:function Person() { this.name = name }
通过
new
来调用构造函数,将会生成构造函数Person
的实例,对实例执行typeof
关键字将返回"object"
,上述情况打印出"object"
。
-
输出什么?
let newList = [1, 2, 3].push(4)console.log(newList.push(5)) //Uncaught TypeError: newList.push is not a function
解析:
.push
方法返回数组的长度,而不是数组本身!通过将newList
设置为[1,2,3].push(4)
,实际上newList
等于数组的新长度:4
。然后,尝试在
newList
上使用.push
方法。由于newList
是数值4
,抛出TypeError。
-
输出什么?
function giveLydiaPizza() { return "Here is pizza!"}const giveLydiaChocolate = () => "Here's chocolate... now go hit the gym already."console.log(giveLydiaPizza.prototype)console.log(giveLydiaChocolate.prototype)
解析:
常规函数,例如
giveLydiaPizza
函数,有一个prototype
属性,它是一个带有constructor
属性的对象(原型对象)。然而,箭头函数,例如giveLydiaChocolate
函数,没有这个prototype
属性。尝试使用giveLydiaChocolate.prototype
访问prototype
属性时会返回undefined
。
-
输出什么?
const name = "Lydia"console.log(name()) //TypeError: name is not a function
解析:
浏览器的几种报错类型:
-
SyntaxError
语法错误。 -
TypeError
类型错误,通常是 *** is not a function,即***不是一个函数。 -
ReferenceError
引用错误,通常是 *** is not defined,即***未定义,不同于undefined,underfind不是报错,而是一种数值类型。 -
RangeError
是当一个值超出有效范围时发生的错误。主要有几种情况,一是数组长度为负数,二是Number对象的方法参数超出范围,以及函数堆栈超过最大值。 -
URIError
是URI相关函数的参数不正确时抛出的错误,主要涉及encodeURI()、decodeURI()、encodeURIComponent()、decodeURIComponent()、escape()和unescape()这六个函数。 -
eval
函数没有被正确执行时,会抛出EvalError错误。该错误类型已经不再在ES5中出现了,只是为了保证与以前代码兼容,才继续保留。
-
-
输出什么?
const getList = ([x, ...y]) => [x, y] const getUser = user => { name: user.name, age: user.age } const list = [1, 2, 3, 4] const user = { name: "Lydia", age: 21 } console.log(getList(list)) // [1, [2, 3, 4]] console.log(getUser(user)) // undefined
解析:
getList
函数接收一个数组作为其参数。在getList
函数的括号之间,我们立即解构这个数组。您可以将其视为:[x, ...y] = [1, 2, 3, 4]
使用剩余的参数
... y
,我们将所有剩余参数放在一个数组中。在这种情况下,其余的参数是2
,3
和4
。y
的值是一个数组,包含所有其余参数。在这种情况下,x
的值等于1
,所以当我们打印[x,y]
时,会打印[1,[2,3,4]]
。检查可以如下const [x, ...y] = [1, 2, 3, 4]; console.log(y) //[2, 3, 4]
getUser
函数接收一个对象。对于箭头函数,如果只返回一个值,我们不必编写花括号。但是,如果您想从一个箭头函数返回一个对象,您必须在圆括号之间编写它,否则不会返回任何值!下面的函数将返回一个对象:const getUser = user => ({ name: user.name, age: user.age })
由于在这种情况下不返回任何值,因此该函数返回
undefined
。
- 输出什么?
const info = {
[Symbol('a')]: 'b'
}
console.log(info) //{[Symbol('a')]: 'b'}
console.log(Object.keys(info)) // []
解析:
Symbol
类型是不可枚举的。Object.keys
方法返回对象上的所有可枚举的键属性。Symbol
类型是不可见的,并返回一个空数组。记录整个对象时,所有属性都是可见的,甚至是不可枚举的属性。这是
Symbol
的众多特性之一:除了表示完全唯一的值(防止对象意外名称冲突,例如当使用2个想要向同一对象添加属性的库时),您还可以隐藏
这种方式对象的属性(尽管不完全。你仍然可以使用Object.getOwnPropertySymbols()
方法访问Symbol
。
-
输出什么?
class Person { constructor() { this.name = "Lydia" } } Person = class AnotherPerson { constructor() { this.name = "Sarah" } } const member = new Person() console.log(member.name) //Sarah
解析:
我们可以将类设置为等于其他类/函数构造函数。在这种情况下,我们将
Person
设置为AnotherPerson
。这个构造函数的名字是Sarah
,所以新的Person
实例member
上的name属性是Sarah
。
- 输出什么?
function nums(a, b) {
if
(a > b)
console.log('a is bigger')
else
console.log('b is bigger')
return
a + b
}
console.log(nums(4, 2)) //a is bigger undefined
console.log(nums(1, 2)) //b is bigger undefined
解析:
在JavaScript中,我们不必显式地编写分号(
;
),但是JavaScript引擎仍然在语句之后自动添加分号。这称为自动分号插入。例如,一个语句可以是变量,或者像throw
、return
、break
这样的关键字。在这里,我们在新的一行上写了一个
return
语句和另一个值a + b
。然而,由于它是一个新行,引擎并不知道它实际上是我们想要返回的值。相反,它会在return
后面自动添加分号。你可以这样看:return; a + b
这意味着永远不会到达
a + b
,因为函数在return
关键字之后停止运行。如果没有返回值,就像这里,函数返回undefined
。注意,在if/else
语句之后没有自动插入!
- 输出什么?
const person = {
name: "Lydia",
age: 21
}
for (const [x, y] of Object.entries(person)) {
console.log(x, y) //name Lydia age 21
}
解析:
Object.entries()
方法返回一个给定对象自身可枚举属性的键值对数组,上述情况返回一个二维数组,数组每个元素是一个包含键和值的数组:[['name','Lydia'],['age',21]]
使用
for-of
循环,我们可以迭代数组中的每个元素,上述情况是子数组。我们可以使用const [x,y]
在for-of
循环中解构子数组。x
等于子数组中的第一个元素,y
等于子数组中的第二个元素。第一个子阵列是
[“name”,“Lydia”]
,其中x
等于name
,而y
等于Lydia
。第二个子阵列是[“age”,21]
,其中x
等于age
,而y
等于21
。
38.输出什么?
let name = 'Lydia'function getName() { console.log(name) let name = 'Sarah'}getName() //ReferenceError
解析:
每个函数都有其自己的执行上下文。
getName
函数首先在其自身的上下文(范围)内查找,以查看其是否包含我们尝试访问的变量name
。上述情况,getName
函数包含其自己的name
变量:我们用let
关键字和Sarah
的值声明变量name
。带有
let
关键字(和const
)的变量被提升,但是与var
不同,它不会被初始化。在我们声明(初始化)它们之前,无法访问它们。这称为“暂时性死区”。当我们尝试在声明变量之前访问变量时,JavaScript会抛出ReferenceError: Cannot access 'name' before initialization
。如果我们不在
getName
函数中声明name
变量,则javascript引擎会查看原型练。会找到其外部作用域有一个名为name
的变量,其值为Lydia
。在这种情况下,它将打印Lydia
:
-
输出什么?
const food = ['🍕', '🍫', '🥑', '🍔']const info = { favoriteFood: food[0] }info.favoriteFood = '🍝'console.log(food) //['🍕', '🍫', '🥑', '🍔']
解析:
我们将
info
对象上的favoriteFood
属性的值设置为披萨表情符号“🍕”的字符串。字符串是原始数据类型。在JavaScript中,原始数据类型通过值起作用在这种情况下,我们将
info
对象上的favoriteFood
属性的值设置为等于food
数组中的第一个元素的值,字符串为披萨表情符号('🍕'
)。字符串是原始数据类型,并且通过值进行交互,我们更改info
对象上favoriteFood
属性的值。food数组没有改变,因为favoriteFood的值只是该数组中第一个元素的值的复制,并且与该元素上的元素没有相同的内存引用食物[0]
。当我们记录食物时,它仍然是原始数组['🍕','🍫','🥑','🍔']
。若info.favoriteFood的值是引用类型值,如下:
const food = [{fruit: '🥑'} ,'🍕', '🍫', '🥑', '🍔'] const info = { favoriteFood: food[0] } info.favoriteFood.fruit = '🍝' console.log(food) //[{fruit: '🍝'}, '🍕', '🍫', '🥑', '🍔']
-
输出什么?
function* generatorOne() { yield ['a', 'b', 'c']; } function* generatorTwo() { yield* ['a', 'b', 'c']; } const one = generatorOne() const two = generatorTwo() console.log(one.next().value) // ['a', 'b', 'c'] console.log(two.next().value) // a
解析:
通过
yield
关键字, 我们在Generator
函数里执行yield
表达式. 通过yield*
关键字, 我们可以在一个Generator
函数里面执行(yield
表达式)另一个Generator
函数, 或可遍历的对象 (如数组).在函数
generatorOne
中, 我们通过yield
关键字 yield 了一个完整的数组['a', 'b', 'c']
。函数one
通过next
方法返回的对象的value
属性的值 (one.next().value
) 等价于数组['a', 'b', 'c']
.
-
哪些方法修改了原数组?
const emojis = ['✨', '🥑', '😍']emojis.map(x => x + '✨')emojis.filter(x => x !== '🥑')emojis.find(x => x !== '🥑')emojis.reduce((acc, cur) => acc + '✨')emojis.slice(1, 2, '✨') emojis.splice(1, 2, '✨')
解析:
使用
splice
方法,我们通过删除,替换或添加元素来修改原始数组。在这种情况下,我们从索引1中删除了2个元素(我们删除了'🥑'
和'😍'
),同时添加了✨emoji表情。map
,filter
和slice
返回一个新数组,find
返回一个元素,而reduce
返回一个减小的值。
-
输出什么?
function compareMembers(person1, person2 = person) { if (person1 !== person2) { console.log("Not the same!") } else { console.log("They are the same!") } } const person = { name: "Lydia" } compareMembers(person) // They are the same!
解析:
对象通过引用传递。当我们检查对象的严格相等性(===)时,我们正在比较它们的引用。
我们将“person2”的默认值设置为“person”对象,并将“person”对象作为“person1”的值传递。
这意味着两个值都引用内存中的同一位置,因此它们是相等的。
运行“ else”语句中的代码块,并记录
They are the same!
。
-
结果是什么?
Promise.resolve(5) //Promise {<fulfilled>: 5}
解析:
我们可以将我们想要的任何类型的值传递
Promise.resolve
,无论是否promise
。该方法本身返回带有已解析值的Promise
(<fulfilled>
)。如果您传递常规函数,它将是具有常规值的已解决promise
。如果你通过了promise,它将是一个已经resolved的且带有传的值的promise。上述情况,我们传了数字5,因此返回一个resolved状态的promise,resolve值为
5
-
输出什么?
const person = { name: "Lydia", age: 21 } const changeAge = (x = { ...person }) => x.age += 1 const changeAgeAndName = (x = { ...person }) => { x.age += 1 x.name = "Sarah" } changeAge(person) changeAgeAndName() console.log(person) //{ name: "Lydia", age: 22 }
解析:
函数
changeAge
和函数changeAgeAndName
有着不同的参数,定义一个 新 生成的对象{ ...person }
。这个对象有着所有person
对象 中 k/v 值的副本。首项, 我们调用
changeAge
函数并传递person
对象作为它的参数。这个函数对age
属性进行加一操作。person
现在是{ name: "Lydia", age: 22 }
。然后,我们调用函数
changeAgeAndName
,然而我们没有传递参数。取而代之,x
的值等价 new 生成的对象:{ ...person }
。因为它是一个新生成的对象,它并不会对对象person
造成任何副作用。person
仍然等价于{ name: "Lydia", age: 22 }
。
-
哪一个方法会返回
'Hello world!'
?const myMap = new Map()const myFunc = () => 'greeting'myMap.set(myFunc, 'Hello world!')//1myMap.get('greeting')//2 ---返回Hello worldmyMap.get(myFunc)//3myMap.get(() => 'greeting')
解析:
当通过
set
方法添加一个键值对,一个传递给set
方法的参数将会是键名,第二个参数将会是值。在这个case里,键名为 函数() => 'greeting'
,值为'Hello world'
。myMap
现在就是{ () => 'greeting' => 'Hello world!' }
。1 是错的,因为键名不是
'greeting'
而是() => 'greeting'
。3 是错的,因为我们给get
方法传递了一个新的函数。对象受 引用 影响。函数也是对象,因此两个函数严格上并不等价,尽管他们相同:他们有两个不同的内存引用地址。
-
将会发生什么?
let config = { alert: setInterval(() => { console.log('Alert!') }, 1000)}config = null
解析:
一般情况下当我们将对象赋值为
null
, 那些对象会被进行 垃圾回收(garbage collected) 因为已经没有对这些对象的引用了。然而,setInterval
的参数是一个箭头函数(所以上下文绑定到对象config
了),回调函数仍然保留着对config
的引用。只要存在引用,对象就不会被垃圾回收。因为没有被垃圾回收,setInterval
的回调每1000ms (1s)会被调用一次。
-
输出什么?
console.log(`${(x => x)('I love')} to program`) //I love to program
解析:
带有模板字面量的表达式首先被执行。相当于字符串会包含表达式,这个立即执行函数
(x => x)('I love')
返回的值. 我们向箭头函数x => x
传递'I love'
作为参数。x
等价于返回的'I love'
。这就是结果I love to program
。x => x 匿名函数是
(x) => {return x}
的缩写,当只有一个参数时参数可以省略小括号,当只含有一个表达式时,可省略掉了{ ... }和return。
-
输出什么?
class Counter { #number = 10 increment() { this.#number++ } getNum() { return this.#number } } const counter = new Counter() counter.increment() console.log(counter.#number) //SyntaxError
解析:
在 ES2020 中,通过
#
我们可以给 class 添加私有变量。在 class 的外部我们无法获取该值。当我们尝试输出counter.#number
,语法错误被抛出:我们无法在 classCounter
外部获取它!
-
以下哪一项会对对象
person
有副作用?const person = { name: "Lydia Hallie", address: { street: "100 Main St" } }; Object.freeze(person); //A: person.name = "Evan Bacon" //B: delete person.address //C: person.address.street = "101 Main St" //D: person.pet = { name: "Mara" }
解析:
使用方法
Object.freeze
对一个对象进行 冻结。不能对属性进行添加,修改,删除。然而,它仅 对对象进行 浅 冻结,意味着只有 对象中的 直接 属性被冻结。如果属性是另一个 object,像案例中的
address
,address
中的属性没有被冻结,仍然可以被修改。 -
以下哪一项会对对象
person
有副作用?const person = { name: "Lydia Hallie" };Object.seal(person);//A: person.name = "Evan Bacon"//B: person.age = 21//C: delete person.name//D: Object.assign(person, { age: 21 })
解析:
使用
Object.seal
我们可以防止新属性 被添加,或者存在属性 被移除.然而,你仍然可以对存在属性进行更改。
-
我们需要向对象
person
添加什么,以致执行[...person]
时获得形如["Lydia Hallie", 21]
的输出?const person = { name: "Lydia Hallie", age: 21}[...person] // ["Lydia Hallie", 21]
解析:
*[Symbol.iterator]() { for (let x in this) yield* Object.values(this) }
对象默认并不是可迭代的。如果迭代规则被定义,则一个对象是可迭代的(An iterable is an iterable if the iterator protocol is present)。我们可以通过添加迭代器symbol
[Symbol.iterator]
来定义迭代规则,其返回一个 generator 对象,比如说构建一个 generator 函数*[Symbol.iterator]() {}
。如果我们想要返回数组["Lydia Hallie", 21]
:yield* Object.values(this)
,这个 generator 函数一定要 yield 对象person
的Object.values
。
-
哪一个选项会导致报错?
const emojis = ["🎄", "🎅🏼", "🎁", "⭐"]; /* 1 */ emojis.push("🦌"); /* 2 */ emojis.splice(0, 2); /* 3 */ emojis = [...emojis, "🥂"]; //报错 const不能重新赋值 /* 4 */ emojis.length = 0;
解析:
const
关键字意味着我们不能 重定义 变量中的值,它 仅可读。然,值本身不可修改。数组emojis
中的值可被修改,如 push 新的值, 拼接,又或者将数组的长度设置为0
-
输出什么?
class Bird { constructor() { console.log("I'm a bird. 🦢"); } } class Flamingo extends Bird { constructor() { console.log("I'm pink. 🌸"); super(); } } const pet = new Flamingo();
解析:
我们创建了类
Flamingo
的实例pet
。当我们实例化这个实例,Flamingo
中的constructor
被调用。首相,输出"I'm pink. 🌸"
, 之后我们调用super()
。super()
调用父类的构造函数,Bird
。Bird
的构造函数被调用,并输出"I'm a bird. 🦢"
。
-
选择哪一个?
const teams = [ { name: "Team 1", members: ["Paul", "Lisa"] }, { name: "Team 2", members: ["Laura", "Tim"] } ]; function* getMembers(members) { for (let i = 0; i < members.length; i++) { yield members[i]; } } function* getTeams(teams) { for (let i = 0; i < teams.length; i++) { // ✨ SOMETHING IS MISSING HERE ✨ } } const obj = getTeams(teams); obj.next(); // { value: "Paul", done: false } obj.next(); // { value: "Lisa", done: false } //A: yield getMembers(teams[i].members) //B: yield* getMembers(teams[i].members) ---true //C: return getMembers(teams[i].members) //D: return yield getMembers(teams[i].members)
解析:
为了遍历
teams
数组中对象的属性members
中的每一项,我们需要将teams[i].members
传递给 Generator 函数getMembers
。Generator 函数返回一个 generator 对象。为了遍历这个 generator 对象中的每一项,我们需要使用yield*
.如果我们没有写
yield
,return yield
或者return
,整个 Generator 函数不会第一时间 return 当我们调用next
方法.
(记录在公众号或各帖子上看到的觉得有意思的题目,欢迎关注一起学习)