• Javascript面试知识点


    注:链接大部分是笔记的参考文章,想详细了解的可以点进去看。

    *Javascript (一):封装

    http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance.html

    function Cat(name,color){
      this.name = name;
      this.color = color;
    }
     Cat.prototype.type = "猫科动物";
     Cat.prototype.eat = function(){alert("吃鱼")};
      
    let cat1 = new Cat("小黄","black");
    let cat2 = new Cat("锋锋","white");
    cat1.eat();
    

    *Javascript(二):构造函数的继承

    //动物类
    function Animal(){
      this.species = "动物";
    }
    //猫类
    function Cat(name,color){
      this.name = name;
      this.color = color;
    }
    
    //如何让猫继承动物呢?
    
    

    一、 构造函数绑定

    使用call或apply方法

    function Cat(name,color){
      Animal.apply(this,arguments);
      this.name = name;
      this.color = color;
    }
    var cat1 = new Cat;
    alert("cat1.species");//动物
    
    

    二、 prototype模式

    Cat.prototype = new Animal();
    Cat.prototype.constructor = Cat;
    var cat1 = new Cat;
    alert("cat1.species");//动物
    

    任何一个prototype对象都有一个constructor属性,指向它的构造函数.
    当执行了Cat.protoType = new Animal 这句代码后,Cat.protoType.constructor指向Animal
    而实例car1的cat1.constructor也指向Animal

    因此我们必须手动纠正,将Cat.prototype对象的constructor值改为Cat。

    为了避免继承链紊乱,下文都遵循这一点,即如果替换了prototype对象,

    o.prototype = new plant();
    o.prototype.constructor = o;
    

    三、直接继承prototype

    function Animal(){}
    Animal.prototype.species = "动物";
    Cat.prototype = Animal.prototype;
    Cat.prototype.constructor = Cat;
    

    虽然这种继承方法相较第二种效率更高更省内存(不用建立Animal实例),但此时,Animal.protoType.constructor也指向了Cat。这种方法其实是错误的。

    四、利用空对象作为中介

    function F(){
    }
    F.prototype = Animal.prototype;
    Cat.prototype = new F();
    Cat.prototype.constructor = Cat;
    alert(Animal.prototype.constructor);//Animal
    

    这样F()几乎不占内存,且修改Cat的prototype.constructor不会影响到Animal

    将上面的方法封装一下,以便调用

    function extend(Child,Parent){
        function F(){}
        F.prototype = Parent.prototype;
        Child.prototype = new F();
        Child.prototypr.constructor = Child;
        Child.uber = Parent.prototype;
    }
        
    

    然后就可以调用啦

    extend(Cat,Animal);
    let cat1 = new Cat("锋锋","黑色");
    alert(cat1.species);//动物
    

    五、拷贝继承

    function Animal(){
      
    }
    Animal.prototype.species = "动物";
    
    function extend2(Child,Parent){
      var c = Child.prototype;
      var p = Parent.prototype;
      
      for(var i in p){
        c[i]=p[i];
      }
      c.uber = p;
    }
    
    extend2(Cat,Animal);
    let cat1 = new Cat("锋锋","黑色");
    alert(cat1.species);//动物
    

    *Javascript(三):非构造函数的继承

    非构造函数的继承,比如:对象间的继承

    var Chinese = {nation:'中国'}
    var Doctor = {career:'医生'}
    
    function object(o){
      function F(){}
      F.prototype = o;
      return new F();
    }
    
    var Doctor = object(Chinese);
    //Doctor.career = '医生';
    alert(Doctor.nation);
    

    一、内置类型

    NaN表示未定义,或不可表示的值
    boolean ->true/ false
    函数就是对象

    图片

    二、Type of

    typeof 对于基本类型,除了 null 都可以显示正确的类型
    对于 null 来说,虽然它是基本类型,但是会显示 object,这是一个存在很久了的 Bug

    三、类型转换

    3.1 boolean

    在条件判断时,除了 undefined, null, false, NaN, '', 0, -0,其他所有值都转为 true,包括所有对象。(不懂)

    3.2对象转基本类型

    对象在转换基本类型时,首先会调用 valueOf 然后调用 toString。这两个方法可以重写。
    (先赋值,后类型转换)

    let a = {
      valueOf() {
        return 0;
      },
      toString() {
        return '1';
      },
      [Symbol.toPrimitive]() {
        return 2;
      }
    }
    1 + a // => 3
    '1' + a // => '12'
    
    

    3.3 四则运算符

    当数字和字符串进行运算时:如: '2'和4
    加法运算:字符串优先级高。其它运算,数字优先级高。

    '2'+4='24'
    '2'*4 =8
    [1, 2] + [2, 1] // '1,22,1'
    'a' + + 'b' // -> "aNaN"
    

    因为: + '1' -> 1

    四、原型

    构造函数
    指向 就是“===”(完全等于)
    prototype上的所有属性,子孙都可以共享

    function Foo() {}
    const f1 = new Foo();
    f1.__proto__ === Foo.prototype;  // 实例(子)的__proto__ === 其构造函数(父)的原型(prototype)
    Foo.__proto__ === Function.prototype;
    
    Foo.prototype.constructor ===Foo(); 
    

    五、instanceof(实例)

    》f1 instanceof Foo
    

    》true

    六、this

    http://www.ruanyifeng.com/blog/2018/06/javascript-this.html
    谁调用this指向谁

    七、闭包

    【详情见:http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
    一般情况下:

    function f1(){
      var n=100;
      function f2(){
        alert(n);  //100
      }
    }
    

    一般情况下,内部函数可以读取全局变量,但是外部函数无法读取内部变量。

    但是!!!在f1函数return内部f2函数!!就可以在f1外部读取到内部变量了!!

    闭包的目的:能够读取其他函数内部变量的函数
    在下面代码中,f2函数就是闭包

    function f1(){
      let n=100;
      function f2(){
        alert(n);
      }
      return f2(); 
    }
    
    let result = f1();
    result();  //100
    

    闭包的用途

    闭包的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

    看下面的例子:

    function f1(){
      let n=100;
      add=function(){n+=1;}
      
      funciton f2(){
        alert(n);
      }
      retrun f2();
    }
    
    let result = f1();
    result(); //100
    add();
    result();//101
    

    result运行了两次。一次值为100,第二次值为101,这证明了函数f1中的局部变量n一直保存在内存中,没有在f1调用后被自动清除。

    使用闭包的注意点

    1. 由于闭包会使函数中的变量都被保存在内存中,所以不能滥用闭包。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

    2. 闭包会在父函数外部,改变父函数内部变量。所以一定要小心,不要随便改变父函数内部变量的值。

    思考题

    例子1:

    var name = "The Window";
    
      var object = {
        name : "My Object",
    
        getNameFunc : function(){
          return function(){
            return this.name;
    

          };

        }

      };

      alert(object.getNameFunc()());

    例子2:

     var name = "The Window";
    
      var object = {
        name : "My Object",
    
        getNameFunc : function(){
          var that = this;
          return function(){
            return that.name;
    

          };

        }

      };

      alert(object.getNameFunc()());

    例子1 输出结果是:The Window
    例子2 输出结果是:My Object

    this的指向是由它所在函数调用的上下文决定的,而不是由它所在函数定义的上下文决定的。

    例子1中alert(object.getNameFunc()());这句调用的时候在window中,所以this指向window

    八、深浅拷贝

    浅拷贝

    var Chinese={nation:'中国'};
    var Doctor ={career:'医生'};
    
    function extendcopy(p){
      var c={};
      for( var i in p){
        c[i]=p[i]
      }
      c.uber=p;
      return c;
    }
    
    Chinese.birthPlace=['广州','四川','上海'];
    var Doctor = extendcopy(Chinese);
    Doctor.birthPlace.push('厦门');
    alert(Chinese.birthPlace);//广州,四川,上海,厦门
    
    

    当父对象属性为数组时,子对象改变,会将父对象的属性改变。
    extendCopy()只是拷贝基本类型的数据,我们把这种拷贝叫做"浅拷贝"

    深拷贝

    function deepcopy(p,c){
       var c = c ||{} //如果不传入c,则赋c为{
       for( var i in p){
         if(typeof p[i] === 'object' ){
           c[i] = (p[i].construcor===array)?[]:{};
           deepcopy(p,c);
         }else{
           c[i]=p[i];
         }
       }
       return c;
       }
    

    这样写,父对象就不会受到影响了。

    九、call、apply、bind使用和区别

    https://juejin.im/post/5a9640335188257a7924d5ef

    首先,它们的作用是什么呢?
    答案是:改变函数执行时的上下文,再具体一点就是改变函数运行时的this指向。

      let obj = {name: 'tony'};
      
      function Child(name){
        this.name = name;
      }
      
      Child.prototype = {
        constructor: Child,
        showName: function(){
          console.log(this.name);
        }
      }
      var child = new Child('thomas');
      child.showName(); // thomas
      
      //  call,apply,bind使用
      child.showName.call(obj);
      child.showName.apply(obj);
      let bind = child.showName.bind(obj); // 返回一个函数
    

    bind(); // tony

    • call、apply与bind的差别

    call和apply改变了函数的this上下文后便执行该函数,而bind则是返回改变了上下文后的一个函数。

    • call、apply的区别

    他们俩之间的差别在于参数的区别,call和aplly的第一个参数都是要改变上下文的对象。
    除了第一个参数外,call 可以接收一个参数列表,apply 只接受一个参数数组。

     let arr1 = [1, 2, 19, 6];
    //例子:求数组中的最值
    console.log(Math.max.call(null, 1,2,19,6)); // 19
    console.log(Math.max.call(null, arr1)); // NaN
    console.log(Math.max.apply(null, arr1)); //  19 直接可以用arr1传递进去
    

    主要应用:
    1、将伪数组转化为数组(含有length属性的对象,dom节点, 函数的参数arguments)

    case1: dom节点:
    
    <div class="div1">1</div>
    <div class="div1">2</div>
    <div class="div1">3</div>
    
    let div = document.getElementsByTagName('div');
    console.log(div); // HTMLCollection(3) [div.div1, div.div1, div.div1] 里面包含length属性
    
    let arr2 = Array.prototype.slice.call(div);//Array.prototype.slice,浅拷贝
    console.log(arr2); // 数组 [div.div1, div.div1, div.div1]
    
    
    

    十、Map、FlatMap 和 Reduce

    Map

    语法:var new_array = arr .map(function callback(currentValue [,index [,array]]){
    //返回new_array的元素
    } [, thisArg ])

    • 参数:currentValue 必选,当前元素

    index 可选,索引
    arr 可选,原数组

    [1, 2, 3].map((v) => v + 1)
    // -> [2, 3, 4] 
    

    FlatMap

    语法:var new_array = arr .flatMap(function callback(currentValue [,index [,array]]){
    //返回new_array的元素
    } [, thisArg ])

    Map和FlatMap的区别

    区别1:
    arr1.map( x => [x * 2] ); 
     // [[2], [4], [6], [8]]
    
     arr1.flatMap(x => [x * 2] ); 
      // [2, 4, 6, 8]
      
    区别2:(flatMap原数组将维)
    [1, [2], 3].flatMap((v) => v + 1)
    

    // -> [2, 3, 4]

    Reduce
    Reduce 作用是数组中的值组合起来,最终得到一个值

    function a() {
        console.log(1);
    }
    
    function b() {
        console.log(2);
    }
    
    [a, b].reduce((a, b) => a(b()))
    

    // -> 2 1

    十一、async 和 await

    async是异步的意思。
    一个函数如果加上 async ,那么该函数就会返回一个 Promise

    async function test() {
      return "1";
    }
    console.log(test()); // -> Promise {<resolved>: "1"}
    

    await只能用在async函数中,就是用于等待异步完成

    function sleep() {
      return new Promise(resolve => {
        setTimeout(() => {
          console.log('finish')
          resolve("sleep");
        }, 2000);
      });
    }
    async function test() {
      let value = await sleep();
      console.log("object");
    }
    test()
    

    在上面代码中会先打印‘finish’再打印‘object’,因为await会等待sleep()执行完。

    async和await相比Promise的优缺点:

    优点:处理 then 的调用链,能够更清晰准确的写出代码。

    缺点:滥用 await 可能会导致性能问题,因为 await 会阻塞代码,也许之后的异步代码并不依赖于前者,但仍然需要等待前者完成,导致代码失去了并发性。

  • 相关阅读:
    vue(七)--监听属性(watch)
    vue(六)--计算属性(computed)
    JVM参数配置&&命令工具
    GC回收算法&&GC回收器
    JVM宏观认知&&内存结构
    分布式配置中心Apollo——QuickStart
    了解敏捷开发
    服务链路跟踪 && 服务监控
    数据读写API——IO流
    Nginx+Zuul集群实现高可用网关
  • 原文地址:https://www.cnblogs.com/shendan/p/10200364.html
Copyright © 2020-2023  润新知