关于JavaScript必学的基础知识(下)

96 阅读10分钟

16. JavaScript 中“this”的值是多少?

基本上,this 指的是当前正在执行或调用函数的对象的值。 this 的价值根据我们使用它的上下文和我们使用它的地方而变化。

const carDetails = {
  name: "maxwell",
  yearBought: 2023,
  getName(){
    return this.name;
  },
  isRegistered: true
};
console.log(carDetails.getName()); // maxwell

这通常是我们所期望的,因为在我们返回 this.name 的 getName 方法中。 在此上下文中,this 指的是 carDetails 对象,它是执行函数的当前“所有者”对象。

接下来我们做一些奇怪的事情:

var name = "maxwell";
var getCarName = carDetails.getName;

console.log(getCarName()); // maxwell

它在上面打印了‘maxwell’,这很奇怪,因为第一个 console.log 语句打印了‘maxwell’。 这样做的原因是 getCarName 方法有一个不同的“所有者”对象,即窗口对象。

在全局范围内使用 var 关键字声明变量时,它们将作为属性附加到具有相同变量名的 window 对象。

请注意,当不使用“use strict”时,全局范围内的 this 指的是 window 对象。

console.log(getCarName === window.getCarName); // true
console.log(getCarName === this.getCarName); // true

在这个例子中,this 和 window 指的是同一个对象。

解决此问题的一种方法是在函数中使用 apply 和 call 方法。

console.log(getCarName.apply(carDetails));
console.log(getCarName.call(carDetails));

apply 和 call 方法期望第一个参数是一个对象,这将是函数内部 this 的值。

IIFE 或立即调用函数表达式、在全局范围内声明的函数、对象方法中的匿名函数和内部函数都具有指向窗口对象的默认值 this。

(function (){
    console.log(this);
  })();

  function iHateThis(){
     console.log(this);
  }

  iHateThis();

  const myFavoriteObj = {
    guessThis(){
       function getName(){
         console.log(this.name);
       }
       getName();
    },
    name: 'Maxwell',
    thisIsAnnoying(callback){
      callback();
    }
  };


  myFavoriteObj.guessThis();
  myFavoriteObj.thisIsAnnoying(function (){
    console.log(this);
  });

如果我们想获取myFavoriteObj对象中name属性(Maxwell)的值,有两种方法可以解决这个问题。

一种方法是将 this 的值保存在变量中。

const myFavoriteObj = {
 guessThis(){
  const self = this; // Store the value of this in the self variable
  function getName(){
    console.log(self.name);
  }
  getName();
 },
 name: 'Marko Polo',
 thisIsAnnoying(callback){
   callback();
  }
};

第二种方法是使用箭头函数。

const myFavoriteObj = {
  guessThis(){
     const getName = () => {
       console.log(this.name);
     }
     getName();
  },
  name: 'Maxwell',
  thisIsAnnoying(callback){
   callback();
  }
};

箭头函数没有自己的 this。 它们从封闭的词法范围继承 this 值。 在此示例中,this 值是 myFavoriteObj 对象,它位于 getName 内部函数之外。

17. ‘var’、‘let’和‘const’有什么区别?

用 var 声明的变量被提升并附加到 window 对象,而用 let 和 const 声明的变量则不会。

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

用 var 声明的变量会进行变量提升,而用 let 和 const 声明的变量不会被提升。

console.log(a); // undefined  ===>  a has been declared and not assigned a value, the default is undefined
var a = 100;

console.log(b); // error:b is not defined
let b = 10;

console.log(c); // error:c is not defined
const c = 10;

let 和 const 声明创建块作用域。

if(1){
  var a = 100;
  let b = 10;
}

console.log(a); // 100
console.log(b)  // error:b is not defined

-------------------------------------------------------------

if(1){
  var a = 100;
  const c = 1;
}
console.log(a); // 100
console.log(c)  // error:c is not defined

在同一范围内,let 和 const 不能声明同名变量,而 var 允许。

var a = 100;
console.log(a); // 100

var a = 10;
console.log(a); // 10
-------------------------------------
let a = 100;
let a = 10;

//  error:Identifier 'a' has already been declared

const

/*
*   1、Once declared, it must be assigned a value and cannot be used null .
*
*   2、Cannot be modified after declaration
*
*   3、If the declaration is for a composite data type, its properties can be modified.
*
* */

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

18. 什么是箭头函数?

与函数表达式相比,箭头函数表达式具有更简洁的语法,并且没有自己的 this、arguments、super 或 new.target。 箭头函数表达式更适合原本使用匿名函数的情况,不能作为构造函数使用。

//ES5 Version
var getCurrentDate = function (){
  return new Date();
}

//ES6 Version
const getCurrentDate = () => new Date();

在这个例子中,在 ES5 版本中,我们有 function(){} 声明和 return 关键字,它们分别是创建函数和返回值所必需的。

在箭头函数版本中,我们只需要括号 () 而不需要 return 语句,因为如果我们只有一个表达式或值要返回,则箭头函数具有隐式返回。

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

箭头函数无法访问参数对象。 因此,调用第一个 getArgs 函数会抛出错误。 相反,我们可以使用剩余参数来获取箭头函数中传递的所有参数。

const data = {
  result: 0,
  nums: [1, 2, 3, 4, 5],
  computeResult() {
    // Here "this" refers to the "data" object
    const addAll = () => {
      return this.nums.reduce((total, cur) => total + cur, 0)
    };
    this.result = addAll();
  }
};

箭头函数没有自己的 this 值。 它们捕获词法作用域函数的 this 值。 在此示例中,addAll 函数将从 computeResult 方法继承 this 值。 如果我们在全局范围内声明一个箭头函数,则 this 值将是 window 对象。

19. 什么是对象的原型?

简单来说,原型就是一个对象的蓝图。 如果它存在于当前对象中,则用作属性和方法的回退。 它是对象之间共享属性和功能的一种方式,也是 JavaScript 中实现继承的核心。

const o = {};
console.log(o.toString()); // logs [object Object]

即使 o 对象没有 o.toString 方法,它也不会抛出错误而是返回字符串 [object Object]。

当一个对象中没有找到某个属性时,它会在它的原型中寻找它,如果仍然不存在,它会继续在原型的原型中寻找,以此类推,直到找到同名的属性 在原型链中。

原型链的末端是 Object.prototype。

console.log(o.toString === Object.prototype.toString); // logs true

20. 什么是 IIFE,它的目的是什么?

IIFE 或立即调用函数表达式,是在创建或声明后立即调用或执行的函数。 创建 IIFE 的语法是将函数 (){} 包裹在圆括号 () 中,然后用另一对圆括号 () 调用它,例如 (function(){})()。

(function(){
  ...
} ());

(function () {
  ...
})();

(function named(params) {
  ...
})();

(() => {

});

(function (global) {
  ...
})(window);

const utility = (function () {
  return {
    ...
  }
})

所有这些示例都是有效的 IIFE。 倒数第二个示例演示了我们可以将参数传递给 IIFE 函数。 最后一个示例表明我们可以将 IIFE 的结果保存到一个变量中供以后使用。

IIFE 的主要目的之一是避免命名冲突或全局范围内的变量污染全局命名空间。 这是一个例子:

<script src="https://cdnurl.com/somelibrary.js"></script>

假设我们已经导入了一个指向 omelibr.js 的链接,它提供了我们在代码中使用的一些全局函数。 但是,这个库有两个方法,createGraph 和 drawGraph,我们没有使用它们,因为它们有错误。 我们要实现自己的 createGraph 和 drawGraph 方法。

解决这个问题的一种方法是直接覆盖它们。

<script src="https://cdnurl.com/somelibrary.js"></script>
<script>
   function createGraph() {
      // createGraph logic here
   }
   function drawGraph() {
      // drawGraph logic here
   }
</script>

当我们使用这个解决方案时,我们重写了库提供的两个方法。

另一种方法是我们自己重命名它们。

<script src="https://cdnurl.com/somelibrary.js"></script>
<script>
   function myCreateGraph() {
      // createGraph logic here
   }
   function myDrawGraph() {
      // drawGraph logic here
   }
</script>

当我们使用这个解决方案时,我们将函数调用更改为新的函数名称。

另一种方法是使用 IIFE

<script src="https://cdnurl.com/somelibrary.js"></script>
<script>
   const graphUtility = (function () {
      function createGraph() {
         // createGraph logic here
      }
      function drawGraph() {
         // drawGraph logic here
      }
      return {
         createGraph,
         drawGraph
      }
   })
</script>

在这个解决方案中,我们声明了一个变量 graphUtility 来存储 IIFE 执行的结果。 该函数返回一个包含两个方法的对象,createGraph 和 drawGraph。

IIFE 也可以用来解决常见的面试问题。

var li = document.querySelectorAll('.list-group > li');
for (var i = 0, len = li.length; i < len; i++) {
   li[i].addEventListener('click', function (e) {
      console.log(i);
   })

假设我们有一个类为 list-group 的

    元素,它有 5 个
  • 子元素。 当我们点击一个单独的
  • 元素时,我们想要打印它对应的索引值。 但是,上面的代码没有按预期工作。在这种情况下,我们每次点击
  • 时,他都将i的值打印为5,这是由于闭包造成的。

    元素时,它会打印 5,因为这是稍后在回调函数中引用 i 时的值。

    闭包只是函数从其当前作用域、父函数作用域和全局作用域记住变量的能力。 当我们在全局范围内使用 var 关键字声明一个变量时,我们创建了一个全局变量 i。 因此,当我们点击

  • 元素时,它会打印 5,因为这是稍后在回调函数中引用 i 时的值。

    使用 IIFE 可以解决这个问题。

    var li = document.querySelectorAll('.list-group > li');
    for (var i = 0, len = li.length; i < len; i++) {
       (function (currentIndex) {
          li[currentIndex].addEventListener('click', function (e) {
             console.log(currentIndex);
          })
       })(i);
    }
    

    该解决方案之所以有效,是因为 IIFE 为每次迭代创建了一个新范围。 我们捕获 i 的值并将其作为 currentIndex 参数传递,因此当调用 IIFE 时,每次迭代都有不同的 currentIndex 值。

    21. Function.prototype.apply 方法的目的是什么?

    apply() 方法调用具有给定 this 值的函数,并将参数作为数组(或类似数组的对象)提供。

    const details = {
      message: 'Hello medium!'
    };
    
    function getMessage(){
      return this.message;
    }
    
    getMessage.apply(details); // 'Hello medium!'
    

    call() 方法的用途与 apply() 方法类似,区别在于 call() 接受参数列表,而 apply() 接受参数数组。

    const person = {
      name: "Maxwell"
    };
    
    function greeting(greetingMessage) {
      return `${greetingMessage} ${this.name}`;
    }
    
    greeting.apply(person, ['Hello']); // "Hello Maxwell!"
    

    22. Function.prototype.call 方法的作用是什么?

    call() 方法用于调用具有指定 this 值和提供的各个参数的函数。

    const details = {
      message: 'Hello Medium!'
    };
    
    function getMessage(){
      return this.message;
    }
    
    getMessage.call(details); // 'Hello Medium!'
    

    注意:此方法的语法和用途类似于 apply() 方法,唯一的区别是 call() 接受参数列表,而 apply() 接受包含多个参数的数组

    const person = {
      name: "Maxwell"
    };
    
    function greeting(greetingMessage) {
      return `${greetingMessage} ${this.name}`;
    }
    
    greeting.call(person, 'Hello'); // "Hello Maxwell!"
    

    23. Function.prototype.apply 和 Function.prototype.call 有什么区别?

    apply() 方法可以调用具有指定 this 值和参数数组(或类数组对象)的函数或方法。 call() 方法类似于 apply(),唯一的区别是 call() 接受参数作为参数列表。

    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]); // 15
    reduceAdd.call(obj2, 1, 2, 3, 4, 5); // 15
    

    24. Function.prototype.bind() 的作用是什么?

    bind() 方法创建一个新函数,在调用时将其 this 值设置为 bind() 的第一个参数,后续参数在调用时作为新函数的参数提供。

    import React from 'react';
    
    class MyComponent extends React.Component {
         constructor(props){
              super(props);
              this.state = {
                 value : ""
              }
              this.handleChange = this.handleChange.bind(this);
         }
    
         handleChange(e){
           //do something amazing here
         }
    
         render(){
            return (
                  <>
                    <input type={this.props.type}
                            value={this.state.value}
                         onChange={this.handleChange}
                      />
                  </>
            )
         }
    }
    

    25. 什么是高阶函数?

    高阶函数简单来说就是将函数作为参数或返回函数的函数。

    function higherOrderFunction(param,callback){
        return callback(param);
    }
    

    26. 什么是“arguments”对象?

    arguments 对象是作为参数传递给函数的值的集合。 它是一个类似数组的对象,因为它具有“长度”属性,并且可以使用数组索引符号(如 arguments[1])访问各个值。 但是,它没有内置的数组方法,如 forEach、reduce、filter 和 map。

    我们可以使用 Array.prototype.slice 将参数对象转换为数组。

    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
    

    当我们调用函数四时,它会抛出 ReferenceError: arguments is not defined 错误。 可以使用 rest 语法解决此问题。

    const four = (...args) => args;
    

    这将自动将所有参数值放入一个数组中。

    27.如何创建没有原型的对象?

    我们可以使用 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
    

    28.什么是class?

    class 是一种在 JavaScript 中编写构造函数的新方法。 它是构造函数的语法糖,在底层仍然使用原型和基于原型的继承。

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

    29.什么是模板字符串?

    模板字符串是一种在 JavaScript 中创建字符串的新方法。 我们可以使用反引号 (`) 使字符串成为模板字符串。

    //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 版本中,如果我们需要在字符串中包含表达式或值,我们必须使用 + 运算符。 在模板字符串中,我们可以使用 ${expr} 来嵌入一个表达式,这使得它比 ES5 版本更干净。

    30.什么是对象解构?

    对象解构是一种新的、更简洁的从对象或数组中提取值的方法。 假设我们有以下对象:

    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;
    

    当然,如果属性值未定义,我们也可以指定一个默认值:

    let { firstName = "Mark", lastName: lName, position, yearHired } = employee;
    

    总结

    以上就是我们今天这篇文章想与你分享的全部内容,希望对你有用。

    最后,感谢你的阅读。