DAY18刷面试题

国庆出去玩了四天,玩疯了,国庆还有几天滚回来搞学习!!!

ES6 的模块化和 CommonJS 模块化

CommonJS:(要在node里使用)
导入 var {xxx} = requeir(’./xxx/js’)
使用 var test = xxx.test;
var count = xxx.count;
或者是这样导入 var { test , count } = requeir(’./xxx.js’)
导出 moudle.export = {
test : test,
count : count
}

ES6:
html导入:<script src="./xxx.js" type="module" ></script>这样就会当成一个模块
js文件导入import { test , count } from “./xxx.js”(.js可以简写)
导出:export = {
test , count
}

import和require的区别

1)遵循规范
require 是 AMD规范引入方式
import是es6的一个语法标准,如果要兼容浏览器的话必须转化成es5的语法

2)调用时间
require是运行时调用,所以require理论上可以运用在代码的任何地方
import是编译时调用,所以必须放在文件开头

3)本质
require是赋值过程,其实require的结果就是对象、数字、字符串、函数等,再把require的结果赋值给某个变量
import是解构过程,但是目前所有的引擎都还没有实现import,在node中使用babel支持ES6,也仅仅是将ES6转码为ES5再执行,import语法会被转码为require

4)require / exports :
遵循 CommonJS/AMD,只能在运行时确定模块的依赖关系及输入/输出的变量,无法进行静态优化。
用法只有以下三种简单的写法:

const fs = require('fs')
exports.fs = fs
module.exports = fs

5)import / export:
遵循 ES6 规范,支持编译时静态分析,便于JS引入宏和类型检验。动态绑定。
写法就比较多种多样:

import fs from 'fs'
import {default as fs} from 'fs'
import * as fs from 'fs'
import {readFile} from 'fs'
import {readFile as read} from 'fs'
import fs, {readFile} from 'fs'

export default fs
export const fs
export function readFile
export {readFile, read}
export * from 'fs'

讲一下 ES6 继承 extend

一个类可以去继承其他类里面的东西,这里定义一个叫Person的类,然后在constructor里面添加两个参数:name和birthday;
下面再添加一个自定义的方法intro,这个方法就是简单地返回this.name和this.birthday;

class Person{
  constructor(name,birthday){
    this.name = name;
    this.birthday= birthday;
  }
  intro(){
    return '${this.name},${this.birthday}';
  }
}

然后再定一个Chef类,使用extends去继承Person这个类,如果这个类里面有constructor方法,就要在constructor方法里面使用super,它可以去调用父类里面的东西

class Chef extends Person{
  constructor(name,birthday){
    super(name,birthday);
  }
}
 
let zhangsan = new Chef('zhangsan','1988-04-01');
console.log(zhangsan.intro()); //zhangsan,1988-04-01

因为Chef这个类继承了Person类,所以在Person类里面定义的方法可以直接使用

super
super 这个关键字,既可以当作函数使用,也可以当作对象使用。当作函数使用时super 代表父类的构造函数,并在子类中执行 Parent.apply(this),从而将父类实例对象的属性和方法,添加到子类的 this上面。
1)子类必须在 constructor 方法中调用 super 方法,如果子类没有定义 constructor 方法,constructor 方法以及其内部的 super 方法会被默认添加。
2)在子类的 constructor 方法中,只有调用 super 之后,才可以使用 this 关键字,否则会报错。
3)super() 只能用在子类的constructor方法之中,用在其他地方就会报错。
4)super 作为对象时,在子类中指向父类的原型对象。即 super = Parent.prototype。

讲一下 ES6 之前的继承方式(6 种)

原型链继承
构造函数、原型和实例之间的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个原型对象的指针。

继承的本质就是复制,即重写原型对象,代之以一个新类型的实例。

function SuperType() {
  this.property = true
}
SuperType.prototype.getSuperValue = function() {
  return this.property
}
function SubType() {
  this.subProperty = false
}
SubType.prototype.getSubValue = function() {
  return this.subProperty
}
// 关键,创建SuperType的实例,让SubType.prototype指向这个实例
SubType.prototype = new SuperType()
console.dir(SuperType)
let inst1 = new SuperType()
let inst2 = new SubType()
console.log(inst2.getSuperValue()) // true

优点:

  • 父类方法可以复用

缺点:

  • 父类的引用属性会被所有子类实例共享,多个实例对引用类型的操作会被篡改(代码如下);
  • 子类构建实例时不能向父类传递参数
function SuperType(){
  this.colors = ["red", "blue", "green"];
}
function SubType(){}

SubType.prototype = new SuperType();

// 多个实例共享父类引用(上面的 new SuperType())
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"

var instance2 = new SubType(); 
alert(instance2.colors); //"red,blue,green,black"

构造函数继承
使用父类的构造函数来增强子类实例,等同于复制父类的实例给子类(不使用原型)

function SuperType() {
  this.color = ['red', 'green']
}

// 构造函数继承
// 使得每个实例都会复制得到自己独有的一份属性
function SubType() {
  // 将父对象的构造函数绑定在子对象上
  SuperType.call(this)
}

let inst1 = new SubType()

console.log(inst1)

// SubType {color: Array(2)}

核心代码是SuperType.call(this),创建子类实例时调用SuperType构造函数,于是SubType的每个实例都会将SuperType中的属性复制一份,解决了原型链继承中多实例相互影响的问题

优点:

  • 和原型链继承完全反过来
  • 父类的引用属性不会被共享
  • 子类构建实例时可以向父类传递参数

缺点:

  • 只能继承父类的实例属性和方法,不能继承原型属性/方法
  • 无法实现复用,每个子类都有父类实例函数的副本,影响性能
...
// 父类原型链上的方法
SuperType.prototype.getColor = function () {
  return this.color
}
...
console.log(inst1.getColor()) // TypeError: inst1.getColor is not a function

组合继承(上面两种结合起来)
组合上述两种方法就是组合继承。用原型链实现对原型属性和方法的继承,用借用构造函数技术来实现实例属性的继承。

function SuperType(name) {
  this.name = name
  this.colors = ['red', 'blue', 'green']
}

SuperType.prototype.getName = function () {
  return this.name
}

function SubType(name, age) {
  // 1、构造函数来复制父类的属性给SubType实例
  // *** 第二次调用SuperType()
  SuperType.call(this, name)
  this.age = age
}

SubType.prototype.getAge = function () {
  return this.age
}

// 2、原型继承
// *** 第一次调用SuperType()
SubType.prototype = new SuperType()
// 手动挂上构造器,指向自己的构造函数 SubType
SubType.prototype.constructor = SubType
SubType.prototype.getAge = function () {
  return this.age
}

let inst1 = new SubType('Asuna', 20)

console.log('inst1', inst1)
console.log(inst1.getName(), inst1.getAge())
console.log(inst1 instanceof SubType, inst1 instanceof SuperType)

// inst1 SubType {name: "Asuna", colors: Array(3), age: 20}
// Asuna 20
// true true

优点:

  • 父类的方法可以被复用
  • 父类的引用属性不会被共享
  • 子类构建实例时可以向父类传递参数

缺点(对照注释):

  • 第一次调用SuperType():给SubType.prototype写入两个属性name,color。
  • 第二次调用SuperType():给instance1写入两个属性name,color。
  • 实例对象inst1上的两个属性就屏蔽了其原型对象SubType.prototype的两个同名属性。所以,组合模式的缺点就是在使用子类创建实例对象时,其原型中会存在两份相同的父类实例的属性/方法。这种被覆盖的情况造成了性能上的浪费。

原型式继承
(哎,就是浅拷贝)
我们举个 ,比如,现在有一个对象,叫做"中国人",还有一个对象,叫做"医生"。

const Chinese = {
  nation: '中国'
}
const Doctor = {
  career: '医生'
}

请问怎样才能让"医生"去继承"中国人",也就是说,我怎样才能生成一个"中国医生"的对象?

这里要注意,这两个对象都是普通对象,不是构造函数,所以无法使用构造函数方法实现"继承"。

可以用object()方法

利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型。

// ES5中存在Object.create()的方法,能覆盖下面这个方法
function object(obj) {
  function F() { }
  F.prototype = obj
  return new F()
}

object()本质上是对传入其中的对象执行了一次浅拷贝,将构造函数F的原型直接指向传入的对象。

let person = {
  name: "Nicholas",
  friends: ["Shelby", "Court", "Van"]
};

let anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

let yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");

alert(person.friends);   //"Shelby,Court,Van,Rob,Barbie"

ECMAScript 5 通过新增 Object.create() 方法规范化了原型式继承。这个方法接收两个参数:一 个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。在传入一个参数的情况下, Object.create()与 object()方法的行为相同。——《JavaScript高级程序设计》

let yetAnotherPerson = object(person)
//  => 
let yetAnotherPerson = Object.create(person)

优点:

  • 父类方法可以复用

缺点:

  • 原型链继承多个实例的引用类型属性指向相同,存在篡改的可能
  • 子类构建实例时不能向父类传递参数

寄生式继承
(能附加一些方法)
使用原型式继承获得一份目标对象的浅拷贝,然后增强了这个浅拷贝的能力。

优缺点其实和原型式继承一样,寄生式继承说白了就是能在拷贝来的对象上加点方法,也就是所谓增强能力。

function object(obj) {
  function F() { }
  F.prototype = obj
  return new F()
}

function createAnother(original) {
  // 通过调用函数创建一个新对象
  let clone = object(original)
  //以某种方式来增强这个对象
  clone.getName = function () {
    console.log('我有了getName方法: ' + this.name)
  }
  return clone
}

let person = {
  name: 'Asuna',
  friends: ['Kirito', 'Yuuki', 'Sinon']
}

let inst1 = createAnother(person)
let inst2 = createAnother(person)

优点:

  • 父类方法可以复用

缺点:

  • 原型链继承多个实例的引用类型属性指向相同,存在篡改的可能
  • 子类构建实例时不能向父类传递参数

寄生组合继承
(最优方案)
组合继承会有两次调用父类的构造函数而造成浪费的缺点,寄生组合继承就可以解决这个问题。

核心在于inheritPrototype(subType, superType),让子类的prototype指向父类原型的拷贝,这样就不会调用父类的构造函数,进而引发内存的浪费问题。

完整代码:

function inheritPrototype(subType, superType) {
  // 修正子类原型对象指针,指向父类原型的一个副本 (用object()也可以) 
  subType.prototype = Object.create(superType.prototype)
  // 增强对象,弥补因重写原型而失去的默认的constructor属性
  subType.prototype.constructor = subType
}

function SuperType(name) {
  this.name = name
  this.colors = ['red', 'blue', 'green']
}

SuperType.prototype.getColors = function () {
  console.log(this.colors)
}

function SubType(name, age) {
  SuperType.call(this, name)
  this.age = age
}

inheritPrototype(SubType, SuperType)

SubType.prototype.getAge = function () {
  console.log(this.age)
}

let inst1 = new SubType("Asuna", 20)
let inst2 = new SubType("Krito", 21)
console.log('inst1', inst1)
console.log('inst2', inst2)
// 构造函数继承和组合继承的缺陷:二次调用 SuperType 的构造函数
subType.prototype = new SuperType()
// 改为 => 
subType.prototype = Object.create(superType.prototype)

实现多继承
希望能继承到多个对象,则可以使用混入的方式。

function MyClass() {
     SuperClass.call(this);
     OtherSuperClass.call(this);
}

// 继承一个类(就是寄生组合继承的套路)
MyClass.prototype = Object.create(SuperClass.prototype);

// 混合其它类,关键是这里的 assign() 方法
Object.assign(MyClass.prototype, OtherSuperClass.prototype);

// 重新指定constructor
MyClass.prototype.constructor = MyClass;

// 在之类上附加方法
MyClass.prototype.myMethod = function() {
  // do a thing
};

Object.assign 会把 OtherSuperClass原型上的函数拷贝到 MyClass原型上,使 MyClass 的所有实例都可用 OtherSuperClass 的方法。Object.assign 是在 ES2015 引入的,且可用 polyfilled。要支持旧浏览器的话,可用使用 jQuery.extend() 或者 _.assign()。 ——[MDN] Object.create()

自己讲一下对 vue 的理解,自己讲用到的 vue 的功能

  • 渐进式 JavaScript 框架(渐进式指的是在使用该产品做开发的时候,不需要完全的去重构之前的代码结构,可以非常方便将该产品集成到自身的项目中)
  • vuejs 是基于MVVM 思想的框架(
    M 是 Model 的简写,负责数据,在前端其表现形式就是一个js对象,一般称之为模型变量(数据)
    V 是 View 的简写,负责数据的展示和收集,在前端的表现形式就是一个 DOM 区域。
    VM 是 ViewModel 的简写,负责业务逻辑,在前端的表现形式就是当前的产品本身的一个实例对象,它以数据驱动作为指导思想,通过双向数据绑定进行数据的操作)
  • vuejs 是以数据驱动为主的框架(无须关心底层的dom实现,只需要提供数据,框架本身会给我们进行dom操作)
  • vuejs 是以组件化进行开发的框架(组件化指的是在开发过程中,把一些可以复用的代码高度的抽象、提取出来,封装成一个一个的代码块,然后在其他的地方进行复用。)

vue2 中数据双向绑定使用到的 Object.definePrototype 如何做的依赖收集?

1.首先observer一个对象,使其中的参数都有get,set方法;
2.watch一个属性的时候,new 一个Watcher实例,并把Dep.target设置为当前的Watcher实例,然后获取一次该属性的值,触发get方法中的依赖收集。

依赖收集在哪一个方法中去完成的?

computed
Vue中computed的依赖收集

说一下 dep 和 watcher 之间的关系

Dep与Watcher之间是多对多的关系
一个data属性对应一个Dep,一个Dep对应n个Watcher(属性多次在模板中被使用时n>1:{{a}}/v-text=‘a’)
一个表达式对应一个Watcher, 一个Watcher对应n个Dep(多层表达式时n>1:a.b.c)

双向绑定的功能是在哪一个生命周期中实现的?

init
组件实例化时,调用init(),初始化组件的生命周期相关属性,事件,props,data等等。vue双向绑定也在这个期间完成的

对 axios 的分析,比如添加中间件的能力

(相响应劫持这类的)
axios封装和请求响应劫持

axios封装与请求响应劫持
在src目录下,创建api目录,用来封装axios
api/axios.js
axios.js – 源代码

import axios from 'axios'
import config from '../config/index.js'
// 判断是开发环境,还是运行环境
const baseUrl = process.env.NODE_ENV === 'development' ? config.baseUrl.dev : config.baseUrl.pro

class HttpRequect {
	// 初始化构造方法
	constructor(baseUrl) {
		this.baseUrl = baseUrl;
		this.queue = {}
	}
	// 定义将被写死的配置
	getInsideonfig() {
		const config = {
			baseURL: this.baseUrl,
			header: {
				// 配置请求头
				
			}
		}
		return config;
	}
	// 定义拦截器  instance:axios实例   url:请求地址
	interceptors(instance,url) {
		instance.interceptors.request.use(config=>{
			// 处理请求
			console.log("拦截和处理请求")
			console.log(config)
			return config
		})
		instance.interceptors.response.use(res=>{
			// 处理响应
			console.log("处理响应")
			console.log(res)
			return res
		},(error)=>{
			// 请求出问题
			console.log(error)
			return {error:'网络出错'}
		})
	}
	// 发起请求
	request(options) {
		const instance = axios.create() //创建axios实例
		options = Object.assign(this.getInsideonfig(),options)
		this.interceptors(instance,options.url)
		return instance(options);
	}
}
// 实例化对象

const axiosObj = new HttpRequect(baseUrl)
export default axiosObj

api目录下data.js文件
在该文件下,存放所有的请求操作
例:请求轮播图数据代码如下

import axios from './axios.js'

export const getBannerData = ()=>{
	return axios.request({
		url:'banner',
		method:"get"
	})
}

创建axios配置目录
用来存放后台接口地址前缀
config/index.js
源码:

export default {
	title: "config_name",
	baseUrl: {
		dev: "http://localhost:8080", //开发时候的后台地址
		pro: "" //产品上线发布之后,后台接口地址
	}
}

使用封装好的axios
首先需要导入需要请求的操作

import {getBannerData} from './api/data.js'

使用请求操作

async mounted() {
  	let result = await getBannerData()
  }

完整源代码如下:

<template>
  <div id="app">
    <router-view></router-view>
  </div>
</template>

<script>
import {getBannerData} from './api/data.js'
export default {
  name: 'app',
  async mounted() {
  	let result = await getBannerData()
  }
}
</script>
<style>
</style>

技术点:

  • 封装axios,用到了类的知识,创建HttpRequect类之后,需要进行实例化
  • 判断是开发环境,还是运行环境,使用不同的URL
    const baseUrl = process.env.NODE_ENV === ‘development’ ? config.baseUrl.dev : config.baseUrl.pro
  • 发起异步请求,需要对axios进行创建实例化
  • 封装axios之后,需要将该实例对象抛出:export default axiosObj
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值