JavaScipt 面试题+知识点

Posted ~,555555

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScipt 面试题+知识点相关的知识,希望对你有一定的参考价值。

变量类型

前置知识

  • 值类型 vs 引用类型

    // 值类型
    let a = 100;
    let b = a;
    a = 200;
    console.log(b); //100
    
    // 引用类型
    let a = { age: 20 };
    let b = a;
    b.age = 21;
    console.log(a.age); //21
    


    // 常见值类型
    let  a // undefined
    const s = 'abc' 
    const n = 100
    const b = true
    const m = Symbol('m');
    
    // 常见引用类型
    const obj = { x: 100 };
    const arr = ['a', 'b', 'c'];
    const n = null // 特殊引用类型 指针指向空地址
    
    // 特殊引用类型 因为不用于存储数据,所以没有“拷贝、复制函数”一说
    function fn(){}
    
  • 逻辑运算

    • truthy变量:!!a === true 的变量

      let a = 100;
      !a; //false
      !!a; //true
      
    • falsy变量:!!a === false 的变量

      javascript 中只有 8 falsy 值。

      // 以下是falsely变量 除此之外都是truely变量
      !!0 === false
      !!-0 ===false
      !!NaN === false
      !!0n ===false (bigInt)
      !!'' === false
      !!null === false
      !!undefined === false
      !!false === false
      
    1. if 语句 判断的是否为truthy或者falsy。

    2. 与或非同样判断的是 truthy或者falsy

      console.log(10&&0); //0
      console.log(''||'abc') //'abc'
      console.log(!window.abc) //true
      

题目

  1. typeof 能判断哪些类型

    // typeof 可以判断所有值类型
    let a;                     typeof a; //undefined
    let str = 'abc';           typeof str; // string
    const n = 100;             typeof n; // number
    const b = true;            typeof b; // boolean
    const s = Symbol('s');     typeof s; // symbol
    let big = BigInt(10e15);   typeof big; // bigint
    
    // 能判断引用类型
    typeof console.log         // function
    typeof function(){}        // function
    
    // 能识别引用类型
    typeof null;               // object
    typeof [1, 2, 3];          // object
    typeof {x:100};            // object
    
  2. 何时使用 === 何时使用 ==

    // 除了 == null以外,其他一律用 ===, 例如
    const obj = {x:100}
    if(obj.a == null) {}
    // 相当于
    if(obj.a === null || obj===undefined){}
    
  3. 值类型和引用类型的区别

    值类型的值直接存储在栈中,而引用类型在栈中存储的是地址,地址指向其在堆中对应的值。

  4. 手写深拷贝

    const obj = {
        age: 20,
        name: '张三',
        address: {
            city: 'beijing'
        },
        arr: [1, 2, 3, [4, 5, 6]]
    }
    
    /**
     * 深拷贝
     * @param {Object} obj  要拷贝的对象
     */
    
    function deepClone(obj) {
        if (typeof obj !== 'object' || obj === undefined) {
            // 如果obj不是object类型或者为undefined
    
            return obj;
        }
        //  初始化返回结果
        let result;
        if (obj instanceof Array) {
            result = [];
        } else {
            result = {};
        }
        for(let key in obj){
            // 保证key 不为原型的属性
            if(obj.hasOwnProperty(key)){
                // 递归 如果是数组,此时的key为索引
                result[key] = deepClone(obj[key]);
            }
        }
        return result;
    }
    const obj2 = deepClone(obj);
    

前置知识

  1. class

    • constructor
    • 属性
    • 方法
    class Student{
        constructor(name,number){
            this.name = name;
            this.number = number;
        }
        sayHi(){
            console.log(`姓名${this.name}  学号:${this.number}`);
        }
    }
    // 实例化对象
    const Alex = new Student('Alex','001');
    console.log(Alex.name);
    console.log(Alex.number);
    Alex.sayHi();
    
  2. 继承

    • extends
    • super
      • 执行 super.method(...) 来调用一个父类方法。
      • 执行 super(...) 来调用一个父类 constructor(只能在我们的 constructor 中)。
    // 父类
    class People{
        constructor(name){
            this.name = name;
        }
        eat(){
            console.log(`${this.name} can eat`);
        }
    } 
    
    // 子类
    class Student extends People{
        constructor(name,number){
            super(name);
            this.number = number;
        }
        sayHi(){
            super.eat();
            console.log(`姓名${this.name}  学号:${this.number}`);
        }
    }
    
    const Alex = new Student('Alex','001');
    console.log(Alex.name);
    console.log(Alex.number);
    Alex.sayHi();
    

题目

  1. 如何准确判断一个变量是否为数组?

    Array.isArray(xx) 或者 xx instanceof Array

  2. 手写一个简易的jQuery,考虑插件和拓展性?

    class jQuery{
        constructor(selector){
            this.selector = selector;
            const result = document.querySelectorAll(selector);
            for(let i =0; i<result.length;i++){
                this[i] = result[i];
            }
            this.length = result.length;
        }
        get(index){
            return this[index];
        }
        each(fn){
            for(let i=0;i<this.length;i++){
                fn(this[i])
            }
        }
        on(type,fn){
            return this.each(elem=>{
                elem.addEventListener(type,fn,false);
            })
        }
    }
    
    // 插件
    jQuery.prototype.dialog =function(info){
        alert(info);
    }
    
    // 复写
    class myJQuery extends jQuery{
        constructor(selector){
            super(selector);
        }
        getLenth(){
            console.log(this.length);
        }
    }
    
     new myJQuery('p').getLenth();
    
    
  3. 如何理解class的原型本质

    人们常说 class 是一个语法糖,因为我们实际上可以在没有 class 的情况下声明相同的内容。

    尽管,它们之间存在着重大差异:

    1. 首先,通过 class 创建的函数具有特殊的内部属性标记 [[IsClassConstructor]]: true。因此,它与手动创建并不完全相同。

      编程语言会在许多地方检查该属性。例如,与普通函数不同,必须使用 new 来调用它:

    2. 类方法不可枚举。 类定义将 "prototype" 中的所有方法的 enumerable 标志设置为 false

      这很好,因为如果我们对一个对象调用 for..in 方法,我们通常不希望 class 方法出现。

    3. 类总是使用 use strict。 在类构造中的所有代码都将自动进入严格模式。

作用域和闭包

前置知识

  • 自由变量

    • 一个变量在当前作用域没有定义,但是被使用了
    • 向上一级作用域,一层一层依次寻找,直至找到为止
    • 如果到全局作用域都没有找到,则报错xx is not defined
  • 作用域

    • 全局作用域
    • 函数作用域
    • 块级作用域
  • 闭包

    • 作用域应用的特殊情况,有两种表现:①函数作为参数传递 ②函数作为返回值被返回

    • 最关键的地方:在函数定义的位置找!

    • 所有的自由变量的查找,是在函数定义的地方,向上级作用域查找,不是在执行的地方。

      // 函数作为返回值
      function create() {
          let a = 100;
          return function () {
              console.log(a);
          }
      }
      
      let fn = create();
      
      let a = 200;
      
      fn(); //100
      
      // 函数作为参数
      function print(fn){
          let a = 200;
          fn();
      }
      
      let a =100;
      
      function fn(){
          console.log(a);
      }
      print(fn); //100
      
  • this

    this的值是在函数调用时确定的,不是在函数定义时确定的。

    • 作为普通函数

      function fn1(){
          console.log(this);
      }
      
      fn1(); // window
      
    • 使用call apply bind

      fn1.call({x:100});  // this指向 {x:100}
      
      const fn2 = fn1.bind({x:200});
      fn2();  //{x:200}
      
    • 作为对象方法被调用

      const zhangsan = {
          name: '张三',
          sayHi(){
              // this 即当前对象
              console.log(this);
          },
          wait(){
              setTimeout(function(){
                  // this指向window
                  console.log(this);
              },10);
          },
          waitAbitLonger(){
              setTimeout(() => {
                  console.log(this);
              }, 100);
          }
      }
      
      zhangsan.sayHi();
      zhangsan.wait(); //window
      zhangsan.waitAbitLonger(); //指向对象张三
      
      const wait = zhangsan.waitAbitLonger;
      wait(); // window
      
    • 在class方法中被调用

      class People{
          constructor(name){
              this.name = name;
              this.age = 20;
          }
          sayHi(){
              console.log(this);
          }
      }
      
      const zhangsan = new People('张三');
      zhangsan.sayHi(); // zhangsan 对象
      
    • 箭头函数

题目

  1. this 的不同应用场景,如何取值?

  2. 手写 bind 函数

    function fn1(...args){
        console.log('this',this);
        console.log(...args);
        return 'this is fn1'
    }
    
    
    // const fn2 = fn1.bind({x:200},10,20,30);  原生bind
    // const res = fn2();
    // console.log(res);
    
    // 手写bind
    Function.prototype.bind1= function(){
        // 将参数变为数组
        const args = [...arguments];
        // 获取this
        const t = args.shift();
          
        // 此时的this指向调用bind1的函数 比如fn1.bind1() 则bind1()中的this指向fn1
         const self = this;
    
        //返回一个函数
         return function(){
              return self.apply(t,args);
         }
    
    }
    
    let test = fn1.bind1({x:200},10,20,30);
    console.log(test());
    
    // 手写apply
    function fn(...args) {
        console.log(this);  //(**)
        console.log('I got called with ' + [...args].join(' '));
        return [...args].reduce((total, curr) => total + curr, 0);
    }
    
    
    Function.prototype.applyTest = function (t, args) {
        // 此时的this 指向调用applyTest的函数
        // t为需要被指定的上下文
        t.__proto__.fn = this; 
        // 为什么要在原型链上添加,因为如果不在原型链添加 在(**)中打印this时就多了这个函数
    
        let result = t.fn(...args);
        delete t.__proto__.fn;
        return result
    }
    
    let result = fn.apply({ zs: 100 }, [2, 3, 4]);
    console.log(result);
    
    // 瞎写补充一点
    Function.prototype.applyTest = function (t, args) {
        if (t == undefined) {
            if (args == undefined) {
                // 此时没有上下文 也没有参数 直接调用函数即可
                return this(args);
            } else {
                // 展开参数
                return this(...args);
            }
        }
        t.__proto__.fn = this;
        let result;
        if (args == undefined) {
            result = t.fn(args);
        } else {
            result = t.fn(...args);
        }
        delete t.__proto__.fn;
        return result;
    }
    
  3. 闭包在实际开发中的应用场景,举例说明

    // 隐藏闭包中的数据 缓存数据
    // 例子1
    function createCache(){
        const data = {}; // 闭包中的数据被隐藏 不被外界访问
        return {
            set: function(key,val){
                data[key] = val;
            },
            get:function(key){
                return data[key];
            }
        }
    }
    
    const c = createCache();
    c.set('a',100);
    console.log(c.get('a'));
    
    
    // 例子2
    function slow(x) {
      // 这里可能会有重负载的 CPU 密集型工作
      alert(`Call

    以上是关于JavaScipt 面试题+知识点的主要内容,如果未能解决你的问题,请参考以下文章

    Java工程师面试题,二级java刷题软件

    Java进阶之光!2021必看-Java高级面试题总结

    剑指Offer名企面试官精讲典型编程题pdf

    剑指Offer名企面试官精讲典型编程题pdf

    经验总结:Java高级工程师面试题-字节跳动,成功跳槽阿里!

    promise知识点以及面试题