写在前面
我们已经根据这些测验答案的统计数据发布了一篇包含最难主题的文章。为了识别这些主题,我们将所有已发布的测验按主题进行划分,它一共有15个主题,并计算每个主题的平均百分比。
这个实验最有趣的地方在于,除了计算正确答案之外,我们还对 Javascript 最困难的方面进行了调查,结果大相径庭。
在调查中,大部分受访者回答他们最困难的话题是 Promises,而据统计,Promises 仅排在第 4 位。
我们应该在每项任务旁边留下关于正确答案百分比的注释。你不应该将此笔记视为恒定的并且 100% 反映现实。
首先,新程序员每天都会回答我们发布的测验并更改统计数据,文章中出现的所有数字都是在文章发表时确定的。
其次,一些答案当然是不小心猜到了,或者点错了地方等等。不过,在采访了大量的 JS 开发人员之后,我们可以自信地说,这个统计数据清楚地反映了现实。
那么,让我们看看 TOP-5 最难的 JS 挑战并进行分析,剧透:只有 8% 的响应者正确解决了 TOP-1 测验。
Top-5、默认函数参数和函数长度属性,18% 的人回答正确
function foo(a, b = 10, c) {
console.log(foo.length);
}
foo(1, 2, 3);
这里的关键点是函数的长度属性应该提供有关函数的元数的信息,该信息是作为她的正式定义参数的数量计算的。
ES2015 中引入了默认参数功能。在此之前,所有函数参数都被视为形式参数,函数长度属性用于返回所有函数参数编号。
随着默认参数的引入,长度属性的行为发生了变化。由于很明显带有默认值的参数是可选的,所以这样的参数不包括在函数的长度中。
按照常识,默认值参数后面的所有参数也是可选的。因此,它们也不包含在函数的长度属性中。
TOP-4、Object.defineProperty 方法及其默认参数,14% 的人回答正确
const obj = {};
Object.defineProperty(obj, 'myCompany', {
value: 'intspirit'
});
console.log(obj.myCompany);
delete obj.myCompany;
console.log(obj.myCompany);
大多数受访者对此测验的回答未定义。原因:不知道 Object.defineProperty() 方法是如何工作的。
Object.defineProperty() 方法定义对象的新属性,或修改对象的现有属性。
语法:
Object.defineProperty(obj, prop, descriptors)
看这里:
- obj — 要在其上定义或修改属性的对象。
- prop — 要定义或修改的属性的名称。
- descriptors — 属性的描述符。
有两种类型的描述符:数据描述符(值、可写、可枚举、可配置)和访问描述符(get 和 set)。在此示例的上下文中,我们对数据描述符感兴趣。
默认情况下,使用 Object.defineProperty() 添加的属性不可写、不可枚举且不可配置。
可配置属性指定是否可以从对象中删除属性,以及将来是否可以更改属性描述符。如果为真,则该属性将可用于删除和修改其描述符,如果为假,则不可以修改。默认设置为 false。
因此,测验的正确答案是 intspirit,删除该属性的尝试将被忽略。如果你在严格模式下运行代码,你会得到一个错误:
TypeError: Cannot delete property ‘myCompany’ of #<Object>
Top-3、Array.map & parseInt,14% 的人回答正确
const numbers = ['9', '10', '11'].map(parseInt);
console.log(numbers);
Array.map() 方法接受一个带有 3 个参数的回调函数。我们只会对前两个感兴趣:值和索引。
parseInt 函数有 2 个参数:一个要转换为数字的字符串和一个基数。
所以在我们的例子中, parseInt 将使用以下参数调用:
parseInt('9', 0);
parseInt('10', 1);
parseInt('11', 2);
要了解 parseInt 如何处理这些基数,让我们看一下 mdn 中的基数参数描述:
radix — 2 到 36 之间的整数,表示字符串的基数(数学数字系统中的基数)。如果超出此范围,该函数将始终返回 NaN。如果 是0 或未提供,JavaScript 假定如下:
1). 如果输入字符串以 0x 或 0X(零,后跟小写或大写 X)开头,去除了前导空格和可能的 +/- 符号,则假定基数为 16,字符串的其余部分被解析为一个十六进制数。
2). 如果输入字符串以任何其他值开头,则基数为 10(十进制)。
根据这个定义,我们得到以下结果:
- parseInt('9', 0) -> radix 0 等同于没有基数的调用。因为第一个参数不是以 0x 或 0X 开头,所以 radix 将默认为10 -> parseInt(‘9’, 10) -> 9
- parseInt('10', 1)-> 1 — 无效基数(超出范围)-> NaN
- parseInt('11', 2) -> 2 — 有效基数,二进制中的 11 是 3 -> 3
TOP-2、使用 Object.create 和 Object.assign 克隆对象。11% 的人回答正确
function User() {
this.verified = true;
}
const user = new User();
const admin = Object.create(user);
const clone1 = { admin };
const clone2 = Object.assign({}, admin);
console.log(admin.verified, clone1.verified, clone2.verified);
我们的频道中有一系列测验,专门讨论 Object.assign 和 ...spread 运算符的工作差异。对于任何对深度 JS 感兴趣的人,我们强烈建议你解决所有这些问题。
在每个测验下,你都会找到关于它是如何工作的详细说明。这只是对本示例中的代码如何工作的简要描述,因为事实证明它是整个测验系列中的受访者最困难的。
所以..让我们了解这个例子中发生了什么。
1).将已验证属性设置为 true 的用户构造函数及其实例被创建:
function User() {
this.verified = true;
}
const user = new User();
2).使用用户对象作为原型创建管理对象。根据 mdn网站的介绍:
Object.create() 方法创建一个新对象,使用现有对象作为新创建对象的原型。
const admin = Object.create(user);
3). 创建了两个克隆:一个使用 ...spread 运算符,另一个使用 Object.assign:
const clone1 = { admin };
const clone2 = Object.assign({}, admin);
你知道rest和spread算子的区别吗?两者都使用三个点(…),但这两个运算符不一样。
它们之间的主要区别在于,rest 运算符的目标是在扩展运算符将可迭代对象扩展为单个元素时,将其余一些提供的值放入一个数组中。
4).查看验证的属性是否被克隆:
console.log(admin.verified, clone1.verified, clone2.verified); // true, undefined, undefined
admin 对象显然将其验证属性设置为 true,因为它使用用户作为其原型。但是,如你所见,没有一个克隆具有经过验证的属性。这是因为 ...spread 运算符和 Object.assign 在克隆时都忽略了原型。
这些对象的原型:
admin.__proto__ User { verified: true },
clone1.__proto__ [Object: null prototype] {},
clone2.__proto__ [Object: null prototype] {}
克隆一个对象,包括它的原型:
const clone1 = { __proto__: Object.getPrototypeOf(obj), obj };
const clone2 = Object.assign(Object.create(Object.getPrototypeOf(obj)), obj);
注意:__proto__ 只是 Web 浏览器中的强制功能,一般 JS 引擎中没有。
TOP-1、字符串函数和 instanceof 运算符,8%的人回答正确
var str = 'Hello';
var str2 = String('Hello');
console.log(str instanceof String);
console.log(str2 instanceof String);
这是一百多个特别挑选的非平凡任务中最困难的一个任务。只有 2 个正确答案——其中一个是频道管理员给出的,呵呵 :)
有什么难的?
如果你查看答案的统计数据,你会发现受访者的意见在两个错误答案之间大致相等。
在本文发表时——38% 的开发人员认为这两个表达式都会返回 true,35% 的开发人员认为只有第二个语句是true。下半场更接近了。
可以假设那些回答该表达式的人
‘Hello’ instanceof String 为false,而 String(‘Hello’) instanceof String 为true,知道 instanceof 运算符仅适用于对象,不适用于原语,但对 String 函数返回的内容感到困惑。
事实上,这两种说法都是错误的。因为:
- instanceof 运算符仅适用于对象。
- 字符串文字“Hello”是原始的。
- 非构造函数上下文中的字符串调用(不使用 new 关键字调用)返回一个原始字符串。
到这里,我就把这个5个问题分解完了,希望对你有用。
总结
关于JavaScript的挑战学习测试题,其实有很多,我这里只是选取了一些看起来容易搞混出错的题目,希望你能从中学习到一些新东西。