面试刷题--004

119 阅读11分钟

一、javaScript

1. 刷新浏览器后,Vuex数据持久化

1.1 使用插件vuex-persistedstate

  • 安装vuex-persistedstate
npm i vuex-persistedstate
  • src/store 文件夹下新建 modules 文件,在 modules 下新建 user.js 和 cart.js 三个模块。 (根据项目需求创建)

image.png

  • 使用
import createPersistedState from "vuex-persistedstate"
import user from './modules/user'
import cart from './modules/cart'

export default createStore({
  state: {},
  mutations: {},
  actions: {},
  getters: {},
  modules: {
    // 模块化数据
    user,
    cart
  },
  plugins: [
    // veux持久化配置
    createPersistedstate({
      key: 'rabbitstore-client',
      paths: ['user', 'cart']
    })
  ]
})

默认存储到localStorage 想要存储到sessionStorage,配置如下

import createPersistedState from "vuex-persistedstate"
import user from './modules/user'
import user from './modules/cart'

export default createStore({
  state: {
      assessmentData:null
  },
  mutations: {},
  actions: {},
  getters: {},
  modules: {
    // 模块化数据
    user,
    cart
  },
  plugins: [
    // veux持久化配置
    createPersistedstate({
      storage: window.sessionStorage,
      //只储存state中的assessmentData则加reducer
      reducer(val) {
        return {
          // 只储存state中的assessmentData
          assessmentData: val.assessmentData
        }
      }
    })
  ]
})

1.2 使用vuex-along

vuex-along 的实质也是将 vuex 中的数据存放到 localStorage 或者 sessionStroage 中,只不过这个存取过程组件会帮我们完成,我们只需要用vuex的读取数据方式操作就可以了,简单介绍一下 vuex-along 的使用方法。

  • 安装 vuex-along:
npm install vuex-along --save
  • 配置 vuex-along: 在 store/index.js 中最后添加以下代码:
import VueXAlong from 'vuex-along' //导入插件
export default new Vuex.Store({
    //modules: {
        //controler  //模块化vuex
    //},
    plugins: [VueXAlong({
        name: 'store',     //存放在localStroage或者sessionStroage 中的名字
        local: false,      //是否存放在local中  false 不存放 如果存放按照下面session的配置
        session: { list: [], isFilter: true } 
        //如果值不为false 那么可以传递对象 其中 当isFilter设置为true时, list 数组中的值就会被过滤调,这些值不会存放在seesion或者local中
    })]
});

2. vue3 中的响应式设计原理 reactive ref

const targetMap = new WeakMap() // targetMap stores the effects that each object should re-run when it's updated
let activeEffect = null // The active effect running

function track(target, key) {
  if (activeEffect) {
    // <------ Check to see if we have an activeEffect
    // We need to make sure this effect is being tracked.
    let depsMap = targetMap.get(target) // Get the current depsMap for this target
    if (!depsMap) {
      // There is no map.
      targetMap.set(target, (depsMap = new Map())) // Create one
    }
    let dep = depsMap.get(key) // Get the current dependencies (effects) that need to be run when this is set
    if (!dep) {
      // There is no dependencies (effects)
      depsMap.set(key, (dep = new Set())) // Create a new Set
    }
    dep.add(activeEffect) // Add effect to dependency map
  }
}

function trigger(target, key) {
  const depsMap = targetMap.get(target) // Does this object have any properties that have dependencies (effects)
  if (!depsMap) {
    return
  }
  let dep = depsMap.get(key) // If there are dependencies (effects) associated with this
  if (dep) {
    dep.forEach((effect) => {
      // run them all
      effect()
    })
  }
}

function reactive(target) {
  const handler = {
    get(target, key, receiver) {
      let result = Reflect.get(target, key, receiver)
      track(target, key) // If this reactive property (target) is GET inside then track the effect to rerun on SET
      return result
    },
    set(target, key, value, receiver) {
      let oldValue = target[key]
      let result = Reflect.set(target, key, value, receiver)
      if (result && oldValue != value) {
        trigger(target, key) // If this reactive property (target) has effects to rerun on SET, trigger them.
      }
      return result
    },
  }
  return new Proxy(target, handler)
}

function ref(raw) {
  const r = {
    get value() {
      track(r, 'value')
      return raw
    },
    set value(newVal) {
      raw = newVal
      trigger(r, 'value')
    },
  }
  return r
}

// function ref(intialValue) {
//   return reactive({ value: initialValue })
// }

function effect(eff) {
  activeEffect = eff
  activeEffect()
  activeEffect = null
}

let product = reactive({ price: 5, quantity: 2 })
let salePrice = ref(0)
let total = 0

effect(() => {
  salePrice.value = product.price * 0.9
})

effect(() => {
  total = salePrice.value * product.quantity
})

console.log(
  `Before updated quantity total (should be 9) = ${total} salePrice (should be 4.5) = ${salePrice.value}`
)
product.quantity = 3
console.log(
  `After updated quantity total (should be 13.5) = ${total} salePrice (should be 4.5) = ${salePrice.value}`
)
product.price = 10
console.log(
  `After updated price total (should be 27) = ${total} salePrice (should be 9) = ${salePrice.value}`
)

3. 实现斐波那契数列

3.1 递归(改进递归-摘出存储计算结果的功能函数)

var memoizer = function (func) {
    let memo = [];
    return function (n) {
        if (memo[n] == undefined) {
            memo[n] = func(n)
        }
        return memo[n]
    }
};
var fibonacci=memoizer(function(n){
    if (n == 1 || n == 2) {
        return 1
    };
    return fibonacci(n - 2) + fibonacci(n - 1);
})
fibonacci(30)

3.2 循环(for循环+解构赋值

var fibonacci = function (n) {
    let n1 = 1, n2 = 1;
    for (let i = 2; i < n; i++) {
        [n1, n2] = [n2, n1 + n2]
    }
    return n2
}
fibonacci(30)

4. JS高阶函数——reduce()用法总结

4.1 定义

reduce()方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。语法为:

arr.reduce(callback,[initialValue])
  • callback (执行数组中每个值的函数,包含四个参数)

    • previousValue (上一次调用回调返回的值,或者是提供的初始值(initialValue))
    • currentValue (数组中当前被处理的元素)
    • index (当前元素在数组中的索引)
    • array (调用 reduce 的数组)
  • initialValue (作为第一次调用 callback 的第一个参数。)

4.2 常见用法

4.21 求加法(乘法同理)

let arr = [1, 2, 3, 4, 5];
arr.reduce((sum, curr) => sum + curr, 0);       // 得到15

此基础上还可以求平均值

4.22 求最大值(最小值同理)

let arr = [23,123,342,12];
let max = arr.reduce((pre,cur,index,arr) => {
  return pre > cur ? pre : cur
});                                               // 得到 342

4.23 将字符串转换为整数

let str = "43214"
​
let strParseInt = str.split('')                   // 得到 ['4', '3', '2', '1', '4']
    .map(item => {return item.charCodeAt() - 48}) // 得到 [4, 3, 2, 1, 4]
    .reduce((a, b) => {return a * 10 + b})        // 得到 43214

4.24 字符统计/单词统计同理

let str = 'abcdaabc';
​
str.split('').reduce((res, cur) => {
    res[cur] ? res[cur] ++ : res[cur] = 1 // 如果cur第一次出现,记为1
    return res;                           // 否则记录数+1
}, {})                                    // 得到 {a: 3, b: 2, c: 2, d: 1}

4.25 数组去重

let arr = [1, 2, 3, 4, 4, 1]
let newArr = arr.reduce((pre,cur) => {
    if(!pre.includes(cur)){
      return pre.concat(cur)
    }else{
      return pre
    }
},[])                                     // 得到 [1, 2, 3, 4]

4.26 数组维度转换

let arr = [[0, 1], [2, 3], [4, 5]]      // 二维数组
let newArr = arr.reduce((pre,cur) => {
    return pre.concat(cur)              // 合并pre 与 cur, 并返回一个新数组
},[])
console.log(newArr);                    // 一维数组 [0, 1, 2, 3, 4, 5]

4.3 进阶应用

使用reduce方法可以完成多维度的数据叠加。

4.31 目标对象多个属性的同时叠加,完整代码如下:

var reducers = {  
  totalInEuros : function(state, item) {
    return state.euros += item.price * 0.897424392;
  },
  totalInYen : function(state, item) {
    return state.yens += item.price * 113.852;
  }
};

var manageReducers = function(reducers) {
  return function(state, item) {
    return Object.keys(reducers).reduce(
      function(nextState, key) {
        reducers[key](state, item);
        return state;
      },
      {}
    );
  }
};

var bigTotalPriceReducer = manageReducers(reducers);
var initialState = {euros:0, yens: 0};
var items = [{price: 10}, {price: 120}, {price: 1000}];
var totals = items.reduce(bigTotalPriceReducer, initialState);
console.log(totals);

4.32 koa的源码中,有一个only模块,整个模块就一个简单的返回reduce方法操作的对象:

var only = function(obj, keys){
  obj = obj || {};
  if ('string' == typeof keys) keys = keys.split(/ +/);
  return keys.reduce(function(ret, key){
    if (null == obj[key]) return ret;
    ret[key] = obj[key];
    return ret;
  }, {});
};

var a = {
    env : 'development',
    proxy : false,
    subdomainOffset : 2
}
only(a,['env','proxy'])   // {env:'development',proxy : false}

4.33 一串字符串中每个字母出现的次数


var arrString = 'abcdaabc';

arrString.split('').reduce(function(res, cur) {
    res[cur] ? res[cur] ++ : res[cur] = 1
    return res;
}, {})

ie9以下的浏览器中,并不支持该方法reduce

5. 确保构造函数只能被new调用,而不能被普通调用

JavaScript 中的函数一般有两种使用方式:

  • 当作构造函数使用: new Func()
  • 当作普通函数使用: Func()

5.1 使用 instanceof 实现

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

使用语法:

object instanceof constructor

5.2 new 绑定/ 默认绑定

  • 通过 new 来调用构造函数,会生成一个新对象,并且把这个新对象绑定为调用函数的 this 。
  • 如果普通调用函数,非严格模式 this 指向 window,严格模式指向 undefined

5.3 ES6 new.target

function Person() {
    if (!(new.target)) {
        throw new TypeError('Function constructor A cannot be invoked without "new"')
    }
}
// Uncaught TypeError: Function constructor A cannot be invoked without "new"
console.log("not new:", Person())

5.4 使用ES6 Class

ES6 提供 Class 作为构造函数的语法糖,来实现语义化更好的面向对象编程,并且对 Class 进行了规定:类的构造器必须使用 new 来调用

因此后续在进行面向对象编程时,强烈推荐使用 ES6 的 Class。 Class 修复了很多 ES5 面向对象编程的缺陷,例如类中的所有方法都是不可枚举的;类的所有方法都无法被当作构造函数使用等。

class Person {
    constructor (name) {
        this.name = name;
    }
}
// Uncaught TypeError: Class constructor Person cannot be invoked without 'new'
console.log(Person())

5.5 new.target 实现抽象类

class Animal {
    constructor (type, name, age) {
        this.type = type;
        this.name = name;
        this.age = age;
        console.log(new.target)
    }
}
// extends 是 Class 中实现继承的关键字
class Dog extends Animal {
    constructor(name, age) {
        super("dog", "baobao", "1")
    }
}
const dog = new Dog()

输出结果:

image.png 通过上面案例,我们可以发现子类调用和父类调用的返回结果是不同的,我们利用这个特性,就可以实现父类不可调用而子类可以调用的情况——面向对象中的抽象类

抽象类也可以理解为不能独立使用、必须继承后才能使用的类。

// extends 是 Class 中实现继承的关键字
class Dog extends Animal {
    constructor(name, age) {
        super("dog", "baobao", "1")
    }
}
// Uncaught TypeError: abstract class cannot new
const dog = new Animal("dog", "baobao", 18)

//正确引用
const dog = new Dog("dog", "baobao", 18)
console.log('dog',dog)//{age: "1"name: "baobao"type: "dog"}

Animal 就是一个抽象类,我们不应该通过它来生成动物,而是通过它的子类,例如 Dog、Cat 等来生成对应的 dog/cat 实例。

5.6 总结

本文介绍了三种限制构造函数只能被 new 调用的方案

  • 借助 instanceof 和 new 绑定的原理,适用于低版本浏览器
  • 借助 new.target 属性,可与 class 配合定义抽象类
  • 面向对象编程使用 ES6 class——最佳方案

6. == 的机制

==运算的规则:

  • undefined == null,结果是true。且它俩与所有其他值比较的结果都是false
  • String == Boolean,需要两个操作数同时转为Number。
  • String/Boolean == Number,需要String/Boolean转为Number。
  • Object == Primitive,需要Object转为Primitive(具体通过valueOftoString方法)。

7. 常见函数及IIFE(立即执行函数)

7.1 常见函数

//函数声明:使用function关键字声明一个函数,再指定一个函数名,叫函数声明。
function foo(){     
  var a = 8;
  console.log(a);
}
foo();  //调用函数

//函数表达式 :使用function关键字声明一个函数,但未给函数命名,最后将匿名函数赋予一个变量,叫函数表达式,这是最常见的函数表达式语法形式。
var foo = function () {
  var a = 8;
  console.log(a);
};
foo();  //调用函数

//匿名函数:使用function关键字声明一个函数,但未给函数命名,所以叫匿名函数.
//匿名函数属于函数表达式,匿名函数有很多作用,赋予一个变量则创建函数,赋予一个事件则成为事件处理程序或创建闭包等等。
function () {}; 

函数声明和函数表达式不同之处在于:

  • Javascript引擎在解析javascript代码时会‘函数声明提升’(Function declaration Hoisting)当前执行环境(作用域)上的函数声明,而函数表达式必须等到Javascirtp引擎执行到它所在行时,才会从上而下一行一行地解析函数表达式。
  • 函数表达式后面可以加括号立即调用该函数(所有IIFE在函数外面加()以表示其为函数表达式,不是函数声明),函数声明不可以,只能以fnName()形式调用

7.2 IIFE(立即执行函数)

(function foo(){
  var a = 8;
  console.log(a);
})();

8. Race Condition

8.1 简介

race condition是多线程的应用程序中经常遇到的问题。

从定义来说,race condition是代码中一些执行结果取决于其执行的相对时间或者多线程交错执行的判断条件。它的结果是不可预测的。如何一个程序中不存在race condition,那么它就是线程安全的(thread-safe)。

8.2 Race condition的两个模式

8.21 Check-Then-Act

Race condition有两个模式,第一个就是上面的转账的例子,属于Check-Then-Act模式,这类模式通常是一个流程中有一个检查环节,通过检查的结果再决定后续怎么走。

懒加载是Check-Then-Act的第二例子,例如在单例模式中,懒汉模式如下:

class Singleton {
    public static Object singleton;

    public static Object getSingleton(){
        if (singleton == null) {
            singleton = new Object();
        }
        return singleton;
    }
}

singleton == null就是一个race condition,根据上面的解决办法,最简单是加上synchronized关键字,让判断和赋值两个操作具有线程排他性。

8.22 Read-Modify-Write

Read-Modify-Write是race condtion的第二个模式。在大多数编程语言中,对一个变量的修改通常分为读取,修改,写入三个步骤。问题通常是对变量的并发的读取修改(至少有一个修改,纯读取是没问题的)引起的。例如:
在这里插入图片描述

8.3 如何检测race condition

race condition通常很难重现,定位和排除,因为问题发生依赖于特定的场景。即使写一个多线程的单元也不可以保证程序的100%的正确定,但有一些方法可以排除race condition。

8.4 如何避免race condition

8.41 1.避免使用共享变量

在多线程环境中,race condtion的出现往往伴随着共享变量,如上面例子中的账户余额和count变量,都可以被多个线程读取操作,那么避免问题发生最简单直接的办法就是避免使用共享变量。可以使用不可变对象或者线程本地对象(例如java中的threadlocal)。

8.42 2.使用同步或者原子操作

使用同步可以避免race condition的问题,但是线程同步往往伴随很多同步的性能开销,还可能导致死锁。两个办法是使用原子操作,例如java中的提供的原子类。

9. DNS优化

9.1 在介绍dns-prefetch之前,先要提下当前对于DNS优化主流方法。

一般来说,一次DNS解析需要耗费 20-120ms,所以为了优化DNS,我们可以考虑两个方向:

  1. 减少DNS请求次数
  2. 缩短DNS解析时间dns-prefetch

9.2 最佳实践

请记住以下三点:

  1. dns-prefetch 仅对跨域域上的 DNS查找有效,因此请避免使用它来指向相同域。这是因为,到浏览器看到提示时,您站点域背后的IP已经被解析。
  2. 除了link 还可以通过使用 HTTP链接字段将 dns-prefetch(以及其他资源提示)指定为 HTTP标头
Link: <https://fonts.gstatic.com/>; rel=dns-prefetch
  1. 考虑将 dns-prefetch 与 preconnect(预连接)提示配对。

由于dns-prefetch 仅执行 DNS查找,不像preconnect 会建立与服务器的连接。

如果站点是通过HTTPS服务的,两者的组合会涵盖DNS解析,建立TCP连接以及执行TLS握手。将两者结合起来可提供进一步减少跨域请求的感知延迟的机会。如下所示:

<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin>
<link rel="dns-prefetch" href="https://fonts.gstatic.com/">

注意: 如果页面需要建立与许多第三方域的连接,则将它们预先连接会适得其反。 preconnect 提示最好仅用于最关键的连接。对于其他的,只需使用 <link rel="dns-prefetch"> 即可节省第一步的时间DNS查找。

10. 什么是点击劫持?如何防范点击劫持?

点击劫持是一种视觉欺骗的攻击手段,攻击者将需要攻击的网站通过iframe嵌套的方式嵌入自己的网页中,并将 iframe 设置为透明,在页面中透出一个按钮诱导用户点击。

我们可以在 http 相应头中设置 X-FRAME-OPTIONS 来防御用 iframe 嵌套的点击劫持攻击。通过不同的值,可以规定页面在特 定的一些情况才能作为 iframe 来使用。