Skip to content

模拟题六

1. CSS 优先级是怎么计算的

!important > 内联样式 > ID 选择器 > 类选择器 = 属性选择器 = 伪类选择器 > 标签选择器 = 伪元素选择器

2. 手写 apply、call、bind

apply

javascript
Function.prototype.myapply = function (context = window, args) {
    if (this === Function.prototype) {
        return undefined;
    }
    const fn = Symbol()
    context[fn] = this
    
    let result;
    if (!Array.isArray(args)) {
        result = context[fn]()
    } else {
        result = context[fn](...args)
    }
    
    delete context[fn]
    return result
}

call

javascript
Function.prototype.mycall = function (context = window, ...args) {
    if (this === Function.prototype) {
        return undefined;
    }
    const fn = Symbol();
    context[fn]= this;
    const result = context[fn](...args)
    delete context[fn]
    return result
}

bind

javascript
Function.prototype.mybind = function (context, ...args1) {
    if (this === Function.prototype) {
        return new TypeError('Error')
    }
    const _this = this;
    return function F(...args2) {
        if (this instanceof F) {
            return new _this(...args1, ...args2)
        }
        return _this.apply(context, args1.concat(args2))
    }
}

一个绑定函数也能使用 new 操作符创建对象,就像把原函数当做构造器。

衍生问题:Symbol、数据类型、instanceof、new

Symbol

唯一值,ES6新增

数据类型

分为基本类型和引用类型

基本类型包含:undefined、null、string、number、boolean、symbol(ES6 新增)、bigint(ES10 新增)

引用类型:object

基本类型存在栈内存中,引用类型存在堆内存中

判断数据类型的方式有四种: typeof、instanceof、constructor、Object.prototype.toString.call()

手写 instanceof

javascript
function myInstanceof (left, right) {
    if (typeof left !== 'object' || typeof left === null) return false;
    
   	let proto = Object.getPrototypeOf(left);
    while(true) {
        if (proto === null) return false
        
        if (proto === right.prototype) return true
        proto = Object.getPrototypeOf(proto)
    }
}

手写 new

javascript
function new2(Constructor, ...args) {
    if(typeof Fn != 'function'){
        throw new TypeError(Fn + 'is not a constructor')
    }
    let obj = Object.create(null);
    obj.__proto__ = Constructor.prototype;
    const result = Constructor.apply(obj, args)
    return typeof result === 'object' ? result : obj
}

相关文章:call、apply、bind三大将

3. 谈谈变量提升

考察点:变量提升

变量是会提升的。var 申明的变量会提升至顶部,然后赋值时在执行

函数提升的权重大于变量提升

4. let 和 const

类似问题:let、var、const的区别

var 会有变量提升

let、const 则不会

5.React 中的多次渲染如何优化

使用 React.memo + React.useCallback 和 React.useMemo 来做优化

6. useLayoutEffect 和 useEffect 的区别

useLayoutEffect 和 useEffect 的区别在于执行时机:

  • useEffect 是在浏览器页面渲染后异步执行、,它不会阻塞浏览器的渲染过程
    • commit 阶段的 before mutation 阶段调用
    • 但是会在 layout 完成后才异步执行
  • useLayoutEffect 是在浏览器完成 DOM 更新后但浏览器进行下一次绘制之前同步执行,它会阻塞浏览器的渲染过程
    • commit 阶段 的 layout 阶段同步执行
    • 等价于类组件中的 componentDidMount

衍生问题:React 的执行时机

React 的执行时机

React 分为 render 和 commit 阶段

render 阶段找虚拟 DOM 中变化的部分,并打上增删改的标记(effectTag),将这些节点收集到一个队列中(effectList)

在 commit 阶段遍历 effectList,根据 effectTag 来增删改 DOM,commit 阶段分为 3 个小阶段

before mutation

  • 异步调用 useEffect 的回调函数
  • 异步调用,等 layout 阶段执行完后再执行异步的回调函数

mutation

  • 遍历 effectList 更新 DOM

layout

  • 同步调用 useLayout 的回调函数
  • 能拿到新的 dom
  • 还会更新 ref

7. 什么是单点登录(SSO )?如何实现

单点登录(SSO,Single Sign-On)是一种身份验证解决方案

SSO 的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统

SSO 一般都需要一个独立的认证中心(passport),子系统的登录均得通过 passport,子系统本身将不参与登录操作

这种方式简化了用户身份验证过程,提高了用户体验,减少了用户需要记住和输入多个用户名和密码的麻烦。

衍生题:与 OAuth2.0 有什么关系

OAuth2.0:联合登录,一处登录,多处使用

SSO 单点登录,一处登录,多处同时登录

8. 浏览器:Cookie、localStorage、sessionStorage、IndexedDB的区别

Cookie:

  • 每次请求都会发送服务器
  • 大小限制在 4 KB 左右,具有生存期,可以设置过期时间

localStorage:

  • 尽在客户端使用
  • 适用于存储较大数量的数据,通常大小限制为 5MB 至 10MB
  • 数据持久存在,除非主动清理

sessionStorage:

  • 也仅在客户端使用,且仅在一个浏览器标签页的会话中存在。
  • 当关闭标签页或浏览器时,数据会被清除

indexedDB:

  • 支持大量数据的存储,并可进行复杂的查询和事务处理
  • 数据存储更为灵活,可以处理结构化的数据,适合大规模应用。
  • 支持异步操作,不会阻塞主线程

衍生问题:cookie 如何防止信息被篡改

  1. cookie 有效期不要过长
  2. 设置 HTTPOnly 的属性为 true
  3. 设置复杂的 cookie
  4. session 和 cookie 一起用
  5. 尽量使用 https

9. webpack 怎么配置多页面?

对应的 webpack config:

javascript
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    entry: {
        app: "./src/app.js",
        admin: "./src/admin.js"
    },
    plugins: [
        new HtmlWebpackPlugin({
            filename: 'index.html',
            chunks: ['app']
        }),
        new HtmlWebpackPlugin({
            filename: 'admin.html',
            chunks: ['admin']
        })
    ]
}

但是,这样配置会有一个「重复打包」的问题:假设 app.js 和 adminjs 都引入了 vue.js,那么 vue.js的代码既会打包进 app.js,也会打包进 admin.js。我们需要使用 optimization.splitchunks 将共同依赖单独打包成 common.js(HtmlWebpackPlugin 会自动引入 commonjs)。

如何支持无限多页面呢?

写遍历文件就实现了

javascript
const HtmlWebpackPlugin = require('html-webpack-plugin')
const fs = require('fs')
const path = require('path')

const filenames = fs.readdirSync('./src/pages')
	.filter(file => file.endsWith('.js'))
	.map(file => path.basename(file, '.js'))

const entries = filesnames.reduce((result, name) => (
{...result, [name]: `./src/pages/${name}.js`}
), {})

const plugins = filenames.map((name) => 
    new HtmlWebpackPlugin({
    	filename: name + '.html',
    	chunks: [name]
	})                     
)

module.exports = {
    entry: {
       ...entries
    },
    plugins: [
       ...plugins
    ]
}

10.算法题:有效的括号

javascript
function isValid(s) {
    const n = s.length;
    
    if (n % 2 === 1) {
        return false
    }
    
    const pairs = new Map([
        [')', '('],
        ['}', '{'],
        [']', '[']
    ])
    
    const stk = [];
    for (let ch of s) {
        if (pairs.has(ch)) {
            if (!stk.length || stk[stk.length - 1] !== pairs.get(ch)) {
                return false
            }
            stk.pop()
        } else {
            stk.push(ch)
        }
    }
    return !stk.length
}