Skip to content

第一次模拟面试

January 13, 2023 | 07:38 PM

第一次模拟面试

es6新增了哪些内容

回答这个问题的时候最好说自己熟悉的内容,比如let const,箭头函数,Promise等等,方便面试官接着问之后的东西

这些特性使得 ES6 更加现代化、强大和易用,为 JavaScript 开发带来了很多便利。

CSS部分:

css实现三角形:

<div class="div"></div>
    <style>
      .div{
        width:0;
        height:0;
        border-bottom:50px solid black;
        border-left:50px solid transparent;
        border-right:50px solid transparent;
      }
    </style>

具体代码:https://stackblitz.com/edit/web-platform-wqhwfy?file=index.html

BFC是什么:

块格式化上下文(Block Formatting Context,BFC)是Web页面的可视化CSS渲染的一部分,是布局过程中生成块级盒子的区域,也是浮动元素与其他元素的交互限定区域。

通俗理解:

这里简单列举几个触发BFC使用的CSS属性:

水平 垂直居中:

.box {
  display: flex;
}

.item {
  margin: auto;
}
image.png

演示代码地址:https://stackblitz.com/edit/web-platform-sjpbjm?file=index.html

选择器权重:

简单计算

!important>行内样式>ID选择器 > 类选择器 | 属性选择器 | 伪类选择器 > 元素选择器

正确的回答方式就是按照顺序来说,然后说权值是多少,比如1000 0100 0010 0001

上面的四个就是大厂面试最最常见的四种css面试题

JavaScript部分:

0.1+0.2==?

0.1 + 0.2  // 0.30000000000000004 

简单概括:浮点数转二进制时丢失了精度,计算完再转回十进制时和理论结果不同。

具体原理

计算机是通过二进制的方式存储数据的,所以计算机计算0.1+0.2的时候,实际上是计算的两个数的二进制的和。0.1的二进制是0.0001100110011001100...(1100循环),0.2的二进制是:0.00110011001100...(1100循环),这两个数的二进制都是无限循环的数。

一般我们认为数字包括整数和小数,但是在 JavaScript 中只有一种数字类型:Number,它的实现遵循IEEE 754标准,使用64位固定长度来表示,也就是标准的double双精度浮点数。在二进制科学表示法中,双精度浮点数的小数部分最多只能保留52位,再加上前面的1,其实就是保留53位有效数字,剩余的需要舍去,遵从“0舍1入”的原则。

根据这个原则,0.1和0.2的二进制数相加,再转化为十进制数就是:0.30000000000000004

JavaScript数据类型:

image.png

上面的是基本数据类型,还有个数据类型就是object

箭头函数和普通函数的区别:

箭头函数无 this,无 prototype(显示原型)

箭头函数无原型函数

箭头函数有几个使用注意点。

箭头函数里面的this指向哪?

箭头函数本身是没有this的,箭头函数的this指向它所在上下文的对象,如果在箭头函数里使用了this,它会指向最近一层作用域内的this

原型和原型链

原型:构造函数.显示prototype=实例对象.隐式__proto__,构造函数的显示原型中含有该构造器 原型链:在此基础上,构造函数的显示原型指向另外一个构造函数的显示原型

image.png

在这里的隐式原型我们就可以使用student._proto_,便可以访问到隐式原型了

image.png

Student.prototype便是显示原型

image.png

通过原型链一层层往上找,形成链式结构,直到找到null

如何构造一条原型链

JavaScript中的原型链是通过对象的prototype属性来实现的。

可以使用以下代码构造一条原型链:

Copy code// 创建父类
function Parent() {
  this.name = "Parent";
}

// 父类的方法
Parent.prototype.sayName = function() {
  console.log(this.name);
};

// 创建子类
function Child() {
  this.name = "Child";
}

// 设置子类的原型对象为父类的实例
Child.prototype = new Parent();

// 创建子类实例
var child = new Child();

// 调用父类方法
child.sayName(); // "Child"

这样,我们就构造出了一条原型链:Child.prototype指向Parent的实例,Parent.prototype指向Object.prototypeObject.prototype指向null

手写部分:

发布订阅模式

发布-订阅模式其实是一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知。

订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。

1. 实现思路

2.代码实现

let eventEmitter = {
    // 缓存列表
    list: {},
    // 订阅
    on (event, fn) {
        let _this = this;
        // 如果对象中没有对应的 event 值,也就是说明没有订阅过,就给 event 创建个缓存列表
        // 如有对象中有相应的 event 值,把 fn 添加到对应 event 的缓存列表里
        (_this.list[event] || (_this.list[event] = [])).push(fn);
        return _this;
    },
    // 监听一次
    once (event, fn) {
        // 先绑定,调用后删除
        let _this = this;
        function on () {
            _this.off(event, on);
            fn.apply(_this, arguments);
        }
        on.fn = fn;
        _this.on(event, on);
        return _this;
    },
    // 取消订阅
    off (event, fn) {
        let _this = this;
        let fns = _this.list[event];
        // 如果缓存列表中没有相应的 fn,返回false
        if (!fns) return false;
        if (!fn) {
            // 如果没有传 fn 的话,就会将 event 值对应缓存列表中的 fn 都清空
            fns && (fns.length = 0);
        } else {
            // 若有 fn,遍历缓存列表,看看传入的 fn 与哪个函数相同,如果相同就直接从缓存列表中删掉即可
            let cb;
            for (let i = 0, cbLen = fns.length; i < cbLen; i++) {
                cb = fns[i];
                if (cb === fn || cb.fn === fn) {
                    fns.splice(i, 1);
                    break
                }
            }
        }
        return _this;
    },
    // 发布
    emit () {
        let _this = this;
        // 第一个参数是对应的 event 值,直接用数组的 shift 方法取出
        let event = [].shift.call(arguments),
            fns = [..._this.list[event]];
        // 如果缓存列表里没有 fn 就返回 false
        if (!fns || fns.length === 0) {
            return false;
        }
        // 遍历 event 值对应的缓存列表,依次执行 fn
        fns.forEach(fn => {
            fn.apply(_this, arguments);
        });
        return _this;
    }
};

function user1 (content) {
    console.log('用户1订阅了:', content);
}

function user2 (content) {
    console.log('用户2订阅了:', content);
}

function user3 (content) {
    console.log('用户3订阅了:', content);
}

function user4 (content) {
    console.log('用户4订阅了:', content);
}

// 订阅
eventEmitter.on('article1', user1);
eventEmitter.on('article1', user2);
eventEmitter.on('article1', user3);

// 取消user2方法的订阅
eventEmitter.off('article1', user2);

eventEmitter.once('article2', user4)

// 发布
eventEmitter.emit('article1', 'Javascript 发布-订阅模式');
eventEmitter.emit('article1', 'Javascript 发布-订阅模式');
eventEmitter.emit('article2', 'Javascript 观察者模式');
eventEmitter.emit('article2', 'Javascript 观察者模式');

// eventEmitter.on('article1', user3).emit('article1', 'test111');

/*
    用户1订阅了: Javascript 发布-订阅模式
    用户3订阅了: Javascript 发布-订阅模式
    用户1订阅了: Javascript 发布-订阅模式
    用户3订阅了: Javascript 发布-订阅模式
    用户4订阅了: Javascript 观察者模式
*/

bind是什么,如何实现:

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

js当中实现最简单的bind:

Function.prototype.fakeBind = function(obj) {
  return (...args) => this.apply(obj, args)
}

测试一下:

function f (arg) {
  console.log(this.a, arg)
}

// output: 3, 4
f.bind({ a: 3 })(4)

// output: 3, 4
f.fakeBind({ a: 3 })(4)

实现call

Function.prototype.myCall=function(thisArg,...args){
  thisArg.t=this
  return thisArg.t(...args)
}

实现apply

Function.prototype.myApply=function(thisarg,args){ 
  let fnName=Symbol()
  thisarg[fnName]=this  //this指向被调用的函数fn
  const result = thisarg[fnName](...args)
  delete thisarg[fnName]
  return result
}

手撕instanceof:

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

A.instance of(B) 沿着A的隐式原型链去寻找B的显示原型

function my_instanceofA,B
{
  while(true)
  {
  if(A._proto_=== B.prototype)
  	{
    return true;
 	  }
  A = A._proto_;
  if(A._proto_ === null)
  	{
    return false;
 	  }
}

具体代码(包含测试):https://stackblitz.com/edit/js-udyzw9?file=index.js

手撕promise.all

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(88);
  }, 1000);
});
let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(99);
  }, 3000);
});

let arr = [p1, p2, 3, 4];

function myPromiseALL(array) {
  //返回的是一个Promise,直接用async包裹
  let result = [];
  let flag = 0;
  return new Promise((resolve, reject) => {
    for (let index in array) {
      if (array[index] instanceof Promise) {
        array[index].then(
          (res) => {
            result[index] = res;
            flag++;
            if (flag == array.length) {
              resolve(result);
            }
          },
          (err) => {
            reject(err);
          }
        );
      } else {
        result[index] = array[index];
        flag++;
        if (flag == array.length) {
          resolve(result);
        }
      }
    }
  });
}

Promise.myPromiseAll = myPromiseALL;
myPromiseALL(arr).then(
  (res) => {
    console.log(res, "res");
  },
  (err) => {
    console.log(err, "err");
  }
);
//[88,99,3,4] 'res'

//将p2改为reject
let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(99);
  }, 3000);
});
//99 'err'

手撕promise.race()

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(88);
  }, 1000);
});
let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(99);
  }, 2000);
});
let arr = [p1, p2];
function myPromiseRace(array) {
  return new Promise((resolve, reject) => {
    for (let item of array) {
      if (item instanceof Promise) {
        item.then(
          (res) => {
            resolve(res);
          },
          (err) => {
            reject(err);
          }
        );
      } else {
        resolve(item);
      }
    }
  });
}
Promise.myPromiseRace = myPromiseRace;
myPromiseRace(arr).then(
  (res) => {
    console.log(res, "res");
  },
  (err) => {
    console.log(err, "err");
  }
);

计算机网络

关键渲染路径

什么是关键渲染路径?

GUI线程中把htmlcss代码解析到渲染在页面上的这条流水线

过程

1.HTML解析生成DOM树

2.CSS解析生成CSSOM树

3.DOM树和CSSOM树合成 渲染render树

4.根据render树和视口进行Layout布局,计算每个节点的位置和大小(布局render树)

5.Paint绘制或栅格化,将每个节点绘制到页面上(绘制render树,像素px信息)

6.转交给合成进程,光栅化线程池,GPU进程合成,显示在屏幕上

重新定义重溯和回流

回流:Layout阶段计算出节点的位置大小发送改变,然后重新布局渲染

重溯:Layout阶段未作出改变,只重新进行Paint渲染/栅格化

输入URL到页面渲染

1.DNS解析

将域名解析成ip地址

2.发送TCP连接

传包 TCP三次握手、四次挥手 2MSL的原因

3.发送http请求

特殊请求报文 keep-alive https http2 先进先出 队头阻塞

4.服务器获取请求并返回http响应报文

特殊响应报文 状态码 浏览器缓存

5.浏览器解析并渲染页面

HTML-DOM CSS-CSSOM 渲染树 减少重溯和回流-重绘和重排 js解析 事件循环

6.断开连接

四次挥手的原因

Https安全的原因:

因为在中间加入了一层SSL/TLS(运输 安全 协议)协议

使用CA证书给客户端公钥认证 想当于打了一层身份证

特点:非对称加密

vue部分:

vue2和vue3响应式原理

Vue2

数据劫持核心: defineProperty setter和getter 遍历添加响应式: Oberserver类和defineReactive 优化: ob 发布订阅模式: Dep类和Watcher类 添加订阅:dep.addSub(wtacher实例) 发布通知: dep.notify( )->sub[i].undate()->wtacher.run()->watcher.get()->getter watcher种类: 计算属性watcher、用户自定义watcher。渲染watcher

Vue3

数据劫持核心: Proxy sethandler和gethandler

nextTick()解决了什么问题

当你在 Vue 中更改响应式状态时,最终的 DOM 更新并不是同步生效的,而是由 Vue 将它们缓存在一个队列中,直到下一个“tick”才一起执行。这样是为了确保每个组件无论发生多少状态改变,都仅执行一次更新。

nextTick() 可以在状态改变后立即使用,以等待 DOM 更新完成。你可以传递一个回调函数作为参数,或者 await 返回的 Promise