javaScript 面试题

1.什么是类?
类(class)是在 JS 中编写构造函数的新方法。它是使用构造函数的语法糖,在底层中使用仍然是原型和基于原型的继承。

//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}`;
        }
   }

重写方法并从另一个类继承。

//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

2.什么是模板字符串?
模板字符串是在 JS 中创建字符串的一种新方法。我们可以通过使用反引号使模板字符串化

//ES5 Version
var greet = 'Hi I\'m Mark';

//ES6 Version
let greet = `Hi I'm Mark`;

在 ES5 中我们需要使用一些转义字符来达到多行的效果,在模板字符串不需要这么麻烦:

//ES5 Version
var lastWords = '\n'
  + '   I  \n'
  + '   Am  \n'
  + 'Iron Man \n';


//ES6 Version
let lastWords = `
    I
    Am
  Iron Man
`;

在ES5版本中,我们需要添加\n以在字符串中添加新行。在模板字符串中,我们不需要这样做。

//ES5 Version
function greet(name) {
  return 'Hello ' + name + '!';
}


//ES6 Version
function greet(name) {
  return `Hello ${name} !`;
}

在 ES5 版本中,如果需要在字符串中添加表达式或值,则需要使用+运算符。在模板字符串s中,我们可以使用${expr}嵌入一个表达式,这使其比 ES5 版本更整洁。

3.什么是对象解构?
对象析构是从对象或数组中获取或提取值的一种新的、更简洁的方法。假设有如下的对象:

const employee = {
  firstName: "Marko",
  lastName: "Polo",
  position: "Software Developer",
  yearHired: 2017
};

从对象获取属性,早期方法是创建一个与对象属性同名的变量。这种方法很麻烦,因为我们要为每个属性创建一个新变量。假设我们有一个大对象,它有很多属性和方法,用这种方法提取属性会很麻烦。

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;

4.什么是 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));

5. 什么是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]

6.什么是回调函数?
回调函数是一段可执行的代码段,它作为一个参数传递给其他的代码,其作用是在需要的时候方便调用这段(回调函数)代码。

在JavaScript中函数也是对象的一种,同样对象可以作为参数传递给函数,因此函数也可以作为参数传递给另外一个函数,这个作为参数的函数就是回调函数。

const btnAdd = document.getElementById('btnAdd');

btnAdd.addEventListener('click', function clickCallback(e) {
    // do something useless
});

在本例中,我们等待id为btnAdd的元素中的click事件,如果它被单击,则执行clickCallback函数。回调函数向某些数据或事件添加一些功能。

数组中的reduce、filter和map方法需要一个回调作为参数。回调的一个很好的类比是,当你打电话给某人,如果他们不接,你留下一条消息,你期待他们回调。调用某人或留下消息的行为是事件或数据,回调是你希望稍后发生的操作。

7. 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) => {...});

8.什么是 async/await 及其如何工作?
async/await是 JS 中编写异步或非阻塞代码的新方法。它建立在Promises之上,让异步代码的可读性和简洁度都更高。

async/await是 JS 中编写异步或非阻塞代码的新方法。它建立在Promises之上,相对于 Promise 和回调,它的可读性和简洁度都更高。但是,在使用此功能之前,我们必须先学习Promises的基础知识,因为正如我之前所说,它是基于Promise构建的,这意味着幕后使用仍然是Promise。
使用 Promise

function callApi() {
  return fetch("url/to/api/endpoint")
    .then(resp => resp.json())
    .then(data => {
      //do something with "data"
    }).catch(err => {
      //do something with "err"
    });
}

使用async/await

在async/await,我们使用 tru/catch 语法来捕获异常。

async function callApi() {
  try {
    const resp = await fetch("url/to/api/endpoint");
    const data = await resp.json();
    //do something with "data"
  } catch (e) {
    //do something with "err"
  }
}

注意:使用 async关键声明函数会隐式返回一个Promise。

const giveMeOne = async () => 1;

giveMeOne()
  .then((num) => {
    console.log(num); // logs 1
  });

注意:await关键字只能在async function中使用。在任何非async function的函数中使用await关键字都会抛出错误。await关键字在执行下一行代码之前等待右侧表达式(可能是一个Promise)返回。

const giveMeOne = async () => 1;

function getOne() {
  try {
    const num = await giveMeOne();
    console.log(num);
  } catch (e) {
    console.log(e);
  }
}

// Uncaught SyntaxError: await is only valid in async function

async function getTwo() {
  try {
    const num1 = await giveMeOne(); // 这行会等待右侧表达式执行完成
    const num2 = await giveMeOne();
    return num1 + num2;
  } catch (e) {
    console.log(e);
  }
}

await getTwo(); // 2

9.展开(spread )运算符和 剩余(Rest) 运算符有什么区别?
展开运算符(spread)是三个点(…),可以将一个数组转为用逗号分隔的参数序列。说的通俗易懂点,有点像化骨绵掌,把一个大元素给打散成一个个单独的小元素。

剩余运算符也是用三个点(…)表示,它的样子看起来和展开操作符一样,但是它是用于解构数组和对象。在某种程度上,剩余元素和展开元素相反,展开元素会“展开”数组变成多个元素,剩余元素会收集多个元素和“压缩”成一个单一的元素。

function add(a, b) {
  return a + b;
};

const nums = [5, 6];
const sum = add(...nums);
console.log(sum);

在本例中,我们在调用add函数时使用了展开操作符,对nums数组进行展开。所以参数a的值是5 ,b的值是6,所以sum 是11

function add(...rest) {
  return rest.reduce((total,current) => total + current);
};

console.log(add(1, 2)); // 3
console.log(add(1, 2, 3, 4, 5)); // 15

在本例中,我们有一个add函数,它接受任意数量的参数,并将它们全部相加,然后返回总数。

const [first, ...others] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(others); // [2,3,4,5]

这里,我们使用剩余操作符提取所有剩余的数组值,并将它们放入除第一项之外的其他数组中。

10. 什么是默认参数?
默认参数是在 JS 中定义默认变量的一种新方法,它在ES6或ECMAScript 2015版本中可用。

//ES5 Version
function add(a,b){
  a = a || 0;
  b = b || 0;
  return a + b;
}

//ES6 Version
function add(a = 0, b = 0){
  return a + b;
}
add(1); // returns 1

我们还可以在默认参数中使用解构。

function getFirst([first, ...rest] = [0, 1]) {
  return first;
}

getFirst();  // 0
getFirst([10,20,30]);  // 10

function getArr({ nums } = { nums: [1, 2, 3, 4] }){
    return nums;
}

getArr(); // [1, 2, 3, 4]
getArr({nums:[5,4,3,2,1]}); // [5,4,3,2,1]

我们还可以使用先定义的参数再定义它们之后的参数。

function doSomethingWithValue(value = "Hello World", callback = () => { console.log(value) }) {
  callback();
}
doSomethingWithValue(); //"Hello World"

11.什么是包装对象(wrapper object)?
我们现在复习一下JS的数据类型,JS数据类型被分为两大类,基本类型和引用类型。

基本类型:Undefined,Null,Boolean,Number,String,Symbol,BigInt

引用类型:Object,Array,Date,RegExp等,说白了就是对象。

其中引用类型有方法和属性,但是基本类型是没有的,但我们经常会看到下面的代码:

let name = "marko";

console.log(typeof name); // "string"
console.log(name.toUpperCase()); // "MARKO"

name类型是 string,属于基本类型,所以它没有属性和方法,但是在这个例子中,我们调用了一个toUpperCase()方法,它不会抛出错误,还返回了对象的变量值。

原因是基本类型的值被临时转换或强制转换为对象,因此name变量的行为类似于对象。除null和undefined之外的每个基本类型都有自己包装对象。也就是:String,Number,Boolean,Symbol和BigInt。在这种情况下,name.toUpperCase()在幕后看起来如下:

console.log(new String(name).toUpperCase()); // "MARKO"

在完成访问属性或调用方法之后,新创建的对象将立即被丢弃。

12.隐式和显式转换有什么区别)?
隐式强制转换是一种将值转换为另一种类型的方法,这个过程是自动完成的,无需我们手动操作。

假设我们下面有一个例子。

console.log(1 + '6'); // 16
console.log(false + true); // 1
console.log(6 * '2'); // 12

第一个console.log语句结果为16。在其他语言中,这会抛出编译时错误,但在 JS 中,1被转换成字符串,然后与+运算符连接。我们没有做任何事情,它是由 JS 自动完成。

第二个console.log语句结果为1,JS 将false转换为boolean 值为 0,,true为1,因此结果为1。

第三个console.log语句结果12,它将’2’转换为一个数字,然后乘以6 * 2,结果是12。

而显式强制是将值转换为另一种类型的方法,我们需要手动转换。

console.log(1 + parseInt('6'));

在本例中,我们使用parseInt函数将’6’转换为number ,然后使用+运算符将1和6相加。

13.什么是NaN?以及如何检查值是否为NaN?

NaN表示“非数字”是 JS 中的一个值,该值是将数字转换或执行为非数字值的运算结果,因此结果为NaN。

let a;

console.log(parseInt('abc')); // NaN
console.log(parseInt(null)); // NaN
console.log(parseInt(undefined)); // NaN
console.log(parseInt(++a)); // NaN
console.log(parseInt({} * 10)); // NaN
console.log(parseInt('abc' - 2)); // NaN
console.log(parseInt(0 / 0)); // NaN
console.log(parseInt('10a' * 10)); // NaN

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;
}

14.如何判断值是否为数组?
我们可以使用Array.isArray方法来检查值是否为数组。当传递给它的参数是数组时,它返回true,否则返回false。

console.log(Array.isArray(5));  // false
console.log(Array.isArray("")); // false
console.log(Array.isArray()); // false
console.log(Array.isArray(null)); // false
console.log(Array.isArray({ length: 5 })); // false

console.log(Array.isArray([])); // true

如果环境不支持此方法,则可以使用polyfill实现。

function isArray(value){
 return Object.prototype.toString.call(value) === "[object Array]"
}

当然还可以使用传统的方法:

let a = []
if (a instanceof Array) {
  console.log('是数组')
} else {
  console.log('非数组')

15.如何在不使用%模运算符的情况下检查一个数字是否是偶数?

function isEven(num) {
  if (num & 1) {
    return false
  } else {
    return true
  }
}

0 二进制数是 000
1 二进制数是 001
2 二进制数是 010
3 二进制数是 011
4 二进制数是 100
5 二进制数是 101
6 二进制数是 110
7 二进制数是 111

以此类推…

与运算的规则如下:

aba&b
000
010
111

因此,当我们执行console.log(5&1)这个表达式时,结果为1。首先,&运算符将两个数字都转换为二进制,因此5变为101,1变为001。

然后,它使用按位怀运算符比较每个位(0和1)。 101&001,从表中可以看出,如果a & b为1,所以5&1结果为1。

101&001
101
001
001

首先我们比较最左边的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);

}

16.如何检查对象中是否存在某个属性?
检查对象中是否存在属性有三种方法。

第一种使用 in 操作符号:

const o = {
  "prop" : "bwahahah",
  "prop2" : "hweasa"
};

console.log("prop" in o); // true
console.log("prop1" in o); // false

第二种使用 hasOwnProperty 方法,hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。

onsole.log(o.hasOwnProperty("prop2")); // true
console.log(o.hasOwnProperty("prop1")); // false

第三种使用括号符号obj[“prop”]。如果属性存在,它将返回该属性的值,否则将返回undefined。

console.log(o["prop"]); // "bwahahah"
console.log(o["prop1"]); // undefined

17.AJAX 是什么?
即异步的 JavaScript 和 XML,是一种用于创建快速动态网页的技术,传统的网页(不使用 AJAX)如果需要更新内容,必需重载整个网页面。使用AJAX则不需要加载更新整个网页,实现部分内容更新

用到AJAX的技术:

HTML - 网页结构

CSS - 网页的样式

JavaScript - 操作网页的行为和更新DOM

XMLHttpRequest API - 用于从服务器发送和获取数据

PHP,Python,Nodejs - 某些服务器端语言

18. 如何在 JS 中创建对象?
使用对象字面量:

  const o = {
   name: "Mark",
   greeting() {
      return `Hi, I'm ${this.name}`;
   }
  };

  o.greeting(); //returns "Hi, I'm Mark"

使用构造函数:

function Person(name) {
   this.name = name;
}

Person.prototype.greeting = function () {
   return `Hi, I'm ${this.name}`;
}

const mark = new Person("Mark");

mark.greeting(); //returns "Hi, I'm Mark"

使用 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"

19.Object.seal 和 Object.freeze 方法之间有什么区别?
这两种方法之间的区别在于,当我们对一个对象使用Object.freeze方法时,该对象的属性是不可变的,这意味着我们不能更改或编辑这些属性的值。而在Obj.Engor方法中,我们可以改变现有的属性。

Object.freeze()

Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。

Object.seal()
Object.seal()方法封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置。当前属性的值只要可写就可以改变。
方法的相同点:

ES5新增。

对象不可能扩展,也就是不能再添加新的属性或者方法。

对象已有属性不允许被删除。

对象属性特性不可以重新配置。

方法不同点:

Object.seal方法生成的密封对象,如果属性是可写的,那么可以修改属性值。

Object.freeze方法生成的冻结对象,属性都是不可写的,也就是属性值无法更改。

20.in 运算符和 Object.hasOwnProperty 方法有什么区别?

如你所知,这两个特性都检查对象中是否存在属性,它将返回truefalse。它们之间的区别在于,in操作符还会检查对象的原型链,如果属性在当前对象中没有找到,而hasOwnProperty方法只检查属性是否存在于当前对象中,而忽略原型链。

hasOwnPropert方法
hasOwnPropert()方法返回值是一个布尔值,指示对象自身属性中是否具有指定的属性,因此这个方法会忽略掉那些从原型链上继承到的属性。

看下面的例子:

Object.prototype.phone= '15345025546';

let obj = {
    name: '西门大官人',
    age: '28'
}
console.log(obj.hasOwnProperty('phone')) // false
console.log(obj.hasOwnProperty('name')) // true

可以看到,如果在函数原型上定义一个变量phone,hasOwnProperty方法会直接忽略掉。

in 运算符

如果指定的属性在指定的对象或其原型链中,则in 运算符返回true。

还是用上面的例子来演示:

console.log('phone' in obj) // true

可以看到in运算符会检查它或者其原型链是否包含具有指定名称的属性。

21. 有哪些方法可以处理 JS 中的异步代码?
回调

Promise

async/await

还有一些库:async.js, bluebird, q, co

22.函数表达式和函数声明之间有什么区别?

hoistedFunc();
notHoistedFunc();

function hoistedFunc(){
  console.log("注意:我会被提升");
}

var notHoistedFunc = function(){
  console.log("注意:我没有被提升");
}

notHoistedFunc调用抛出异常:Uncaught TypeError: notHoistedFunc is not a function,而hoistedFunc调用不会,因为hoistedFunc会被提升到作用域的顶部,而notHoistedFunc 不会。

23.调用函数,可以使用哪些方法?
在 JS 中有4种方法可以调用函数。

作为函数调用——如果一个函数没有作为方法、构造函数、apply、call 调用时,此时 this 指向的是 window 对象(非严格模式)

 //Global Scope

  function add(a,b){
    console.log(this);
    return a + b;
  }

  add(1,5); // 打印 "window" 对象和 6

  const o = {
    method(callback){
      callback();
    }
  }

  o.method(function (){
      console.log(this); // 打印 "window" 对象
  });

作为方法调用——如果一个对象的属性有一个函数的值,我们就称它为方法。调用该方法时,该方法的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

24.什么是缓存及它有什么作用?
缓存是建立一个函数的过程,这个函数能够记住之前计算的结果或值。使用缓存函数是为了避免在最后一次使用相同参数的计算中已经执行的函数的计算。这节省了时间,但也有不利的一面,即我们将消耗更多的内存来保存以前的结果。

25.手动实现缓存方法

function memoize(fn) {
  const cache = {};
  return function (param) {
    if (cache[param]) {
      console.log('cached');
      return cache[param];
    } else {
      let result = fn(param);
      cache[param] = result;
      console.log(`not cached`);
      return result;
    }
  }
}

const toUpper = (str ="")=> str.toUpperCase();

const toUpperMemoized = memoize(toUpper);

toUpperMemoized("abcdef");
toUpperMemoized("abcdef");

这个缓存函数适用于接受一个参数。我们需要改变下,让它接受多个参数。

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);

27. 为什么typeof null 返回 object?如何检查一个值是否为 null?
typeof null == 'object’总是返回true,因为这是自 JS 诞生以来null的实现。曾经有人提出将typeof null == 'object’修改为typeof null == ‘null’,但是被拒绝了,因为这将导致更多的bug。

我们可以使用严格相等运算符===来检查值是否为null。

function isNull(value){
  return value === null;
}

28.new 关键字有什么作用?
new关键字与构造函数一起使用以创建对象在JavaScript中。

下面看看例子:

function Employee(name, position, yearHired) {
  this.name = name;
  this.position = position;
  this.yearHired = yearHired;
};

const emp = new Employee("Marko Polo", "Software Developer", 2017);

new关键字做了4件事:

创建空对象 {}

将空对象分配给 this 值

将空对象的__proto__指向构造函数的prototype

如果没有使用显式return语句,则返回this

根据上面描述的,它将首先创建一个空对象{},然后它将this值赋给这个空对象this={},并向这个对象添加属性。因为我们没有显式的return语句,所以它会自动为我们返回this。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值