likes
comments
collection
share

关于前端面试那些题

作者站长头像
站长
· 阅读数 35

JavaScript 部分

this 以及 this的指向

this 是执行上下文中的一个属性,它指向最后一次调用这个方法的对象。

  • 第一种是函数调用模式,当一个函数不是一个对象的属性时,直接作为函数来调用时,this 指向全局对象。

    • 匿名函数自调和回调函数里的 this 指向 window。 严格模式(usestrict)下,this->undefined因为这类函数调用时,前边即没有.,也没有new!

      "use strict";
      (function () {
          console.log(this) // 'use strict' -> undefined 非'use strict' this -> window
      })(); // 匿名函数自调
      var arr = [1]
      arr.forEach(function () { // 回调函数 
          console.log(this) // 'use strict' -> undefined 非'use strict' this -> window
      });
      
    • 箭头函数里的this,指向当前函数之外最近的作用域中的this。

  • 第二种是方法调用模式,如果一个函数作为一个对象的方法来调用时,this 指向这个对象。

    const obj = {
      name: 'lumi',
      fn: function() { console.log(this.name) }
    }
    obj.fn()
    // this 指向 obj
    
  • 第三种是构造器调用模式,如果一个函数用 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象。

    function obj(name) {
      this.name = name;
      this.fn = function() {
        console.log(this.name)
      }
    }
    
    const person = new obj('lumi')
    person.fn(); // lumi
    // fn的this 指向 person对象
    
  • 第四种是 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。其中 apply 方法接收两个参数:一个是 this 绑定的对象,一个是参数数组。call 方法接收的参数,第一个是 this 绑定的对象,后面的其余参数是传入函数执行的参数。也就是说,在使用 call() 方法时,传递给函数的参数必须逐个列举出来。bind 方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this 指向除了使用 new 时会被改变,其他情况下都不会改变。

这四种方式,使用构造器调用模式的优先级最高,然后是 apply、call 和 bind 调用模式,然后是方法调用模式,然后是函数调用模式。

call、bind、apply

作用:改变函数执行时的指向,改变this的指向

// 例子:
function fn(..args) {
  console.log(this, 'this')
  console.log(args, 'args')
}
const obj = {
  name: 'lumi'
}
call()、apply()

aplly()接收两个参数,第一个参数为 this 的指向,第二个参数为参数列表。

call() 第一个参数为this指向,后面传入的是一个参数列表。

只是暂时改变this指向。当第一个参数为nullundefined的时候,默认指向window。

fn.call(obj,1,2)
fn.apply(obj, [1,2])
// 结果
// this 指向 obj { name: 'lumi'}
// args: [1,2]
fn(1,2)
// 结果
// this 指向 window
// args:[1,2]
bind()

bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入)

改变this指向后不会立即执行,而是返回一个永久改变this指向的函数(生成一个新的函数)

const bindFn = fn.bind(obj)
bindFn(1,2)
// 结果:this 指向 obj

ES6 常见

箭头函数

特点:

  • 不能作为构造函数,无法使用 new
  • 函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象
  • 不能使用 argument 对象
  • 单命令行时可以不写 return; 返回对象时,需要用括号包裹。
var sum = (num1, num2) => { return num1 + num2; }
// 等同于
var sum = (num1, num2) => num1 + num2;

let getTempItem = id => ({ id: id, name: "Temp" });
// 等同于
let getTempItem = id => {  return {id: id, name: "Temp"} };
数据类型
  • 基本类型(栈存储):String、Number、Boolean、Undefind、Null、Symbol
  • 引用类型(堆存储):Object、Array、Function
数据类型判断
  • typeof:其中数组、对象、null都会被判断为object,其他判断都正确。
  • instanceof :只能正确判断引用类型数据类型
  • constructor:判断数据类型;对象实例通过其访问它的构造函数
  • Object.prototype.toString.call(): 使用 Object 对象的原型方法 toString 来判断数据类型
判断数组
  • Object.prototype.toString.call()
  • 通过原型链:obj.__proto__ === Array.prototype
  • Array.isArray()
  • instanceof
  • Array.prototype.isPrototypeOf
rest 运算符(剩余运算符)

用于解构数组和对象,扩展运算符(...)被用在函数形参上时,它可以把一个分离的参数序列整合成一个数组

经常用于获取函数的多余参数,或者像上面这样处理函数参数个数不确定的情况。

总结

扩展运算符和rest运算符是逆运算

  • 扩展运算符:数组=>分割序列
  • rest运算符:分割序列=>数组
Set
  • 类似于数组,但成员值唯一。
  • 会比较类型, 5 不等于 ’5‘
  • null、undefined、NaN不会被过滤
  • 本身是一个构造函数,使用new实例化

使用场景:数组/字符串去重

属性方法:add、delete、has、clear、size

遍历:forEach遍历

// 去重
[...new Set([2, 3, 5, 4, 5, 2, 2])] // [2 3 5 4]
[...new Set('ababbc')].join('') // "abc"
// 属性方法
let s = new Set()
s.add(1).add(2).add(2) //{1,2}
s.delete(2) //{1}
s.has(1) //true
s.clear() //{}
s.size //0 注意size是属性,不是方法的调用
Map

类似于对象,也是键值对的集合,但属性不限于字符串,提供了“值—值”的对应,是一种更完善的 Hash 结构实现。只有引用地址一样,map结构才能视为同一个键。

与Object的区别

  • Object键只能是字符串/Symbol,但Map键可以是任意值
  • Map键值是有序的,对象则不是
  • Map 的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算(通过keys数组个数)。
  • Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。

属性方法:set、get、delete、has、clear、size、keys、values

const m = new Map();
const o = {p: 'Hello World'};

m.set(o, 'content')
m.get(o) // "content"

m.has(o) // true
m.delete(o) // true
m.has(o) // false
m.size //0

遍历:通过 forEach 和 for…of,获取key与对应的value

//注意:value在前
map.forEach(function (value, key) {
    console.log(key, value);
});

for (let o of map) {
    console.log(o) //[key,value]
}
Reflect

是一个内置对象,提供了一系列用于操作对象的方法。Reflect 将 Object 对象的一些明显属于语言内部的方法(in、delete),放到Reflect对象上(Reflect.get、Reflect.set)。

WeakMap

EventLoop 事件循环

众所周知,JS是 单线程,有且只有一个调用栈,先执行同步任务,再执行异步任务。

宏任务
  • 回调函数
  • script内容
  • setTimeout 和 setInterval、setImmediate
  • requestAnimationFrame
  • i/o操作
  • ui rendering 渲染
微任务

一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前。

  • Promise.then
  • MutaionObserver
  • Object.observe(已废弃;Proxy 对象替代)
  • process.nextTick(Node.js)
async 与 await

async 是异步的意思,await则可以理解为 async wait。所以可以理解async就是用来声明一个异步方法,而 await是用来等待异步方法执行。

  • async :用来声明一个异步方法,返回一个promise 对象。
  • await:用来等待异步方法执行。await命令后面是一个 Promise对象,返回该对象的结果。如果不是 Promise对象,就直接返回对应的值。不管await后面跟着的是什么,await都会阻塞后面的代码
执行机制
  • 执行一个宏任务,如果遇到微任务就将它放到微任务的事件队列中

  • 当前宏任务执行完成后,会查看微任务的事件队列,然后将里面的所有微任务依次执行完

    关于前端面试那些题

举例说明
例子🌰1
console.log('script start' ) // 宏任务 task1

setTimeout(()=>{
    console.log('setTimeout')
}, 0) // 宏任务 task2

new Promise((resolve, reject)=>{
    console.log('new Promise') // 宏任务task1
    resolve()
}).then(()=>{
    console.log('promise then') // 微任务 micortask1
})

console.log('script end') // 宏任务task1

分析:

  1. 遇到console ,直接打印 script start
  2. 遇到定时器setTimeout,属于新的宏任务,留着
  3. 遇到promise,直接打印 new Promise
  4. 接着是promise.then,属于微任务,留着
  5. 遇到 console,直接打印 script end
  6. 第一轮宏任务(即主线程)执行结束,查看微任务列表,发现promise.then 回调,执行打印 promise end
  7. 当所有微任务执行结束后,执行下一个宏任务setTimeout,打印 setTimeout
例子🌰2
async function async1() {
    console.log('async1 start') //task1.2.1
    await async2() // task1.2.2
    console.log('async1 end') // microtask1
} 
async function async2() {
    console.log('async2')
}

console.log('script start')  // task1.1

setTimeout(function () {
    console.log('settimeout')
}) // task2

async1() //task1.2

new Promise(function (resolve) {
    console.log('promise1') // task1.3
    resolve() // task 1.4
}).then(function () {
    console.log('promise2') // microtask2
})
console.log('script end') // task1.5

分析:

  1. 遇到 console,执行打印 script start
  2. 遇到定时器,属于新的宏任务,留着
  3. 遇到 async1,执行,打印 async1 start,遇到 async2 , 执行打印 async2 , await会阻塞后面的代码(即加入微任务列表),跳出去执行同步任务
  4. 遇到 promise,执行打印 promise1
  5. 遇到promise.then,属于微任务,留着
  6. 遇到console,执行打印 script end
  7. 第一轮宏任务执行结束,检查微任务列表,先执行await后的代码,即打印 async1 end , 接着执行then,打印promise 2
  8. 微任务执行结束,执行下一个宏任务定时器,打印 setTimeout

类似Map,但键名只能是对象,键名所指向的对象,不计入垃圾回收机制。

EventFlow 事件流

存在三个阶段:事件捕获、目标阶段、事件冒泡。Dom标准事件流的触发的先后顺序为:先捕获再冒泡。即当触发dom事件时,会先进行事件捕获,捕获到事件源之后通过事件传播进行事件冒泡。

事件冒泡

当一个元素接收到事件的时候,会把他接收到的事件传给自己的父级,一直到 window/dom。(该事件仅指click、dbclick等事件,而非绑定的函数方法)

例如:A元素包含B元素,点击B元素的同时就会冒泡触发A元素的点击事件。

事件捕获

当鼠标点击或者触发dom事件时(被触发dom事件的这个元素被叫作事件源),浏览器会从根节点 =>事件源(由外到内)进行事件传播。

与冒泡不同是,事件的传播方向,捕获是由外到内的,冒泡是由内到外的。

事件委托

又称事件代理。即利用事件冒泡,将子元素事件绑定到父元素上,如果子元素阻止了冒泡,则委托无法实现。

优点:

  • 替代循环绑定事件的操作,减少内存消耗,提高性能。
  • 简化dom节点更新时相应事件的更新。

缺点:

  • 事件委托基于冒泡,对于不冒泡的事件不支持。
  • 层级过多,冒泡过程中,可能会被某层阻止掉。
  • 理论上委托会导致浏览器频繁调用处理函数,虽然很可能不需要处理。所以建议就近委托,比如在table上代理td,而不是在document上代理td

原型

了解原型需要先了解构造函数。由于Js 没有类的概念(es6有class关键词,使用方法后续拓展),所以使用 构造函数来实现继承机制。

构造函数

Js通过构造函数生成实例。但产生了一个问题:无法共享公共属性,构造函数中通过 this赋值的属性方法是每个实例独有的。

所以原型对象就是用来存储共享属性和方法的。

// 🌰使用构造函数创造一个实例
// 构造函数
function Student(name, age) {
  this.name = name
  this.age = age
}
// 实例
const student1 = new Student('lumi', 18)
原型对象

每个函数在生成的时候,都会创建一个属性 prototype , 这个属性指向一个对象,即 原型对象

原型对象中有一个属性 constructor, 指向该函数,这样两者就联系起来了。

关于前端面试那些题

原型链

原型链就是实例对象和原型对象之间的联系。每个构造函数创建的每一个实例,都有一个属性 __proto__ ,这个属性指向构造函数的原型对象。通过该属性可以一步一步向上查找形成一个链式结构,称为 原型链

关于前端面试那些题

如果通过实例对象的 __proto__ 属性赋值 ,会改变其构造函数的原型对象,从而被所有该构造函数创建的实例所共享。

// 🌰:
function Student(name, age) {
  this.name = name;
  this.age = age;
}

// 往原型对象添加共享的方法属性
Student.prototype.school = '北京大学'
Student.prototype.goToClass = function() { console.log(`${this.name}上课了`) }

// 创建实例对象
const s1 = new Student('lumi', 13);
const s2 = new Student('lucy', 15);
s1.goToClass(); // lumi上课了
s2.goToClass(); // lucy上课了
s2.hasOwnProperty('goToClass') // false,说明不是实例独有的,是共享的
s1.__proto__.leave = function() {
  console.log(`${this.name}放学了`)
} // 往原型对象里添加方法
s2.leave() // lucy放学了 (说明s1添加了方法,s2能共享这个方法)

注意⚠️

生产环境中,不建议使用 __proto__,避免环境产生依赖。可以使用 Object.getPrototypeOf 方法来获取实例对象原型,然后再为原型添加方法和属性。

补充:原型链的尽头是null,详情见:

关于前端面试那些题

Proxy

Proxy代理:在目标对象之前假设一层拦截,可以对外界的访问进行改写。ES6提供原生的Proxy构造函数。

var proxy = new Proxy(target, handler)
// target: 拦截的对象
// handler: 定制拦截行为,可以拦截行为有几十种,仅介绍常见的几种。
常见拦截行为
  • get捕获器:用于拦截对象属性读取
  • set捕获器:用于拦截对象属性赋值
  • has捕获器:用于拦截判断target对象中是否含有某个属性
  • deleteProperty捕获器:用于拦截删除target对象属性的操作
// 🌰:
let person = {
  name: 'lumi'
}
var proxy = new Proxy(person, {
  get: function(target, key) {
    if (key in target) {
      return target[key];
    }else{
      return 'not found'
    }
  },
  set: function(target, key, value) {
    target[key] = value
    return true
  },
  has: (target, key) => {
    return key in target
  },
  deleteProperty: (target, key) => {
    if (key === 'name') {
      throw new Error('name不可被删除')
    } else {
      return delete target[key]
    }
  }
})
console.log(proxy.name) // lumi
console.log(proxy.age) // not found
proxy.age = 18
console.log(proxy.age) // 18
console.log('age' in proxy) // true
console.log('sex' in proxy) // false
 // delete proxy.name // name 不可被删除
delete proxy.age
console.log('age' in proxy) // false

模块化

将js分割成不同职责的js,⽤于解决全局变量污染、变量冲突、代码冗余等问题,提高代码可维护性、可拓展性、复用性。

自执行函数实现模块化

通过函数作用域解决了命名冲突、污染全局作用域的问题。

// 自执行函数实现模块化
(function () {
    var a = 1;
    console.log(a); // 1
})();

(function () {
    var a = 2;
    console.log(a); // 2
})();
AMD

异步模块定义,采用异步方法加载模块,模块加载不影响后面语句运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到所有依赖项都加载完成之后,这个回调函数才会运行。【前置依赖】

CMD

公共模块定义,可以使用 require 同步加载依赖,也可以使用 require.async 来异步加载依赖。【后置依赖】

CommonJs

Node的⼀种模块同步加载规范,⼀个⽂件即是⼀个模块。用在Node端(服务端),加载速度很快,所以可以使用同步方法。

  • 使⽤时require引⼊。
  • module.exports 是 CommonJS 的⼀个 exports 变量,提供对外的接⼝。
  • 输出为一个值的拷贝。
ESModule

是ES6提供的官方js模块化方案。目前浏览器还不能全面支持 ESModule 的语法,需要用 babel 进行解析。

通过export命令显式指定输出的代码。属于编译时加载,⽐Commonjs效率⾼。可以按需加载指定⽅法。效率⾼。

  • export defalut 与 export 是 ES6 Module 中对外暴露数据的。 export defalut 是向外部默认暴露数据,使⽤时 import 引⼊时需要为该模块指定⼀个任意名称,import 后不可以使⽤{};
  • export 是单个暴露数据或⽅法,利⽤ import{}来引⼊,并且{}内名称与 export ⼀⼀对应,{}也可以使⽤ as 为某个数据或⽅法指定新的名称;
  • 输出为一个值的引用。
转载自:https://juejin.cn/post/7352845644737183771
评论
请登录