英文原文地址:https://dev.to/macmacky/70-javascript-interview-questions-5gfi#61-what-are-the-ways-of-making-objects-in-javascript
上一篇1-34个JavaScript知识点总结请看《70个JavaScript知识点详细总结(上)【实践】》
好了,废话少说,进入正题:
35. 手动实现Array.prototype.filter方法
filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。
function filter(arr, filterCallback) {
// 首先,检查传递的参数是否正确。
if (!Array.isArray(arr) || !arr.length || typeof filterCallback !== 'function')
{
return [];
} else {
let result = [];
// 每次调用此函数时,我们都会创建一个 result 数组
// 因为我们不想改变原始数组。
for (let i = 0, len = arr.length; i < len; i++) {
// 检查 filterCallback 的返回值是否是真值
if (filterCallback(arr[i], i, arr)) {
// 如果条件为真,则将数组元素 push 到 result 中
result.push(arr[i]);
}
}
return result; // return the result array
}
}
36. 手动实现Array.prototype.reduce方法
reduce() 方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。
function reduce(arr, reduceCallback, initialValue) {
// 首先,检查传递的参数是否正确。
if (!Array.isArray(arr) || !arr.length || typeof reduceCallback !== 'function')
{
return [];
} else {
// 如果没有将initialValue传递给该函数,我们将使用第一个数组项作为initialValue
let hasInitialValue = initialValue !== undefined;
let value = hasInitialValue ? initialValue : arr[0];
、
// 如果有传递 initialValue,则索引从 1 开始,否则从 0 开始
for (let i = hasInitialValue ? 0 : 1, len = arr.length; i < len; i++) {
value = reduceCallback(value, arr[i], i, arr);
}
return value;
}
}
37. arguments 的对象是什么?
arguments对象是函数中传递的参数值的集合。它是一个类似数组的对象,因为它有一个length属性,我们可以使用数组索引表示法arguments[1]来访问单个值,但它没有数组中的内置方法,如:forEach、reduce、filter和map。
我们可以使用Array.prototype.slice将arguments对象转换成一个数组。
function one() {
return Array.prototype.slice.call(arguments);
}
注意:箭头函数中没有arguments对象。
function one() {
return arguments;
}
const two = function () {
return arguments;
}
const three = function three() {
return arguments;
}
const four = () => arguments;
four(); // Throws an error - arguments is not defined
当我们调用函数four时,它会抛出一个ReferenceError: arguments is not defined error。使用rest语法,可以解决这个问题。
const four = (...args) => args;
这会自动将所有参数值放入数组中。
38. 如何创建一个没有 prototype(原型)的对象?
我们可以使用Object.create方法创建没有原型的对象。
const o1 = {};
console.log(o1.toString()); // [object Object]
const o2 = Object.create(null);
console.log(o2.toString());
// throws an error o2.toString is not a function
39. 为什么在调用这个函数时,代码中的b会变成一个全局变量?
function myFunc() {
let a = b = 0;
}
myFunc();
原因是赋值运算符是从右到左的求值的。这意味着当多个赋值运算符出现在一个表达式中时,它们是从右向左求值的。所以上面代码变成了这样:
function filter(arr, filterCallback) {
// 首先,检查传递的参数是否正确。
if (!Array.isArray(arr) || !arr.length || typeof filterCallback !== 'function')
{
return [];
} else {
let result = [];
// 每次调用此函数时,我们都会创建一个 result 数组
// 因为我们不想改变原始数组。
for (let i = 0, len = arr.length; i < len; i++) {
// 检查 filterCallback 的返回值是否是真值
if (filterCallback(arr[i], i, arr)) {
// 如果条件为真,则将数组元素 push 到 result 中
result.push(arr[i]);
}
}
return result; // return the result array
}
}
首先,表达式b = 0求值,在本例中b没有声明。因此,JS引擎在这个函数外创建了一个全局变量b,之后表达式b = 0的返回值为0,并赋给新的局部变量a。
我们可以通过在赋值之前先声明变量来解决这个问题。
function myFunc() {
let a,b;
a = b = 0;
}
myFunc();
40. ECMAScript 是什么?
ECMAScript 是编写脚本语言的标准,这意味着JavaScript遵循ECMAScript标准中的规范变化,因为它是JavaScript的蓝图。
ECMAScript 和 Javascript,本质上都跟一门语言有关,一个是语言本身的名字,一个是语言的约束条件只不过发明JavaScript的那个人(Netscape公司),把东西交给了ECMA(European Computer Manufacturers Association),这个人规定一下他的标准,因为当时有java语言了,又想强调这个东西是让ECMA这个人定的规则,所以就这样一个神奇的东西诞生了,这个东西的名称就叫做ECMAScript。
javaScript = ECMAScript + DOM + BOM(自认为是一种广义的JavaScript)
ECMAScript说什么JavaScript就得做什么!
JavaScript(狭义的JavaScript)做什么都要问问ECMAScript我能不能这样干!如果不能我就错了!能我就是对的!
——突然感觉JavaScript好没有尊严,为啥要搞个人出来约束自己,
那个人被创造出来也好委屈,自己被创造出来完全是因为要约束JavaScript。
41. ES6或ECMAScript 2015有哪些新特性?
- 箭头函数
- 类
- 模板字符串
- 加强的对象字面量
- 对象解构
- Promise
- 生成器
- 模块
- Symbol
- 代理
- Set
- 函数默认参数
- rest 和展开
- 块作用域
42. var,let和const的区别是什么?
var声明的变量会挂载在window上,而let和const声明的变量不会:
function reduce(arr, reduceCallback, initialValue) {
// 首先,检查传递的参数是否正确。
if (!Array.isArray(arr) || !arr.length || typeof reduceCallback !== 'function')
{
return [];
} else {
// 如果没有将initialValue传递给该函数,我们将使用第一个数组项作为initialValue
let hasInitialValue = initialValue !== undefined;
let value = hasInitialValue ? initialValue : arr[0];
、
// 如果有传递 initialValue,则索引从 1 开始,否则从 0 开始
for (let i = hasInitialValue ? 0 : 1, len = arr.length; i < len; i++) {
value = reduceCallback(value, arr[i], i, arr);
}
return value;
}
}
var声明变量存在变量提升,let和const不存在变量提升:
console.log(a); // undefined ===> a已声明还没赋值,默认得到undefined值
var a = 100;
console.log(b); // 报错:b is not defined ===> 找不到b这个变量
let b = 10;
console.log(c); // 报错:c is not defined ===> 找不到c这个变量
const c = 10;
let和const声明形成块作用域
if(1){
var a = 100;
let b = 10;
}
console.log(a); // 100
console.log(b) // 报错:b is not defined ===> 找不到b这个变量
-------------------------------------------------------------
if(1){
var a = 100;
const c = 1;
}
console.log(a); // 100
console.log(c) // 报错:c is not defined ===> 找不到c这个变量
同一作用域下let和const不能声明同名变量,而var可以
var a = 100;
console.log(a); // 100
var a = 10;
console.log(a); // 10
-------------------------------------
let a = 100;
let a = 10;
// 控制台报错:Identifier 'a' has already been declared ===> 标识符a已经被声明了。
暂存死区
var a = 100;
if(1){
a = 10;
//在当前块作用域中存在a使用let/const声明的情况下,给a赋值10时,只会在当前作用域找变量a,
// 而这时,还未到声明时候,所以控制台Error:a is not defined
let a = 1;
}
const
/*
* 1、一旦声明必须赋值,不能使用null占位。
*
* 2、声明后不能再修改
*
* 3、如果声明的是复合类型数据,可以修改其属性
*
* */
const a = 100;
const list = [];
list[0] = 10;
console.log(list); // [10]
const obj = {a:100};
obj.name = 'apple';
obj.a = 10000;
console.log(obj); // {a:10000,name:'apple'}
43. 什么是箭头函数?
箭头函数表达式的语法比函数表达式更简洁,并且没有自己的this,arguments,super或new.target。箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数。
//ES5 Version
var getCurrentDate = function (){
return new Date();
}
//ES6 Version
const getCurrentDate = () => new Date();
在本例中,ES5 版本中有function(){}声明和return关键字,这两个关键字分别是创建函数和返回值所需要的。在箭头函数版本中,我们只需要()括号,不需要 return 语句,因为如果我们只有一个表达式或值需要返回,箭头函数就会有一个隐式的返回。
function one() {
return Array.prototype.slice.call(arguments);
}
我们还可以在箭头函数中使用与函数表达式和函数声明相同的参数。如果我们在一个箭头函数中有一个参数,则可以省略括号。
function one() {
return arguments;
}
const two = function () {
return arguments;
}
const three = function three() {
return arguments;
}
const four = () => arguments;
four(); // Throws an error - arguments is not defined
箭头函数不能访问arguments对象。所以调用第一个getArgs函数会抛出一个错误。相反,我们可以使用rest参数来获得在箭头函数中传递的所有参数。
const four = (...args) => args;
箭头函数没有自己的this值。它捕获词法作用域函数的this值,在此示例中,addAll函数将复制computeResult 方法中的this值,如果我们在全局作用域声明箭头函数,则this值为 window 对象。
44. 什么是类?
类(class)是在 JS 中编写构造函数的新方法。它是使用构造函数的语法糖,在底层中使用仍然是原型和基于原型的继承。
const o1 = {};
console.log(o1.toString()); // [object Object]
const o2 = Object.create(null);
console.log(o2.toString());
// throws an error o2.toString is not a function
重写方法并从另一个类继承。
//ES5 Version
Employee.prototype = Object.create(Person.prototype);
function Employee(firstName, lastName, age, address, jobTitle, yearStarted) {
Person.call(this, firstName, lastName, age, address);
this.jobTitle = jobTitle;
this.yearStarted = yearStarted;
}
Employee.prototype.describe = function () {
return `I am ${this.getFullName()} and I have a position of ${this.jobTitle}
and I started at ${this.yearStarted}`;
}
Employee.prototype.toString = function () {
return "[object Employee]";
}
//ES6 Version
class Employee extends Person { //Inherits from "Person" class
constructor(firstName, lastName, age, address, jobTitle, yearStarted) {
super(firstName, lastName, age, address);
this.jobTitle = jobTitle;
this.yearStarted = yearStarted;
}
describe() {
return `I am ${this.getFullName()} and I have a position of ${this.jobTitle}
and I started at ${this.yearStarted}`;
}
toString() { // Overriding the "toString" method of "Person"
return "[object Employee]";
}
}
所以我们要怎么知道它在内部使用原型?
class Something {
}
function AnotherSomething(){
}
const as = new AnotherSomething();
const s = new Something();
console.log(typeof Something); // "function"
console.log(typeof AnotherSomething); // "function"
console.log(as.toString()); // "[object Object]"
console.log(as.toString()); // "[object Object]"
console.log(as.toString === Object.prototype.toString); // true
console.log(s.toString === Object.prototype.toString); // true
45. 什么是模板字符串?
模板字符串是在 JS 中创建字符串的一种新方法。我们可以通过使用反引号使模板字符串化。
function myFunc() {
let a = b = 0;
}
myFunc();
在 ES5 中我们需要使用一些转义字符来达到多行的效果,在模板字符串不需要这么麻烦:
function myFunc() {
let a = (b = 0);
}
myFunc();
在ES5版本中,我们需要添加\n以在字符串中添加新行。在模板字符串中,我们不需要这样做。
//ES5 Version
function greet(name) {
return 'Hello ' + name + '!';
}
//ES6 Version
function greet(name) {
return `Hello ${name} !`;
}
在 ES5 版本中,如果需要在字符串中添加表达式或值,则需要使用+运算符。在模板字符串s中,我们可以使用${expr}嵌入一个表达式,这使其比 ES5 版本更整洁。
46. 什么是对象解构?
对象析构是从对象或数组中获取或提取值的一种新的、更简洁的方法。假设有如下的对象:
function myFunc() {
let a,b;
a = b = 0;
}
myFunc();
从对象获取属性,早期方法是创建一个与对象属性同名的变量。这种方法很麻烦,因为我们要为每个属性创建一个新变量。假设我们有一个大对象,它有很多属性和方法,用这种方法提取属性会很麻烦。
var firstName = employee.firstName;
var lastName = employee.lastName;
var position = employee.position;
var yearHired = employee.yearHired;
使用解构方式语法就变得简洁多了:
{ firstName, lastName, position, yearHired } = employee;
我们还可以为属性取别名:
let { firstName: fName, lastName: lName, position, yearHired } = employee;
当然如果属性值为 undefined 时,我们还可以指定默认值:
let { firstName = "Mark", lastName: lName, position, yearHired } = employee;
47. 什么是 ES6 模块?
模块使我们能够将代码基础分割成多个文件,以获得更高的可维护性,并且避免将所有代码放在一个大文件中。在 ES6 支持模块之前,有两个流行的模块。
- CommonJS-Node.js
- AMD(异步模块定义)-浏览器
基本上,使用模块的方式很简单,import用于从另一个文件中获取功能或几个功能或值,同时export用于从文件中公开功能或几个功能或值。
导出
使用 ES5 (CommonJS)
// 使用 ES5 CommonJS - helpers.js
exports.isNull = function (val) {
return val === null;
}
exports.isUndefined = function (val) {
return val === undefined;
}
exports.isNullOrUndefined = function (val) {
return exports.isNull(val) || exports.isUndefined(val);
}
使用 ES6 模块
// 使用 ES6 Modules - helpers.js
export function isNull(val){
return val === null;
}
export function isUndefined(val) {
return val === undefined;
}
export function isNullOrUndefined(val) {
return isNull(val) || isUndefined(val);
}
在另一个文件中导入函数
// 使用 ES5 (CommonJS) - index.js
const helpers = require('./helpers.js'); // helpers is an object
const isNull = helpers.isNull;
const isUndefined = helpers.isUndefined;
const isNullOrUndefined = helpers.isNullOrUndefined;
// or if your environment supports Destructuring
const { isNull, isUndefined, isNullOrUndefined } = require('./helpers.js');
-------------------------------------------------------
// ES6 Modules - index.js
import * as helpers from './helpers.js'; // helpers is an object
// or
import { isNull, isUndefined, isNullOrUndefined as isValid } from './helpers.js';
// using "as" for renaming named exports
在文件中导出单个功能或默认导出
使用 ES5 (CommonJS)
// 使用 ES5 (CommonJS) - index.js
class Helpers {
static isNull(val) {
return val === null;
}
static isUndefined(val) {
return val === undefined;
}
static isNullOrUndefined(val) {
return this.isNull(val) || this.isUndefined(val);
}
}
module.exports = Helpers;
使用ES6 Modules
// 使用 ES6 Modules - helpers.js
class Helpers {
static isNull(val) {
return val === null;
}
static isUndefined(val) {
return val === undefined;
}
static isNullOrUndefined(val) {
return this.isNull(val) || this.isUndefined(val);
}
}
export default Helpers
从另一个文件导入单个功能
使用ES5 (CommonJS)
// 使用 ES5 (CommonJS) - index.js
const Helpers = require('./helpers.js');
console.log(Helpers.isNull(null));
使用 ES6 Modules
import Helpers from '.helpers.js'
console.log(Helpers.isNull(null));
48. 什么是Set对象,它是如何工作的?
Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
我们可以使用Set构造函数创建Set实例。
const set1 = new Set();
const set2 = new Set(["a","b","c","d","d","e"]);
我们可以使用add方法向Set实例中添加一个新值,因为add方法返回Set对象,所以我们可以以链式的方式再次使用add。如果一个值已经存在于Set对象中,那么它将不再被添加。
set2.add("f");
set2.add("g").add("h").add("i").add("j").add("k").add("k");
// 后一个“k”不会被添加到set对象中,因为它已经存在了
我们可以使用has方法检查Set实例中是否存在特定的值。
set2.has("a") // true
set2.has("z") // true
我们可以使用size属性获得Set实例的长度。
set2.size // returns 10
可以使用clear方法删除 Set 中的数据。
set2.clear();
我们可以使用Set对象来删除数组中重复的元素。
const numbers = [1, 2, 3, 4, 5, 6, 6, 7, 8, 8, 5];
const uniqueNums = [...new Set(numbers)]; // [1,2,3,4,5,6,7,8]
49. 什么是回调函数?
回调函数是一段可执行的代码段,它作为一个参数传递给其他的代码,其作用是在需要的时候方便调用这段(回调函数)代码。
在JavaScript中函数也是对象的一种,同样对象可以作为参数传递给函数,因此函数也可以作为参数传递给另外一个函数,这个作为参数的函数就是回调函数。
const btnAdd = document.getElementById('btnAdd');
btnAdd.addEventListener('click', function clickCallback(e) {
// do something useless
});
在本例中,我们等待id为btnAdd的元素中的click事件,如果它被单击,则执行clickCallback函数。回调函数向某些数据或事件添加一些功能。
数组中的reduce、filter和map方法需要一个回调作为参数。回调的一个很好的类比是,当你打电话给某人,如果他们不接,你留下一条消息,你期待他们回调。调用某人或留下消息的行为是事件或数据,回调是你希望稍后发生的操作。
50. Promise 是什么?
Promise 是异步编程的一种解决方案:从语法上讲,promise是一个对象,从它可以获取异步操作的消息;从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。promise有三种状态:pending(等待态),fulfiled(成功态),rejected(失败态);状态一旦改变,就不会再变。创造promise实例后,它会立即执行。
fs.readFile('somefile.txt', function (e, data) {
if (e) {
console.log(e);
}
console.log(data);
});
如果我们在回调内部有另一个异步操作,则此方法存在问题。我们将有一个混乱且不可读的代码。此代码称为“回调地狱”。
// 回调地狱
fs.readFile('somefile.txt', function (e, data) {
//your code here
fs.readdir('directory', function (e, files) {
//your code here
fs.mkdir('directory', function (e) {
//your code here
})
})
})
如果我们在这段代码中使用promise,它将更易于阅读、理解和维护。
promReadFile('file/path')
.then(data => {
return promReaddir('directory');
})
.then(data => {
return promMkdir('directory');
})
.catch(e => {
console.log(e);
})
promise有三种不同的状态:
- pending:初始状态,完成或失败状态的前一个状态
- fulfilled:操作成功完成
- rejected:操作失败
pending 状态的 Promise 对象会触发 fulfilled/rejected 状态,在其状态处理方法中可以传入参数/失败信息。当操作成功完成时,Promise 对象的 then 方法就会被调用;否则就会触发 catch。如:
const myFirstPromise = new Promise((resolve, reject) => {
setTimeout(function(){
resolve("成功!");
}, 250);
});
myFirstPromise.then((data) => {
console.log("Yay! " + data);
}).catch((e) => {...});
51. 什么是 async/await 及其如何工作?
async/await是 JS 中编写异步或非阻塞代码的新方法。它建立在Promises之上,让异步代码的可读性和简洁度都更高。
async/await是 JS 中编写异步或非阻塞代码的新方法。它建立在Promises之上,相对于 Promise 和回调,它的可读性和简洁度都更高。但是,在使用此功能之前,我们必须先学习Promises的基础知识,因为正如我之前所说,它是基于Promise构建的,这意味着幕后使用仍然是Promise。
使用 Promise
var a = 100;
console.log(a,window.a); // 100 100
let b = 10;
console.log(b,window.b); // 10 undefined
const c = 1;
console.log(c,window.c); // 1 undefined
使用async/await
在async/await,我们使用 tru/catch 语法来捕获异常。
console.log(a); // undefined ===> a已声明还没赋值,默认得到undefined值
var a = 100;
console.log(b); // 报错:b is not defined ===> 找不到b这个变量
let b = 10;
console.log(c); // 报错:c is not defined ===> 找不到c这个变量
const c = 10;
注意:使用 async关键声明函数会隐式返回一个Promise。
if(1){
var a = 100;
let b = 10;
}
console.log(a); // 100
console.log(b) // 报错:b is not defined ===> 找不到b这个变量
-------------------------------------------------------------
if(1){
var a = 100;
const c = 1;
}
console.log(a); // 100
console.log(c) // 报错:c is not defined ===> 找不到c这个变量
注意:await关键字只能在async function中使用。在任何非async function的函数中使用await关键字都会抛出错误。await关键字在执行下一行代码之前等待右侧表达式(可能是一个Promise)返回。
var a = 100;
console.log(a); // 100
var a = 10;
console.log(a); // 10
-------------------------------------
let a = 100;
let a = 10;
// 控制台报错:Identifier 'a' has already been declared ===> 标识符a已经被声明了。
52. 展开(spread )运算符和 剩余(Rest) 运算符有什么区别?
展开运算符(spread)是三个点(...),可以将一个数组转为用逗号分隔的参数序列。说的通俗易懂点,有点像化骨绵掌,把一个大元素给打散成一个个单独的小元素。
剩余运算符也是用三个点(...)表示,它的样子看起来和展开操作符一样,但是它是用于解构数组和对象。在某种程度上,剩余元素和展开元素相反,展开元素会“展开”数组变成多个元素,剩余元素会收集多个元素和“压缩”成一个单一的元素。
var a = 100;
if(1){
a = 10;
//在当前块作用域中存在a使用let/const声明的情况下,给a赋值10时,只会在当前作用域找变量a,
// 而这时,还未到声明时候,所以控制台Error:a is not defined
let a = 1;
}
在本例中,我们在调用add函数时使用了展开操作符,对nums数组进行展开。所以参数a的值是5 ,b的值是6,所以sum 是11。
/*
* 1、一旦声明必须赋值,不能使用null占位。
*
* 2、声明后不能再修改
*
* 3、如果声明的是复合类型数据,可以修改其属性
*
* */
const a = 100;
const list = [];
list[0] = 10;
console.log(list); // [10]
const obj = {a:100};
obj.name = 'apple';
obj.a = 10000;
console.log(obj); // {a:10000,name:'apple'}
在本例中,我们有一个add函数,它接受任意数量的参数,并将它们全部相加,然后返回总数。
const [first, ...others] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(others); // [2,3,4,5]
这里,我们使用剩余操作符提取所有剩余的数组值,并将它们放入除第一项之外的其他数组中。
53. 什么是默认参数?
默认参数是在 JS 中定义默认变量的一种新方法,它在ES6或ECMAScript 2015版本中可用。
//ES5 Version
var getCurrentDate = function (){
return new Date();
}
//ES6 Version
const getCurrentDate = () => new Date();
我们还可以在默认参数中使用解构。
//ES5 Version
function greet(name) {
return 'Hello ' + name + '!';
}
//ES6 Version
const greet = (name) => `Hello ${name}`;
const greet2 = name => `Hello ${name}`;
我们还可以使用先定义的参数再定义它们之后的参数。
const getArgs = () => arguments
const getArgs2 = (...rest) => rest
54. 什么是包装对象(wrapper object)?
我们现在复习一下JS的数据类型,JS数据类型被分为两大类,基本类型和引用类型。
基本类型:Undefined,Null,Boolean,Number,String,Symbol,BigInt
引用类型:Object,Array,Date,RegExp等,说白了就是对象。
其中引用类型有方法和属性,但是基本类型是没有的,但我们经常会看到下面的代码:
const data = {
result: 0,
nums: [1, 2, 3, 4, 5],
computeResult() {
// 这里的“this”指的是“data”对象
const addAll = () => {
return this.nums.reduce((total, cur) => total + cur, 0)
};
this.result = addAll();
}
};
name类型是 string,属于基本类型,所以它没有属性和方法,但是在这个例子中,我们调用了一个toUpperCase()方法,它不会抛出错误,还返回了对象的变量值。
原因是基本类型的值被临时转换或强制转换为对象,因此name变量的行为类似于对象。除null和undefined之外的每个基本类型都有自己包装对象。也就是:String,Number,Boolean,Symbol和BigInt。在这种情况下,name.toUpperCase()在幕后看起来如下:
console.log(new String(name).toUpperCase()); // "MARKO"
在完成访问属性或调用方法之后,新创建的对象将立即被丢弃。
55. 隐式和显式转换有什么区别)?
隐式强制转换是一种将值转换为另一种类型的方法,这个过程是自动完成的,无需我们手动操作。
假设我们下面有一个例子。
//ES5 Version
function Person(firstName, lastName, age, address){
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.address = address;
}
Person.self = function(){
return this;
}
Person.prototype.toString = function(){
return "[object Person]";
}
Person.prototype.getFullName = function (){
return this.firstName + " " + this.lastName;
}
//ES6 Version
class Person {
constructor(firstName, lastName, age, address){
this.lastName = lastName;
this.firstName = firstName;
this.age = age;
this.address = address;
}
static self() {
return this;
}
toString(){
return "[object Person]";
}
getFullName(){
return `${this.firstName} ${this.lastName}`;
}
}
第一个console.log语句结果为16。在其他语言中,这会抛出编译时错误,但在 JS 中,1被转换成字符串,然后与+运算符连接。我们没有做任何事情,它是由 JS 自动完成。
第二个console.log语句结果为1,JS 将false转换为boolean 值为 0,,true为1,因此结果为1。
第三个console.log语句结果12,它将'2'转换为一个数字,然后乘以6 * 2,结果是12。
而显式强制是将值转换为另一种类型的方法,我们需要手动转换。
//ES5 Version
Employee.prototype = Object.create(Person.prototype);
function Employee(firstName, lastName, age, address, jobTitle, yearStarted) {
Person.call(this, firstName, lastName, age, address);
this.jobTitle = jobTitle;
this.yearStarted = yearStarted;
}
Employee.prototype.describe = function () {
return `I am ${this.getFullName()} and I have a position of ${this.jobTitle}
and I started at ${this.yearStarted}`;
}
Employee.prototype.toString = function () {
return "[object Employee]";
}
//ES6 Version
class Employee extends Person { //Inherits from "Person" class
constructor(firstName, lastName, age, address, jobTitle, yearStarted) {
super(firstName, lastName, age, address);
this.jobTitle = jobTitle;
this.yearStarted = yearStarted;
}
describe() {
return `I am ${this.getFullName()} and I have a position of ${this.jobTitle}
and I started at ${this.yearStarted}`;
}
toString() { // Overriding the "toString" method of "Person"
return "[object Employee]";
}
}
在本例中,我们使用parseInt函数将'6'转换为number ,然后使用+运算符将1和6相加。
56. 什么是NaN?以及如何检查值是否为NaN?
NaN表示“非数字”是 JS 中的一个值,该值是将数字转换或执行为非数字值的运算结果,因此结果为NaN。
class Something {
}
function AnotherSomething(){
}
const as = new AnotherSomething();
const s = new Something();
console.log(typeof Something); // "function"
console.log(typeof AnotherSomething); // "function"
console.log(as.toString()); // "[object Object]"
console.log(as.toString()); // "[object Object]"
console.log(as.toString === Object.prototype.toString); // true
console.log(s.toString === Object.prototype.toString); // true
JS 有一个内置的isNaN方法,用于测试值是否为isNaN值,但是这个函数有一个奇怪的行为。
console.log(isNaN()); // true
console.log(isNaN(undefined)); // true
console.log(isNaN({})); // true
console.log(isNaN(String('a'))); // true
console.log(isNaN(() => { })); // true
所有这些console.log语句都返回true,即使我们传递的值不是NaN。
在ES6中,建议使用Number.isNaN方法,因为它确实会检查该值(如果确实是NaN),或者我们可以使自己的辅助函数检查此问题,因为在 JS 中,NaN是唯一的值,它不等于自己。
function checkIfNaN(value) {
return value !== value;
}
57. 如何判断值是否为数组?
我们可以使用Array.isArray方法来检查值是否为数组。当传递给它的参数是数组时,它返回true,否则返回false。
//ES5 Version
var greet = 'Hi I\'m Mark';
//ES6 Version
let greet = `Hi I'm Mark`;
如果环境不支持此方法,则可以使用polyfill实现。
//ES5 Version
var lastWords = '\n'
+ ' I \n'
+ ' Am \n'
+ 'Iron Man \n';
//ES6 Version
let lastWords = `
I
Am
Iron Man
`;
当然还可以使用传统的方法:
//ES5 Version
function greet(name) {
return 'Hello ' + name + '!';
}
//ES6 Version
function greet(name) {
return `Hello ${name} !`;
}
58. 如何在不使用%模运算符的情况下检查一个数字是否是偶数?
我们可以对这个问题使用按位&运算符,&对其操作数进行运算,并将其视为二进制值,然后执行与运算。
function isEven(num) {
if (num & 1) {
return false
} else {
return true
}
}
以此类推...
与运算的规则如下:
aba & b000010111
因此,当我们执行console.log(5&1)这个表达式时,结果为1。首先,&运算符将两个数字都转换为二进制,因此5变为101,1变为001。
然后,它使用按位怀运算符比较每个位(0和1)。 101&001,从表中可以看出,如果a & b为1,所以5&1结果为1。
- 首先我们比较最左边的1&0,结果是0。
- 然后我们比较中间的0&0,结果是0。
- 然后我们比较最后1&1,结果是1。
- 最后,得到一个二进制数001,对应的十进制数,即1。
由此我们也可以算出console.log(4 & 1) 结果为0。知道4的最后一位是0,而0 & 1 将是0。如果你很难理解这一点,我们可以使用递归函数来解决此问题。
function isEven(num) {if (num < 0 || num === 1) return false;if (num == 0) return true;
return isEven(num - 2);}
59. 如何检查对象中是否存在某个属性?
检查对象中是否存在属性有三种方法。
第一种使用 in 操作符号:
const employee = {
firstName: "Marko",
lastName: "Polo",
position: "Software Developer",
yearHired: 2017
};
第二种使用 hasOwnProperty 方法,hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。
var firstName = employee.firstName;
var lastName = employee.lastName;
var position = employee.position;
var yearHired = employee.yearHired;
第三种使用括号符号obj["prop"]。如果属性存在,它将返回该属性的值,否则将返回undefined。
{ firstName, lastName, position, yearHired } = employee;
60. AJAX 是什么?
即异步的 JavaScript 和 XML,是一种用于创建快速动态网页的技术,传统的网页(不使用 AJAX)如果需要更新内容,必需重载整个网页面。使用AJAX则不需要加载更新整个网页,实现部分内容更新
用到AJAX的技术:
- HTML - 网页结构
- CSS - 网页的样式
- JavaScript - 操作网页的行为和更新DOM
- XMLHttpRequest API - 用于从服务器发送和获取数据
- PHP,Python,Nodejs - 某些服务器端语言
61. 如何在 JS 中创建对象?
使用对象字面量:
let { firstName: fName, lastName: lName, position, yearHired } = employee;
使用构造函数:
let { firstName = "Mark", lastName: lName, position, yearHired } = employee;
使用 Object.create 方法:
const n = {
greeting() {
return `Hi, I'm ${this.name}`;
}
};
const o = Object.create(n); // sets the prototype of "o" to be "n"
o.name = "Mark";
console.log(o.greeting()); // logs "Hi, I'm Mark"
62. Object.seal 和 Object.freeze 方法之间有什么区别?
这两种方法之间的区别在于,当我们对一个对象使用Object.freeze方法时,该对象的属性是不可变的,这意味着我们不能更改或编辑这些属性的值。而在Obj.Engor方法中,我们可以改变现有的属性。
Object.freeze()
Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。
Object.seal()
Object.seal()方法封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置。当前属性的值只要可写就可以改变。
方法的相同点:
- ES5新增。
- 对象不可能扩展,也就是不能再添加新的属性或者方法。
- 对象已有属性不允许被删除。
- 对象属性特性不可以重新配置。
方法不同点:
- Object.seal方法生成的密封对象,如果属性是可写的,那么可以修改属性值。
- Object.freeze方法生成的冻结对象,属性都是不可写的,也就是属性值无法更改。
63. in 运算符和 Object.hasOwnProperty 方法有什么区别?
如你所知,这两个特性都检查对象中是否存在属性,它将返回truefalse。它们之间的区别在于,in操作符还会检查对象的原型链,如果属性在当前对象中没有找到,而hasOwnProperty方法只检查属性是否存在于当前对象中,而忽略原型链。
hasOwnPropert方法
hasOwnPropert()方法返回值是一个布尔值,指示对象自身属性中是否具有指定的属性,因此这个方法会忽略掉那些从原型链上继承到的属性。
看下面的例子:
// 使用 ES5 CommonJS - helpers.js
exports.isNull = function (val) {
return val === null;
}
exports.isUndefined = function (val) {
return val === undefined;
}
exports.isNullOrUndefined = function (val) {
return exports.isNull(val) || exports.isUndefined(val);
}
可以看到,如果在函数原型上定义一个变量phone,hasOwnProperty方法会直接忽略掉。
in 运算符
如果指定的属性在指定的对象或其原型链中,则in 运算符返回true。
还是用上面的例子来演示:
// 使用 ES6 Modules - helpers.js
export function isNull(val){
return val === null;
}
export function isUndefined(val) {
return val === undefined;
}
export function isNullOrUndefined(val) {
return isNull(val) || isUndefined(val);
}
可以看到in运算符会检查它或者其原型链是否包含具有指定名称的属性。
64. 有哪些方法可以处理 JS 中的异步代码?
- 回调
- Promise
- async/await
- 还有一些库:async.js, bluebird, q, co
65. 函数表达式和函数声明之间有什么区别?
看下面的例子:
// 使用 ES5 (CommonJS) - index.js
const helpers = require('./helpers.js'); // helpers is an object
const isNull = helpers.isNull;
const isUndefined = helpers.isUndefined;
const isNullOrUndefined = helpers.isNullOrUndefined;
// or if your environment supports Destructuring
const { isNull, isUndefined, isNullOrUndefined } = require('./helpers.js');
-------------------------------------------------------
// ES6 Modules - index.js
import * as helpers from './helpers.js'; // helpers is an object
// or
import { isNull, isUndefined, isNullOrUndefined as isValid } from './helpers.js';
// using "as" for renaming named exports
notHoistedFunc调用抛出异常:Uncaught TypeError: notHoistedFunc is not a function,而hoistedFunc调用不会,因为hoistedFunc会被提升到作用域的顶部,而notHoistedFunc 不会。
66. 调用函数,可以使用哪些方法?
在 JS 中有4种方法可以调用函数。
- 作为函数调用——如果一个函数没有作为方法、构造函数、apply、call 调用时,此时 this 指向的是 window 对象(非严格模式)
// 使用 ES5 (CommonJS) - index.js
class Helpers {
static isNull(val) {
return val === null;
}
static isUndefined(val) {
return val === undefined;
}
static isNullOrUndefined(val) {
return this.isNull(val) || this.isUndefined(val);
}
}
module.exports = Helpers;
- 作为方法调用——如果一个对象的属性有一个函数的值,我们就称它为方法。调用该方法时,该方法的this值指向该对象。
const details = {
name : "Marko",
getName(){
return this.name;
}
}
details.getName(); // Marko
// the "this" value inside "getName" method will be the "details" object
- 作为构造函数的调用-如果在函数之前使用new关键字调用了函数,则该函数称为构造函数。构造函数里面会默认创建一个空对象,并将this指向该对象。
function Employee(name, position, yearHired) {
// creates an empty object {}
// then assigns the empty object to the "this" keyword
// this = {};
this.name = name;
this.position = position;
this.yearHired = yearHired;
// inherits from Employee.prototype
// returns the "this" value implicitly if no
// explicit return statement is specified
};
const emp = new Employee("Marko Polo", "Software Developer", 2017);
- 使用apply和call方法调用——如果我们想显式地指定一个函数的this值,我们可以使用这些方法,这些方法对所有函数都可用。
const obj1 = {
result:0
};
const obj2 = {
result:0
};
function reduceAdd(){
let result = 0;
for(let i = 0, len = arguments.length; i < len; i++){
result += arguments[i];
}
this.result = result;
}
reduceAdd.apply(obj1, [1, 2, 3, 4, 5]); // reduceAdd 函数中的 this 对象将是 obj1
reduceAdd.call(obj2, 1, 2, 3, 4, 5); // reduceAdd 函数中的 this 对象将是 obj2
67. 什么是缓存及它有什么作用?
缓存是建立一个函数的过程,这个函数能够记住之前计算的结果或值。使用缓存函数是为了避免在最后一次使用相同参数的计算中已经执行的函数的计算。这节省了时间,但也有不利的一面,即我们将消耗更多的内存来保存以前的结果。
68. 手动实现缓存方法
// 使用 ES6 Modules - helpers.js
class Helpers {
static isNull(val) {
return val === null;
}
static isUndefined(val) {
return val === undefined;
}
static isNullOrUndefined(val) {
return this.isNull(val) || this.isUndefined(val);
}
}
export default Helpers
这个缓存函数适用于接受一个参数。我们需要改变下,让它接受多个参数。
const slice = Array.prototype.slice;
function memoize(fn) {
const cache = {};
return (...args) => {
const params = slice.call(args);
console.log(params);
if (cache[params]) {
console.log('cached');
return cache[params];
} else {
let result = fn(...args);
cache[params] = result;
console.log(`not cached`);
return result;
}
}
}
const makeFullName = (fName, lName) => `${fName} ${lName}`;
const reduceAdd = (numbers, startingValue = 0) =>
numbers.reduce((total, cur) => total + cur, startingValue);
const memoizedMakeFullName = memoize(makeFullName);
const memoizedReduceAdd = memoize(reduceAdd);
memoizedMakeFullName("Marko", "Polo");
memoizedMakeFullName("Marko", "Polo");
memoizedReduceAdd([1, 2, 3, 4, 5], 5);
memoizedReduceAdd([1, 2, 3, 4, 5], 5);
69. 为什么typeof null 返回 object?如何检查一个值是否为 null?
typeof null == 'object'总是返回true,因为这是自 JS 诞生以来null的实现。曾经有人提出将typeof null == 'object'修改为typeof null == 'null',但是被拒绝了,因为这将导致更多的bug。
我们可以使用严格相等运算符===来检查值是否为null。
// 使用 ES5 (CommonJS) - index.js
const Helpers = require('./helpers.js');
console.log(Helpers.isNull(null));
70. new 关键字有什么作用?
new关键字与构造函数一起使用以创建对象在JavaScript中。
下面看看例子:
import Helpers from '.helpers.js'
console.log(Helpers.isNull(null));
new关键字做了4件事:
- 创建空对象 {}
- 将空对象分配给 this 值
- 将空对象的__proto__指向构造函数的prototype
- 如果没有使用显式return语句,则返回this
根据上面描述的,它将首先创建一个空对象{},然后它将this值赋给这个空对象this={},并向这个对象添加属性。因为我们没有显式的return语句,所以它会自动为我们返回this。
70个JS知识点总结,已完结!
推荐JS相关文章
另外,前面一段时间,小编也陆陆续续整理了不少的JavaSxript 知识点的相关文章,不知小伙们都学的怎么样了?咱们一起来回顾一下:
《JavaScript ECMAScript语法概括【思维导图】》
《一个合格的中级前端工程师需要掌握的 28 个 JavaScript 技巧》
英文原文地址:https://dev.to/macmacky/70-javascript-interview-questions-5gfi#61-what-are-the-ways-of-making-objects-in-javascript