前端面试题

css基础题

实现一个正方形的div,宽度是屏幕宽度(可视宽度)的一半

第一种方式: 50vw

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      .box {
        width: 50vw;
        height: 50vw;
        background-color: skyblue;
      }
    </style>
  </head>
  <body>
    <div class="box"></div>
  </body>
</html>

第二种方式: js获取宽度,计算后设置

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      .box {
        background-color: skyblue;
      }
    </style>
  </head>
  <body>
    <div class="box"></div>

    <script>
      let oBox = document.querySelector('.box');
      let halfClientWidth = document.body.clientWidth / 2;
      oBox.style.width = halfClientWidth + 'px';
      oBox.style.height = halfClientWidth + 'px';
      //这种方式也行
      // oBox.style.cssText = `width:${halfClientWidth}px;height:${halfClientWidth}px;`;
    </script>
  </body>
</html>

js基础题

如何区分基础数据类型和引用数据类型

可以使用typeof xxx !== ‘object’ 来判断是基础数据类型(null除外)

let num = 10;
let str = 'abc';
let isTrue = true;
let isNull = null;
let isUndefined = undefined;
let arr = [1, 2, 3];
let obj = { name: 'andy' };

console.log(typeof num); // number
console.log(typeof str); // string
console.log(typeof isTrue); // boolean
console.log(typeof isNull); // object
console.log(typeof isUndefined); // undefined
console.log(typeof arr); // object
console.log(typeof obj); // object
function judgeType(item) {
  if (Array.isArray(item)) {
    //数组类型
    console.log('array');
  } else if (item instanceof Object) {
    //对象类型
    console.log('object');
  } else if (typeof item !== 'object') {
    //基础数据类型
    console.log(typeof item);
  } else {
    //null
    console.log('null');
  }
}
es5实现继承

js中继承的终极方法:
1.在子类的构造函数里面使用call去调用父类的构造函数(这样就可以将属性添加到子类上)
2.将子类的原型对象修改为父类的实例对象(可以父类原型中添加的方法和属性了)

<script>
  function Person(name, age) {
    this.name = name;
    this.age = age;
  }
  Person.prototype.say = function () {
    console.log('hello');
  };

  function Student(name, age, height) {
    this.height = height;
    Person.call(this, name, age);
  }

  Student.prototype = new Person();
  Student.prototype.constructor = Student;
  Student.prototype.run = function () {
    console.log('run');
  };

  let stu = new Student('andy', 23, 178);
  console.log(stu);
  stu.run();
  stu.say();
</script>
es6实现继承 extends + super();
<script>
  class Person {
    constructor(name, age) {
      this.name = name;
      this.age = age;
    }
    run() {
      console.log('run');
    }
  }

  //使用extends关键字实现继承
  class Student extends Person {
    constructor(myName, myAge, myScore) {
      super(myName, myAge); //调用父类的constructor构造函数
      this.score = myScore;
    }
    study() {
      console.log('study');
    }
  }

  let stu = new Student('andy', 23, 99);
  console.log(stu);
  stu.study();
  stu.run();
</script>
class 里面的static是什么意思

static 用来定义静态方法,只能通过类来调用,不能通过类的实例来调用
先看一下MDN的解释:
在这里插入图片描述

<script>
  class Person {
    constructor(name, age) {
      this.name = name;
      this.age = age;
      //实例方法必须写在constructor里面
      this.hi = function () {
            console.log('hi');
          };
    }
    //在这里写的方法会添加到原型上
    run() {
      console.log('run');
    }

    //静态方法
    static say() {
      console.log('hello world');
    }
  }

  let p = new Person('andy', 23);
  console.log(p);
  p.run();
  Person.say();
</script>

如果需要定义静态属性,不可以使用static num = 666;这样的写法,因为不是所有浏览器都支持
可以这样来定义

//静态属性
Person.num = 666;

console.log(p.num); // undefined
console.log(Person.num);
es5的类和es6类的区别
  • es5可以自定义原型,es6不可以自定义原型
function Person(name, age) {
  this.name = name;
  this.age = age;

  this.hi = function () {
    console.log('hi');
  };
}

// Person.prototype.say = function(){
//   console.log('say');
// }

//自定义原型对象
Person.prototype = {
  constructor: Person,
  run() {
    console.log('run');
  },
};

let p = new Person('andy', 23);
console.log(p);
p.run();

es6的类不允许自定义原型对象,只能动态的添加

<script>
  class Person {
    constructor(name, age) {
      this.name = name;
      this.age = age;
      this.hi = function () {
        console.log('hi');
      };
    }
    run() {
      console.log('run');
    }
  }

  Person.prototype.say = function () {
    console.log('say');
  };
  // Person.prototype = {
  //   say() {
  //     console.log('say');
  //   },
  // };
  let p = new Person('andy', 23);
  console.log(p);
  p.run();
  p.say();
</script>
获取对象类型

xxx.constructor.name

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

let obj = new Object(); // Object
let arr = new Array(); //Array
let stu = new Student(); //Student

console.log(obj.constructor.name); //Object
console.log(arr.constructor.name); //Array
console.log(stu.constructor.name); //Student

Object.prototye.toString.call(xxx);

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

let obj = new Object(); // Object
let arr = new Array(); //Array
let stu = new Student(); //Student

console.log(Object.prototype.toString.call(obj)); // [object Object]
console.log(Object.prototype.toString.call(arr)); // [object Array]
console.log(Object.prototype.toString.call(stu)); // [object Object]

为什么是这样呢?

console.log(Object.prototype.toString.call(stu)); // [object Object]

想一下我们在使用构造函数的时候,new一下发生了什么?

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

function mockNew(fn,...args){
  let obj = {};
  obj.__proto__ = fn.prototype;
  let res = fn.apply(args);
  return res instanceof Object ? res : obj;
}

在使用new的时候第一步就是 let obj = {}; 所以这是正常的

console.log(Object.prototype.toString.call(stu)); // [object Object]
instanceof 和 isPrototypeOf

a instanceof B 判断a是不是B的实例对象(B只要是a原型链上的对是true)
c.isPrototypeOf(d) 判断c是不是d的原型对象 (c只要是d原型链上的都是true)

<script>
  class Person {
    constructor(name) {
      this.name = name;
    }
  }

  class Student extends Person {
    constructor(name, age) {
      super(name);
      this.age = age;
    }
  }

  let stu = new Student('andy', 23);
  console.log(stu instanceof Student);
  console.log(stu instanceof Person);
  console.log(Student.prototype.isPrototypeOf(stu));
  console.log(Person.prototype.isPrototypeOf(stu));
</script>
对象深拷贝
  • JSON.parse(JSON.stringify(obj));
    缺点:会丢失undefined 和 function
const target = {
  field1: 'andy',
  field2: null,
  field3: undefined,
  field4: ['andy', 'ted'],
  field5: { age: 23, height: 178 },
  field6() {
    console.log(666);
  },
};

const newObj = JSON.parse(JSON.stringify(target));
console.log(newObj);

在这里插入图片描述

  • 手写深拷贝
const target = {
  field1: 'andy',
  field2: null,
  field3: undefined,
  field4: ['andy', 'ted'],
  field5: { age: 23, height: 178 },
  field6() {
    console.log(666);
  },
};

function clone(obj) {
  if (obj && typeof obj === 'object') {
    let cloneTarget = Array.isArray(obj) ? [] : {};
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        cloneTarget[key] = clone(obj[key]);
      }
    }
    //返回最终的对象
    return cloneTarget;
  } else {
    return obj;
  }
}

let res = clone(target);
console.log(res);

加一句代码 target.target = target;

      const target = {
        field1: 'andy',
        field2: null,
        field3: undefined,
        field4: ['andy', 'ted'],
        field5: { age: 23, height: 178 },
        field6() {
          console.log(666);
        },
      };
      target.target = target;

      function clone(obj, map = new Map()) {
        if (obj && typeof obj === 'object') {
          let cloneTarget = Array.isArray(obj) ? [] : {};
          if (map.has(obj)) {
            return map.get(obj);
          }
          map.set(obj, cloneTarget);
          for (let key in obj) {
            if (obj.hasOwnProperty(key)) {
              cloneTarget[key] = clone(obj[key], map);
            }
          }
          //返回最终的对象
          return cloneTarget;
        } else {
          return obj;
        }
      }

      let res = clone(target);
      console.log(res);
数组扁平化

概念:将一个多维数组变成一个一维数组

[1,2,3,[4,5,[6]]] -->  [1,2,3,4,5,6]
  • 方式1:reduce + concat
    遍历数组的每一项,若值为数组则递归,如果是数值,直接concat
let arr = [1, 2, 3, [4, 5], [6, 7, 8, [9]]];

function flat(arr) {
  return arr.reduce((previous, current) => {
    return previous.concat(
      Array.isArray(current) ? flat(current) : current
    );
  }, []);
}

console.log(flat(arr)); //[1,2,3,4,5,6,7,8,9]
  • 递归 + concat
    在这里插入图片描述
function flat(arr) {
  let res = [];
  arr.forEach((item) => {
    if (Array.isArray(item)) {
      res = res.concat(flat(item));//因为concat不改变原数组,所以这里要赋值
    } else {
      res.push(item);
    }
  });
  return res;
}
  • 扩展运算符(es6的扩展运算符能将二维数组变为一维)
...可以将二维数组变成一维数组 [1,2,3,[4,5]] --> [1,2,3,4,5]
function flat(arr) {
  while (arr.some((item) => Array.isArray(item))) {
    arr = [].concat(...arr);
  }
  return arr;
}
对象扁平化
const obj = {
  a: 1,
  b: [1, 2, { c: true }],
  c: { e: 2, f: 3 },
  g: null,
  h: undefined
};

扁平化之后

{
	a:1,
	b[0]:1,
	b:[1]:2,
	b[2].c:true,
	c.e:2,
	c.f:3,
	g:null,
	h: undefined
}

flatten函数
思路:如果是引用类型就一层一层递归,最后返回result;

const obj = {
  a: 1,
  b: [1, 2, { c: true }],
  c: { e: 2, f: 3 },
  g: null,
  h: undefined,
};

function flatten(obj) {
  let result = {};
  for (let key in obj) {
    partition(result, key, obj[key]);
  }
  return result;
}

function partition(result, key, param) {
  if (Array.isArray(param)) {
    for (let index in param) {
      partition(result, key + '[' + index + ']', param[index]);
    }
  } else if (param instanceof Object) {
    for (let k in param) {
      partition(result, key + '.' + k, param[k]);
    }
  } else if (typeof param !== 'object') {
    result[key] = param;
  } else {
    result[key] = null;
  }
}

let res = flatten(obj);
console.log(res);
函数防抖

一段时间内的多次触发,只执行最后一次触发

  <body>
    <input type="text" id="input" />
    <script>
      let oInput = document.querySelector('#input');
      function debounce(fn, delay) {
        let timer = 0;
        return function (...args) {
          if (timer) clearTimeout(timer);
          timer = setTimeout(() => {
            fn.apply(this, args);
          }, delay);
        };
      }
      oInput.addEventListener(
        'input',
        debounce((e) => {
          console.log(e.target.value);
        }, 800)
      );
    </script>
  </body>
函数节流

一段时间内的多次触发,按照delay间隔执行

  <body>
    <div class="box" draggable="true"></div>
    <script>
      let oBox = document.querySelector('.box');

      function throttle(fn, delay) {
        let timer = 0;
        return function (...arg) {
          if (timer) return;
          timer = setTimeout(() => {
            fn.apply(this, arg);
            timer = null;
          }, delay);
        };
      }

      oBox.addEventListener(
        'drag',
        throttle(function () {
          console.log(this);
        }, 200)
      );
    </script>
  </body>
垃圾回收机制

什么是垃圾回收?
就是当函数执行完,需要回收那些再也用不到的一些对象和数据

  • 引用计数法
    缺陷:会存在循环引用的问题,现代浏览器已经不用这个算法
  • 标记清除法
    定期从window开始遍历各个属性,判断某个对象是不是可到达,回收不可到达的对象
闭包是内存泄漏吗

闭包不是内存泄露,闭包的数据不会被垃圾回收(符合我们预期的)

如何检测js内存泄露

检测内存泄露也就是检测内存变化,如果内存一直是持续升高的状态就说明是泄露了,如果内存的变化是有规律的升高、降低,那么就不是内存泄露

最常规的检测方法:控制台 --> perfermance -> 选中memory,录制一段时间的操作 -> 查看Heap的走势
heap折线图如果一只升高,则存在内存泄露
在这里插入图片描述
正常的heap情况:
在这里插入图片描述

Vue相关

内存泄露的场景(Vue为例)
  • 被全局变量、全局函数引用,组件销毁时未清除
<template>
  <p>memory leak</p>
</template>

<script>
export default {
  name: 'MemoryLeak',
  data() {
    return {
      arr: [1, 2, 3, 4],
    };
  },
  methods: {},
  mounted() {
    //被全局变量、全局函数引用
    window.arr = this.arr;
    window.printArr = () => {
      console.log(this.arr);
    };
  },
  // vue2-beforeDestory
  beforeUnmount() {
    //组件销毁前要清除
    window.arr = null;
    window.printArr = null;
  },
};
</script>
  • 被全局事件、定时器引用,组件销毁时未清除
<template>
  <p>memory leak</p>
</template>

<script>
export default {
  name: 'MemoryLeak',
  data() {
    return {
      arr: [1, 2, 3, 4],
      intervalId: 0,
    };
  },
  methods: {
    printArr() {
      console.log(this.arr);
    },
  },
  mounted() {
    //定时器
    this.intervalId = setInterval(() => {
      console.log(this.arr);
    }, 200);
    //全局事件
    window.addEventListener('resize', this.printArr);
  },
  beforeUnmount() {
    //组件销毁前一定要清除定时器
    if (this.intervalId) {
      clearInterval(this.intervalId);
    }
    //清除全局事件
    window.removeEventListener('resize', this.printArr);
  },
};
</script>
vueRouter 路由模式的区别

在这里插入图片描述

算法题

最大子数组和 leetcode 53

在这里插入图片描述

function maxSubArray(arr) {
  if (arr.length === 1) return arr[0];
  for (let i = 1; i < arr.length; i++) {
    let lastItem = arr[i - 1];
    if (lastItem > 0) {
      arr[i] += lastItem;
    }
  }
  return Math.max(...arr);
}
使用Array模拟Set leetcode 705

在这里插入图片描述

实现add 添加
实现remove 移除
实现contains 是否包含
实现getRandom 随机获取一个元素

<script>
  class MySet {
    constructor() {
      this.map = new Map();
      this.mySet = [];
    }
    add(key) {
      if (this.map.has(key)) return;
      this.mySet.push(key);
      let index = this.mySet.length - 1;
      this.map.set(key, index);
    }
    remove(key) {
      if (this.map.has(key)) {
        let index = this.map.get(key);
        this.mySet.splice(index, 1);
        this.map.delete(key);//删除key
      }
    }
    getRandom() {
      let random = Math.floor(Math.random() * this.mySet.length);
      return this.mySet[random];
    }
    contains(key) {
      return this.map.has(key);
    }
  }

  let set = new MySet();
  set.add(1);
  set.add(2);
  set.add(3);
  set.add(4);
  set.add(5);
  console.log(set.contains(1)); //true
  console.log(set.contains(2)); //true
  console.log(set.contains(3)); //false
  set.add(2);
  console.log(set.contains(2)); //true
  set.remove(2);
  console.log(set.contains(2)); //false
  console.log(set.getRandom()); //随机获取set值
</script>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值