初级前端面试知识体系--JS基础

101 阅读6分钟

1. 作用域/作用域链

JS中的作用域和作用域链

JS的解析与执行过程

  • 作用域(Scope)

作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。内层作用域可以访问外层作用域的变量,反之则不行。全局作用域,函数作用域,块级作用域(let/const 声明)

  • 作用域链

当前作用域找不到的变量,逐渐向父级作用域(创建函数的作用域)寻找,形成的关系链

  • 作用域与执行上下文

JavaScript 的执行分为:解释和执行两个阶段,这两个阶段所做的事并不一样。执行上下文在运行时确定,随时可能改变;作用域在定义时就确定,并且不会改变。同一个作用域下,不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值。

2. 事件委托

  • 事件委托

事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。

  • 事件流

一个完整的事件流是从window开始,最后回到window的一个过程

事件流被分为3个阶段:1-5捕获阶段,5-6:目标阶段,6-10:冒泡阶段

3. this

this、apply、call、bind

彻底理解js中this的指向

this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的

情况1:非严格模式下,如果一个函数中有this,但是它没有被上一级的对象所调用,那么this指向的就是window

情况2:如果一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象。

情况3:如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象

  • 改变this

(1)new构造函数

如果返回值是一个对象(非null),那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例

// 手写new
var a = new _new(myFunction,"Li","Cherry");
funtion _new(myFunction,...args){
    var obj = {};
    obj.__proto__ = myFunction.prototype;
    var result = myFunction.apply(obj, args);
    return typeof result === 'obj'? result : obj;
}

(2)箭头函数

箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined

(3)在函数内部使用_this = this
(4)使用apply,call,bind

fun.apply(thisArg, [argsArray])

fun.call(thisArg[, arg1[, arg2[, ...]]])

thisArg:在 fun 函数运行时指定的 this 值。需要注意的是,指定的 this 值并不一定是该函数执行时真正的 this 值,如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。

argsArray:一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 fun 函数。如果该参数的值为null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。

bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。

4. 闭包

闭包 - JavaScript | MDN

可以从内部函数访问外部函数作用域

5. 原型与原型链,继承

JavaScript中原型对象的彻底理解

继承与原型链

Person函数拥有prototype属性,Person.prototype.constructor属性(指向函数本身),var p1 = new Person(),p1.proto = Person.prototype,p1拥有Person函数实例以及可以访问Person函数的原型prototype属性。

p1.name = 'p1',在p1修改属性实际上是在p1的自身对象中添加属性name,会遮蔽原型上的同名属性name。

  • hasOwnProperty() 方法

在原型中的属性和new返回的实例对象不存在的属性都会返回fasle。

  • in 操作符

new返回的实例对象和原型中有一个地方存在这个属性,就返回true。

var o = {a: 1};

// o 这个对象继承了 Object.prototype 上面的所有属性
// o 自身没有名为 hasOwnProperty 的属性
// hasOwnProperty 是 Object.prototype 的属性
// 因此 o 继承了 Object.prototype 的 hasOwnProperty
// Object.prototype 的原型为 null
// 原型链如下:
// o ---> Object.prototype ---> null

var a = ["yo", "whadup", "?"];

// 数组都继承于 Array.prototype 
// (Array.prototype 中包含 indexOf, forEach 等方法)
// 原型链如下:
// a ---> Array.prototype ---> Object.prototype ---> null

function f(){
  return 2;
}

// 函数都继承于 Function.prototype
// (Function.prototype 中包含 call, bind等方法)
// 原型链如下:
// f ---> Function.prototype ---> Object.prototype ---> null

继承方式

new初始化

function foo(){}
foo.prototype = {
  foo_prop: "foo val"
};
function bar(){}
var proto = new foo;
proto.bar_prop = "bar val";
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop);
console.log(inst.bar_prop);

Object.create()

function foo(){}
foo.prototype = {
  foo_prop: "foo val"
};
function bar(){}
var proto = Object.create(
  foo.prototype
);
proto.bar_prop = "bar val";
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop);
console.log(inst.bar_prop);

Object.setPropertyOf()

function foo(){}
foo.prototype = {
  foo_prop: "foo val"
};
function bar(){}
var proto = {
  bar_prop: "bar val"
};
Object.setPrototypeOf(
  proto, foo.prototype
);
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop);
console.log(inst.bar_prop);

proto

function foo(){}
foo.prototype = {
  foo_prop: "foo val"
};
function bar(){}
var proto = {
  bar_prop: "bar val",
  __proto__: foo.prototype
};
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop);
console.log(inst.bar_prop);

6. 浅拷贝和深拷贝

一文读懂 javascript 深拷贝与浅拷贝

如何写出一个惊艳面试官的深拷贝?

  • 浅拷贝

浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

function shallowClone(source) {
    var target = {};
    for(var i in source) {
        if (source.hasOwnProperty(i)) {
            target[i] = source[i];
        }
    }
    return target;
}
  • 深拷贝

深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。

function deepClone(obj, hash = new WeakMap()) {
    if (obj === null) return obj; 
    // 如果是null或者undefined我就不进行拷贝操作
    if (obj instanceof Date) return new Date(obj);
    if (obj instanceof RegExp) return new RegExp(obj);
    // 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
    if (typeof obj !== "object") return obj;
    // 是对象的话就要进行深拷贝
    if (hash.get(obj)) return hash.get(obj);
    let cloneObj = new obj.constructor();
    // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
    hash.set(obj, cloneObj);
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        // 实现一个递归拷贝
        cloneObj[key] = deepClone(obj[key], hash);
      }
    }
    return cloneObj;
  }

7. 防抖节流

  • 防抖(debounce)

指触发事件后在规定时间内回调函数只能执行一次,如果在规定时间内又触发了该事件,则会重新开始算规定时间。

const debounce = (fun, timeout = 1000) => {
        let flag = null
        return (...args) => {
            if (flag) {
                clearTimeout(flag)
            }
            flag = setTimeout(function (){
                flag = null
                fun.apply(this, args)
            }, timeout);
        }
    }
  • 节流(throttle)

当持续触发事件时,在规定时间段内只能调用一次回调函数。如果在规定时间内又触发了该事件,则什么也不做,也不会重置定时器.

const throttle = (fun, timeout = 1000) => {
        let flag = false
        return (...args) => {
            if (!flag) {
                flag = true
                setTimeout(function (){
                    flag = false
                    fun.apply(this, args)
                }, timeout);
            }
        }       
    }