前端面试:JS基础

91 阅读3分钟

1.JavaScript数据类型有哪些

基本类型:Number String Boolean Null Undefined Symbol BigInt 引用数据类型:Object Array Fucntion

区别:基本数据类型保存在栈里面,可以直接访问他的值;引用数据类型保存在堆里面,栈里面保存的是引用地址,通过栈里面的引用地址去访问堆里面的值。

2.深拷贝和浅拷贝区别

浅拷贝的对象如果是基本数据类型,拷贝的就是基本类型的值。如果是引用类型,拷贝的就是引用地址,修改新对象会影响旧对象。
深拷贝会开辟新的内存空间拷贝对象,修改新对象不会影响旧对象。

手写深拷贝

function deepClone(obj) {
  let cloneObj = Array.isArray(obj) ? [] : {};
  if(obj && typeof obj === 'object') {
    forlet key in obj) {
      if(obj.hasOwnProperty(key)) {
        if(obj[key] && typeof obj[key] === 'object') {
          cloneObj[key] = deepClone(obj[key]);
        } else {
          cloneObj[key] = obj[key];
        }
      }
    }
  }
  return cloneObj;
}

const a = [1, 2, 3, 4, 5];
const b = deepClone(a);
a[0] = 0;
a // [0, 2, 3, 4, 5]
b // [1, 2, 3, 4, 5]

3.new对象过程

  • 创建一个空对象。
  • 为空对象添加__proto__属性,指向构造函数的原型对象。
  • 让函数的this指向这个对象,执行构造函数返回结果。
  • 判断执行结果是否为空,为空则返回新对象,否则返回执行结果。

代码实现

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

4.闭包

闭包可以实现让内部函数访问外部函数的变量。如下。

function showName(name) {
  var name = "tom";
  function alertName() {
    alert(name);
  }
  alertName();
}

showName();

5.手写发布者订阅

class eventCenter {
  constructor() {
    this.map = {};
  }

  on(event, fn) {
    this.map[event] = this.map[event] || [];
    this.map[event].push(fn);
  }

  emit(event, data) {
    const fnList = this.map[event] || [];
    if(!fnList || fnList.length === 0) return;
    fnList.forEach(fn => fn.call(undefined, data));
  }

  off(event, fn) {
    const fnList = this.map[event] || [];
    const index = fnList.indexOf(fn);
    if(index < 0) return;
    fnList.splice(index, 1);
  }

  once(event, callback) {
    const f = (data) => {
      callback(data);
      this.off(event, f);
    }
    this.on(event, f)
  }
}

6.instanceOf原理及实现

function myInstanceOf(left, right) {
  let l = left.__proto__;
  let r = right.prototype;
  while(true) {
    if(!l) return false;
    if(l === r) return true;
    l = l.__proto__;
  }
}

7.用js实现sleep

function sleep(time) {
  return new Promise(resolve => setTimeout(resolve, time));
  }
}

8.用js实现防抖、节流

用时间戳实现节流

functon throttle(fn, delay) {
  let nowTime = Date.now();
  
  return function() {
    let curTime = Date.now(), context = this, args = arguments;
    if(curTime - nowTime >= delay) {
      fn.apply(context, args);
      curTime = Date.now();
    }
  }
}

用计时器实现防抖

function debounce(fn, wait) {
  let timer;
    
  return function() {
    let context = this, args = arguments;
    if(timer) clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(context, args);
    }, wait);
  }
}

9.js继承

  1. 原型链继承
function Parent() {
  this.name = "parent";
  this.list = ['1', '2', '3'];
}

Parent.prototype.sayHi = function() {
  console.log('hi');
}

function Child() {};

Child.prototype = new Parent();

var child = new Child();
console.log(child.name); // parent
child.sayHi(); // hi

缺点:无法向父类构造函数传参;一个子类实例更改父类构造函数共有属性时会导致继承的共有属性发生变化。

var s1 = new Child();
var s2 = new Child();
s1.list.push('4');
console.log(s2.list); // [ '1', '2', '3', '4' ]
  1. 构造函数继承

借用call调用Parent函数。

function Parent(name, id) {
  this.id = id;
  this.name = name;
  this.printName = function() {
    console.log(this.name);
  }
}

Parent.prototype.sayName = function() {
  console.log(this.name);
};

function Child(name, id) {
  Parent.call(this, name, id);
}

var child = new Child("tom", "1");

child.printName(); // tom
child.sayName() // Error

缺点:无法继承父类的原型链方法。

  1. 组合继承
function Parent(name, id) {
  this.id = id;
  this.name = name;
  this.list = ['1', '2', '3'];
  this.printName = function() {
    console.log(this.name);
  }
}

Parent.prototype.sayName = function() {
  console.log(this.name);
};

function Child(name, id) {
  Parent.call(this, name, id);
}

Child.prototype = new Parent();

var child = new Child("tom", "1");

child.printName(); // tom

child.sayName() // tom

优点:可继承父类函数的原型链方法,可向父类函数传参。子类构造函数更改父类构造函数公共属性时不会影响共有属性变化。

var s1 = new Child();
var s2 = new Child();
s1.list.push('4');
console.log(s2.list); // [ '1', '2', '3' ]
console.log(s1.list);  // [ '1', '2', '3', '4' ]
  1. 原型式继承

使用Object.create方法

let parent = {
  name: "parent",
  list: ["1", "2", "3"],
  getName: function() {
    return this.name;
  }
};

let child1 = Object.create(parent);
child1.name = "tom";
child1.list.push("4");

let child2 = Object.create(parent);
child2.list.push("5");

console.log(child1.name); // tom
console.log(child1.name === child1.getName()); // true
console.log(child2.name); // parent
console.log(child1.list); // [ '1', '2', '3', '4', '5' ]
console.log(child2.list); // [ '1', '2', '3', '4', '5' ]

缺点:Object.create是浅拷贝,会存在共享内存的问题。

  1. 寄生式继承

二次封装原型式继承,可添加新的属性和方法。

let parent = {
    name: "parent",
    list: ["1", "2", "3"],
    getName: function() {
        return this.name;
    }
};

function clone(obj) {
    let cloneObj = Object.create(obj);
    cloneObj.getList = function() {
        return this.list;
    };
    return cloneObj;
}

let child1 = clone(parent);
let child2 = clone(parent);

child2.list.push('4');

console.log(child1.getName()); // parent
console.log(child1.getList()); // [ '1', '2', '3', '4' ]

缺点:也存在共享内存的问题。

  1. 寄生组合式继承
function clone (parent, child) {
  child.prototype = Object.create(parent.prototype);
  child.prototype.constructor = child;
}

function Parent() {
  this.name = 'parent';
  this.list = [1, 2, 3];
}
Parent.prototype.getName = function () {
    return this.name;
}

function Child() {
    Parent.call(this);
    this.friends = 'tom';
}

clone(Parent, Child);

Child.prototype.getFriends = function () {
    return this.friends;
}

let child = new Child();

console.log(child); // Child { name: 'parent', list: [ 1, 2, 3 ], friends: 'tom' }
console.log(child.getName()); // parent
console.log(child.getFriends()); // tom
  1. extends
class Person {
  constructor(name) {
    this.name = name;
  }

  getName = function () {
    console.log('Person:', this.name);
  }
}

class Child extends Person {
  constructor(name, age) {
    super(name);
    this.age = age;
  }
}
const child = new Child('tom', 20);
console.log(child); // Child { getName: [Function: getName], name: 'tom', age: 18 }
child.getName(); // Person: tom