这是前端最基础的问题,也是在面试中难倒无数同学的经典问题

01. Javascript 数据类型

  Javascript 数据类型 = 基本类型 + 引用类型

  ES6 之前 5 + 1 = 6 种

  ES6 之后 6 + 1 = 7 种 

  注:基本类型共6种:Number 数值型, String 字符型, Boolean 布尔型, Null 空, Undefind 未定义, Symbol 符号型, 其中Symbol是ES6新增的。

    引用类型只有1种:Object 对象,注意:Function 和 Array 都继承于Object。

 

02. Javascript 类型判断

  Javascript 类型判断主要有三种方法

  ①  typeof  

    最大的问题是判断数组和null等数据类型时,无法获得预期的结果。

  ②  instanceof

    用于判断引用类型的继承关系,如:判断Function是否继承于object

    最大的问题是不支持基本类型判断

  ③  Object.prototype.toString.call() 

    类型判断的最佳实践,使用Object原型上的toString方法,这种方法可以精确的判断各种数据类型。

 

03.  Javascript 函数调用

  ①  直接调用

    function test(msg) {

     console.log(msg)

    } 

    test ('Hello word !')

    这种方法最为常用,使用括号即可调用函数。

 

  ②  方法调用

    const f = {

       a: 1,

     b(){ console.log(this.a) }   

    }

    f.b()  // 1

    当函数被保存为对象的一个属性时,称为方法。当它被调用时this将绑定到该对象。

 

  ③  构造函数调用

    function test() {

        this.a = 1    

    }

    const o = new test()

    console.log(o.a)  // 1

    当使用new来调用函数时,会创建一个新对象,函数内部的this会绑定到新对象。

 

  ④  call 和 apply 调用

    Object.prototype.toString.call({})   // [object Object]

    Object.prototype.toString.apply([])  // [object Array]

    函数也是一个对象,可以拥有方法,call 和 apply 就是函数的方法,任何函数都包含call 和 apply这两个方法,调用它们可以执行函数并改变this的指向。

 

04.  Javascript 函数调用 bind call 和 apply 的区别

    ①  bind 

      function test() {

         // this默认指向window

         console.log(this.a)

      }

      test.bind({a: 1})  // 返回一个新函数

      bind 的作用是改变函数的 this 指向,通过 bind 将 this 绑定到新对象,并返回一个新的函数。

 

    ②  call

      function test(b,c) {

       console.log(this.a, b, c)

      }

      test.call({a: 1}, 2, 3)   // 1 2 3

      call 的作用是执行函数并改变this指向,既然是执行函数,所以需要传入执行参数,call传参的方式是依次传入,用逗号分隔。

 

    ③  apply    

      function test(b,c) {

       console.log(this.a, b, c)

      }

      test.apply({a: 1}, [2, 3])   // 1 2 3

      apply 的作用和 call 类似,只是 传参方式不同, apply 将参数全部放置到一个数组当中。

 

05.  Javascript 变量提升

    ①  基本概念:变量可以后定义先使用。

      a = 2

      console.log(a)  // 2

      var a = 1

      console.log(a)  // 1

      如 后定义了变量 a,但可以在之前使用,是因为变量 a 的定义过程被提前了。

    

    ②  函数同样支持变量提升,可以使用后定义的提升

      test()   // 123

      function test() {

       console.log('123)

      }

 

    注意:字面量定义的函数不支持变量提升

      test()   // error:test is not a function

      var test = function() {

       console.log('123)

      }

      因为 test 变量会进行提升,并且默认值为 undefined。只有执行到赋值语句时,test 才会 变成一个函数。

 

06.  Javascript 作用域

    主要分三种,常用的是全局作用域和函数作用域

    ①  全局作用域

      var a = 123

      function test() {

       console.log(a)

      }

      test()   // 123

      全局作用域的变量,在函数中是可以被访问的,是因为作用域链在起作用,函数内部查找作用域没有找到变量 a 后,会到它的父级作用域,即全局作用域去查找。

 

    ②  函数作用域

      function test() {

       var a = 123

      }

      console.log(a)   // undefined

      函数作用域的变量,在全局作用域中是无法被访问的,要解决这个问题要通过闭包。

      解决方法:

      function test() {

       var a = 123

       return function() {

        return {

         a

        }

       }

      }

      console.log(test()().a)  // 123

      在函数中返回一个函数会形成一个闭包,通过闭包我们可以访问到函数作用域下的变量。

 

07.  Javascript 异常处理

    分为被动触发和主动抛出

    ①   // Uncaught ReferenceError: a is not defined

      console.log(a)

       上面的代码由于没有定义变量 a , 所以会触发未定义错误,属于被动触发。

    

    ②   // Uncaught Error: crash

      throw new Error('crash')

      主动抛出需要使用 throw 关键字,后面需要实例化一个 Error 对象。

 

    ③  如果希望主动捕获异常,可以通过 try  catch ,

      try {

       console.log(a)

      } catch(err) {

       console.error(err)

      }

      通过 try 捕获异常,catch 处理异常。

 

08.  Javascript 原型

    术语:1. 实例(对象):通过构造函数创建出来的对象叫做实例(对象)    注:实例对象可以有多个

       2. 实例化:构造函数创建出来的对象过程

       3. 成员:对象的属性和方法的一个统称

   一.  prototype 

    ①  任何一个函数都有 prototype 属性

      function Person() {}   //  构造函数

      console.dir(Person)   // ƒ Person()  >  prototype: {constructor: ƒ}

 

    ②  函数的 prototype 属性值是一个对象,这个对象叫做原型 (或叫做原型对象)

      function Person() {}   // Person 是函数也是一个对象

      console.log(Person.prototype)    // {constructor: ƒ}   // 这个就是原形对象

    

    ③  作用:通过构造函数创建的实例对象 可以直接访问这个构造函数的 prototype 属性 上的任意成员。 

                   p                                                    原型

      function Person() {}   

      console.log(Person.prototype)   // 原型对象

      var p = new Person()     // 这就是通过构造函数创建的实例对象 

 

      //  现在给原型对象添加 .color 属性 值为 lime

      Person.prototype.color = "lime"
      Person.prototype.legs = 2
      console.log(Person.prototype)    //  {color: "lime", constructor: ƒ}
      console.log(p.color)    //  lime     
      console.log(p.legs)   //  2   发现 p对象可以访问到 color 属性,有值的,这个 color 的值就来源于原型。
 
  二.  __proto__ (注意:是双下滑线)
      ①  任何一个对象都有 __proto__ 属性
         对象的 __proto__ 属性值指向了当前构造函数的 prototype 属性值。
        function Person() {}
        var p = new Person()
        console.log(p.prototype)
        console.log(p.__proto__)
        console.log(Person.prototype === p.__proto__)     // true
      
      ②  要想访问到原型对象,有两种途径:
        1. 通过构造函数的 prototype 属性
        2. 通过实例对象的 __proto__ 属性
         注意:__proto__ 是个私有的属性,该属性不是标准属性,存在兼容性问题(IE不兼容),所以不要在项目中使用 __proto__ 属性(本地用用即可)
 
   三. constructor  
      ①  constructor 是原型对象中自带的属性
        function Person() {}
        var p = new Person()
          console.log(p.constructor  ===  Person)     // true
        console.log(p.constructor  ===  Person.prototype. constructor )     // true
 
   四.  原型三角函数 关系拟人化
      ①  构造函数原型对象 之间的关系 : 配偶关系
         妈妈    爸爸
        1. 妈妈 通过 prototype 访问 爸爸
        2. 爸爸 通过 constructot 访问 妈妈
 
      ② 构造函数实例对象 之间的关系 :母子关系
        妈妈    孩子
        1. 妈妈 通过 new 创建 孩子
        2. 孩子 不可以 直接访问到 妈妈 (关系不是很融洽)
 
      ③ 原型对象实例对象 之间的关系 :父子关系
        爸爸    孩子
        1. 孩子 通过 __proto__ 属性 访问 爸爸
        2. 孩子 通过 爸爸 的 constructot 属性访问到 妈妈
        
 
09.  Javascript  词法作用域
    概念:是指 函数会根据它的创建位置 决定其 this 指向。
 
    ① 案例:
      var a = 1 
      function test() {
       console.log(this.a)  // 1
      }
      函数 test 创建于全局,所以他的 this 指向 window , this.a 获取到的是 window.a ,等于 1
 
    ②  案例(非常容易混淆)
      var a = 1 
      function test() {
       console.log(this.a) 
      }
 
      var o = {
       a: 2,
       fn() {
        var a = 3
        test()
       }
      }

      o.fn()   // 1

      该案例中共定义了 3 个 a ,从直觉上来说 o.fn 函数执行后,应该获取 o.a ,即打印 2 ,但实际结果打印了 1 ,是因为 test 创建于全局,所以它的 this 仍然指向 window ,这与它的调用环境无关。