首页/前端知识汇总/
前端知识汇总
2022-03-08 17:35:08 前端 2114

JavaScript

1. 原型/原型链

每个对象都有一个特殊的属性叫做原型,原型分为隐式原型(__ proto __)和显示原型(prototype),对象的隐式原型指向他构造函数的显示原型。
原型链原理:从一个实例对象往上找构造这个实例的相关联的对象,这个相关联的对象往上找又有创造他的上一级的原型对象,以此类推,一直到object.prototype终止。
总结:一切对象都是继承自Object对象,Object对象直接继承自根源对象 NULL,一切函数对象(包括Object对象),都是继承自Function对象,Function对象的 __ proto __会指向自己的原型对象,最终还是继承 Object对象。

2. 闭包

有权访问另一个函数作用域中的变量的函数,创建闭包的方式,在一个函数的内部创建一个函数。
闭包优点:

  1. 创建私有变量
  2. 延长变量的生命周期

闭包缺点:

  1. 变量会常驻内存空间,增大内存空间使用量,容易造成内存泄漏

3. typeof 能判断哪些类型

  1. 能判断所有值类型
  2. 能判断引用类型(不可再细分)
  3. 能判断函数

4. 何时使用 三等 和 两等

两等会尝试帮你进行一次类型转换再做相等操作,除非判断是否为 NULL 或 Undefined,一律使用三等。

5. JS的事件循环(Event Loop)

提到事件循环,首先要知道JS是如何执行的:

  1. 从前到后,一行一行执行
  2. 如果某一行执行报错,会终止下面代码的执行
  3. 先把同步代码执行完,再执行异步代码

Event Loop的执行过程:

  1. 同步代码,一行一行放在Call Stack来执行,
  2. 遇到异步,会先 “记录” 下,等待时机(定时器,网络请求等),
  3. 时机到了,就移动到 Callback Queue,
  4. 如果Call Stack为空(即同步代码执行完),Event Loop开始工作,
  5. 轮询查找Callback Queue,如果有则移动到Call Stack中执行,
  6. 然后继续轮询查找(像永动机一样)

总结:只有一个线程来执行JS函数,而微任务的监听及触发交给JS引擎的其他线程执行,宏任务的监听和触发交给浏览器的线程来执行,中间插入渲染引擎进行DOM渲染,这样不断轮询就构成了Event Loop

6. 数组常用方法有哪些,并说出他们的用法

删除:

  1. pop(): 从尾部弹出、
  2. shift(): 从头部弹出、

添加:

  1. unshift(): 从头部添加、
  2. push(): 从尾部添加,push方法返回数组的长度。

splice:

  1. splice(起点, 长度):删除
  2. shift(起点, 长度, 添加/替换元素): 插入/替换

7. 面向对象

继承的几种方法
1、 借助构造函数实现继承(原理是在子类中改变父级this的指向,缺点是只能部分继承,不能继承父类原型对象上的方法);

function Parent_1 () {
      this.name = 'Parent_1'
}

Parent_1.prototype.sayHi = function () {}

function Child_1 () {
      Parent_1.call(this) // Parent_1.apply(this)
}

2、 借助原型链继承(缺点是原型链上的对象是共用的,改变某个对象的属性,原型链上的其他对象属性也会改变);

function Parent_2 () {
  this.name = 'Parent2'
  this.play = [1, 2, 3]
}

function Child_2 () {
  this.type = 'child2'
}

Child_2.prototype = new Parent_2()

var s1 = new Child_2();
var s2 = new Child_2();

console.log(s1.play, s2.play);
s1.play.push(4);

3、 组合方式(缺点是父级的构造函数执行了两次并把父类的constructor也继承了);

function Parent_3 () {
  this.name = 'Parent3'
  this.play = [1, 2, 3]
}

function Child_3 () {
  Parent_3.call(this)
  this.type = 'child3'
}
Child_3.prototype = new Parent_3()

var s3 = new Child_3();
var s4 = new Child_3();
console.log(s3.play, s4.play);

4、组合继承优化1(缺点是把父类的constructor也继承了);

function Parent_4 () {
  this.name = 'Parent4'
  this.play = [1, 2, 3]
}

function Child_4 () {
  this.type = 'child4'
}
Child_4.prototype = Parent_4.prototype

var s5 = new Child_4();
var s6 = new Child_4();
console.log(s5, s6);
console.log(s5 instanseof child_4, s5 instanceof Parent_4)

5、组合继承优化2(原理是通过Object.create方法创建一个中间对象,参数是该对象的原型对象,然后把子类的构造函数赋值为该子类)。

function Parent_5 () {
  this.name = 'Parent5'
  this.play = [1, 2, 3]
}

function Child_5 () {
  Parent_5.call(this)
  this.type = 'child5'
}
Child_5.prototype = Object.create(Parent_5.prototype)

6、通过ES6的class来创建,使用extends关键词来继承

class Person {
  constructor(name) {
    this.name = name
  }

  sayHi () {
    return `i'm ${this.name}`
  }
}

class Student extends Person {
  constructor(name, age, sex) {
    super(name) // 通过super关键词调用父类的属性
    this.age = age
    this.sex = sex
  }

  ... // 下面写自己的方法
}

8. 判断数组的方法

  1. 通过 Array.isArray() 来判断
Array.isArray(‘变量’)
// 返回的是布尔值,true是数组,否则不是
  1. 通过 instanceof 来判断
var arr = [1,2,3,1];
alert(arr instanceof Array); // true
  1. 通过 Object.prototype.toString.call() 来判断
function isArrayFn (o) {
      return Object.prototype.toString.call(o) === '[object Array]';
}
var arr = [1,2,3,1];
alert(isArrayFn(arr));// true 
  1. 通过 constructor 来判断
var arr = [1,2,3,1];
alert(arr.constructor === Array); // true

9. 使用 ES5 实现数组的 Map 方法

Array.prototype.myMap = function (fn, ctx) {
  let oArray = Array.prototype.slice.call(this)
  let newArr = []
  let length = oArray.length
  for (let i = 0; i < length; i++) {
    if (!oArray.hasOwnProperty(i)) {
      newArr.length++
    } else {
      newArr.push(fn.call(ctx, oArray[i], i, this))
    }
  }

   return newArr
}

10. 箭头函数的特点

1、箭头函数的 this 为父作用域的 this ,并不是调用的。
箭头函数的this永远指向其父作用域,任何方法都改变不了,包括call,apply,bind。 普通函数的this指向调用它的那个对象。

let person = {
    name:'jike',
    init:function(){
        //为body添加一个点击事件,看看这个点击后的this属性有什么不同
        document.body.onclick = ()=>{
            alert(this.name);//?? this在浏览器默认是调用时的对象,可变的?                  
        }
    }
}
person.init();

上例中的 init 是 function,以 person.init() 调用,其内部的onclick是箭头函数,所以 this 指向父作用域的变量,也就是 person 里的name

let person = {
    name:'jike',
    init: () => {
        //为body添加一个点击事件,看看这个点击后的this属性有什么不同
        document.body.onclick = ()=>{
            alert(this.name);//?? this在浏览器默认是调用时的对象,可变的?                  
        }
    }
}
person.init();

上例的 init 函数是以箭头函数声明的,其内部的 this 为全局windows对象,onclickthis,也就是init函数的this,也是window,得到的this.name就为undefined

2、箭头函数不能作为构造函数

function Person (name) {
  this.name = name
}

const Person = (name) => {
  this.name = name
}

由于 this 必须是对象实例,而箭头函数是没有实例的,此处的 this 又指向别处,不能产生person实例,自相矛盾。

3、箭头函数没有 arguments、caller、callee 箭头函数本身没有 arguments,如果箭头函数在一个 function 里面,它会将外部的 arguments 拿过来使用,箭头函数中想要接受不定参数,可以使用 rest参数...(扩展运算符)来解决

let B = (b)=>{
  console.log(arguments);
}
B(2,92,32,32);   // Uncaught ReferenceError: arguments is not defined

let C = (...c) => {
  console.log(c);
}
C(3,82,32,11323);  // [3, 82, 32, 11323]

4、箭头函数通过call或apply调用,无法改变this指向,只会传入参数

let obj2 = {
  a: 10,
  b: function(n) {
    let f = (n) => n + this.a;
    return f(n);
  },
  c: function(n) {
    let f = (n) => n + this.a;
    let m = {
      a: 20
    };
    return f.call(m,n);
  }
};
console.log(obj2.b(1));  // 11
console.log(obj2.c(1)); // 11

5、箭头函数没有原型属性

var a = () => {
  return 1;
}

function b () {
  return 2;
}

console.log(a.prototype);  // undefined
console.log(b.prototype);   // {constructor: ƒ}

6、箭头函数在 ES6 class 中声明的方法为实例方法,不是原型方法

//deom1
class Super{
    sayName(){
        //do some thing here
    }
}
//通过Super.prototype可以访问到sayName方法,这种形式定义的方法,都是定义在prototype上
var a = new Super()
var b = new Super()
a.sayName === b.sayName //true
//所有实例化之后的对象共享prototypy上的sayName方法

//demo2
class Super{
    sayName =()=>{
        //do some thing here
    }
}
//通过Super.prototype访问不到sayName方法,该方法没有定义在prototype上
var a = new Super()
var b = new Super()
a.sayName === b.sayName //false
//实例化之后的对象各自拥有自己的sayName方法,比demo1需要更多的内存空间