前端面试集

2022-12-03  本文已影响0人  云顶天宫写代码

1. 如何理解 HTML 语义化?

让人更容易读懂(增加代码可读性)。比如video、audio、header(头部)、footor(底部)

2. script 标签中 defer 和 async 的区别?

defer是“渲染完再执行”,async是“下载完就执行”。
举例: 上课期间老师一直讲课,上课中提了一个问题,
defer 的方式是下课后再和同学交流这个问题
async 是有同学想出来了,上课暂时中断,交流提出的问题

3. 从浏览器地址栏输入 url 到请求返回发生了什么

1.解析URL,构建一个请求
2.DNS域名解析
3.TCP连接(三次握手)
4.请求处理
5.浏览器拿到数据后做相应的操作
6.TCP断开(四次挥手)

5. 盒模型 (指的是宽、高属性依据)

1 标准盒模型 (默认使用)
设置宽高属性后只包含 content
2.IE盒模型
设置宽高属性后包含 content + padding + border

6. css 选择器和优先级

内联 > ID选择器 > 类选择器 > 标签选择器。

重排(reflow)和重绘(repaint)的理解

  1. 发生重排一定会发生重绘

对 BFC(块级格式上下文) 的理解

  1. 是指当前元素的一个矩形区域

创建 BFC 的方式:
绝对定位元素(position 为 absolute 或 fixed )。
行内块元素,即 display 为 inline-block 。
overflow 的值为 hidden (比较推荐这种方式) 。

可以解决的布局问题

水平垂直居中多种实现方式

.father {
  position: relative;
}
.son {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}
.father {
  position: relative;
}
.son {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0px;
  margin: auto;
  height: 100px;
  width: 100px;
}

利用绝对定位,设置 left: 50% 和 top: 50% 现将子元素左上角移到父元素中心位置,然后再通过 margin-left 和 margin-top 以子元素自己的一半宽高进行负值赋值。

.father {
  position: relative;
}
.son {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 200px;
  height: 200px;
  margin-left: -100px;
  margin-top: -100px;
}
.father {
  display: flex;
  justify-content: center;
  align-items: center;
}

flex 布局

单词读熟悉 详细请看这里

flex-direction
justify-content
align-items
align-content
flex-wrap 
flex-flow

怎么理解flex:1 ? 可以说是弹性比例的权重,如果有两个元素都设置flex:1, 相当于平分父的宽或高

深拷贝与浅拷贝

深拷贝:增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存
浅拷贝:只是增加了一个指针指向已存在的内存地址

对比举例:

    const a={
        name: "wang",
        age:18
    }
    const b = a;
    b.age = 20;
    console.log(a); // {name:"wang",age:20}
    console.log(b);//{name:"wang",age:20}

实质:如果A拷贝B,修改A中的值,观察B是否变化,如果B变化则是浅拷贝,如果不变化则是深拷贝

浅拷贝实现:

  1. Object.assign() 或者 展开运算符号(...)
var obj = { a: {a: "kobe", b: 39} };
var initalObj = Object.assign({}, obj);
initalObj.a.a = "wade";
console.log(obj.a.a); //wade

当object只有一层的时候,是深拷贝

let obj = {
    username: 'kobe'
    };
let obj2 = Object.assign({},obj);
obj2.username = 'wade';
console.log(obj);   //{username: "kobe"}
  1. 深拷贝实现
 function checkedType(target) {
      return Object.prototype.toString.call(target).slice(8, -1)
    }
    //实现深度克隆---对象/数组
    function clone(target) {
      //判断拷贝的数据类型
      //初始化变量result 成为最终克隆的数据
      let result, targetType = checkedType(target)
      if (targetType === 'Object') {
        result = {}
      } else if (targetType === 'Array') {
        result = []
      } else {
        return target
      }
      //遍历目标数据
      for (let i in target) {
        //获取遍历数据结构的每一项值。
        let value = target[i]
        //判断目标结构里的每一值是否存在对象/数组
        if (checkedType(value) === 'Object' ||
          checkedType(value) === 'Array') { //对象/数组里嵌套了对象/数组
          //继续遍历获取到value值
          result[i] = clone(value)
        } else { //获取到value值是基本的数据类型或者是函数。
          result[i] = value;
        }
      }
      return result
    }

0.1+0.2 ! == 0.3 如何做到让其相等呢?

使用 Number.EPSILON 误差范围。

function isEqual(a, b) {
  return Math.abs(a - b) < Number.EPSILON;
}
console.log(isEqual(0.1 + 0.2, 0.3)); // true

理解 原型与原型链

原型:每一个 JavaScript 对象(null 除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性,其实就是 prototype 对象。
原型链:由相互关联的原型组成的链状结构就是原型链。

我们声明了一个Person函数,既然函数是对象,那么我们就可以使用“.”来查看它的属性,可以看到有一个prototype属性,这个是每一个函数都有的。

我们在代码里面打印出来看看,理解原型示例代码如下:

<script>
  function Person() { }
  console.log(Person.prototype)
</script>

输出结果:

image.png

理解原型链示例代码:

<script>
  function Person(name) {
    this.name = name;
  }
  // 在函数的原型上添加变量和方法
  Person.prototype.name = "信思智学";
  Person.prototype.say = function () {
    console.log("你好信思智学");
  }


  let obj = new Person("张三");
  console.log(obj.name); // 张三
  obj.say(); // 你好信思智学


  console.log(obj)
</script>
image.png

可通过打印的日志,辅助理解原型链

执行上下文

回答和this相关,可以提到箭头函数
以下代码辅助理解 this

var obj = {
  foo: function () { console.log(this.bar) },
  bar: 1
};

var foo = obj.foo;
var bar = 2;

obj.foo() // 1
foo() // 2

闭包理解

用途或者说使用场景:封装私有变量

不会引起内存泄漏!!! IE9之前是有问题的,现在的浏览器都没问题

var person = function(){    
    //变量作用域为函数内部,外部无法访问    
    var name = "default";       

    return {    
       getName : function(){    
           return name;    
       },    
       setName : function(newName){    
           name = newName;    
       }    
    }    
}();    

console.log(person.name);//直接访问,结果为undefined    
console.log(person)
console.log(person.getName());    //default
person.setName("abruzzi");    
console.log(person.getName());    //abruzzi

Promise

面试ES6 开始使用的异步编程解决方式,主要用来解决回调地狱的问题,可以有效的减少回调嵌套, 更符合编码思维

在哪用到过呢?

  1. uni-app中关于 request 请求的封装


    image.png
  2. react 中配合async await,实现数据的同步调用



垃圾回收机制

深入了解这里看这篇文章即可:「硬核 JS」你真的了解垃圾回收机制吗

总结一下:

有两种垃圾回收策略:

function test(){
  let A = new Object()   // 有标记
  let B = new Object()  // 有标记
}

test()  // 执行完毕后 标记为清除对象
let a = new Object()    // 此对象的引用计数为 1(a引用)
let b = a       // 此对象的引用计数是 2(a,b引用)
a = null        // 此对象的引用计数为 1(b引用)
b = null        // 此对象的引用计数为 0(无引用)
...         // GC 回收此对象

这种方式是不是很简单?确实很简单,不过在引用计数这种算法出现没多久,就遇到了一个很严重的问题——循环引用,即对象 A 有一个指针指向对象 B,而对象 B 也引用了对象 A ,如下面这个例子

function test(){
  let A = new Object()
  let B = new Object()
  
  A.b = B
  B.a = A
}

对象 A 和 B 通过各自的属性相互引用着,按照上文的引用计数策略,它们的引用数量都是 2,但是,在函数 test 执行完成之后,对象 A 和 B 是要被清理的,但使用引用计数则不会被清理,因为它们的引用数量不会变成 0,假如此函数在程序中被多次调用,那么就会造成大量的内存不会被释放

防抖与节流

如何避免内存泄漏

  1. 减少不必要的全局变量,使用严格模式避免意外创建全局变量。

例如:无意义的变量提升

  1. 在你使用完数据后,及时解除引用(闭包中的变量,dom引用,定时器清除)。

例如: 轮播图的定时器,setTimeOut /setInterval 要根据ID及时清除

  1. 组织好你的逻辑,避免死循环等造成浏览器卡顿,崩溃的问题。

例如:递归的方法要有终止递归进行的条件判断

React 面试相关

React 16.8 加入的新特性
必须在函数数式组件中使用(类组件可以不用看了,简单了解就行)

memo 是一个高阶组件,默认情况下会对 props 进行浅比较,如果相等不会重新渲染。多数情况下我们比较的都是引用类型,浅比较就会失效,所以我们可以传入第二个参数手动控制。
useMemo 返回的是一个缓存值,只有依赖发生变化时才会去重新执行作为第一个参数的函数,需要记住的是,useMemo 是在 render 阶段执行的,所以不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴

useCallback 可缓存函数,其实就是避免每次重新渲染后都去重新执行一个新的函数。
useMemo 可缓存值。

使用 React.memo 来缓存组件。
使用 React.useMemo 缓存大量的计算
避免使用匿名函数。
使用 React.Fragment 避免添加额外的 DOM。

Keys 是 React 用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识。在开发过程中,我们需要保证某个元素的 key 在其同级元素中具有唯一性。

在React中,组件返回的元素只能有一个根元素。为了不添加多余的DOM节点,我们可以使用Fragment标签来包裹所有的元素,Fragment标签不会渲染出任何元素。React官方对Fragment的解释:

React 中的一个常见模式是一个组件返回多个元素。Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。

import React, { Component, Fragment } from 'react'

// 一般形式
render() {
  return (
    <React.Fragment>
      <ChildA />
      <ChildB />
      <ChildC />
    </React.Fragment>
  );
}
// 也可以写成以下形式
render() {
  return (
    <>
      <ChildA />
      <ChildB />
      <ChildC />
    </>
  );
}

Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。
创建ref的形式有四种:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return <div ref="myref" />;
  }
}

访问当前节点的方式如下:

this.refs.myref.innerHTML = "hello";
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return <div ref={this.myRef} />;
  }
}

当 ref 被传递给 render 中的元素时,对该节点的引用可以在 ref 的 current 属性中访问

const node = this.myRef.current;
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return <div ref={element => this.myref = element} />;
  }
}

获取ref对象只需要通过先前存储的对象即可

const node = this.myref 

通过useRef创建一个ref,整体使用方式与React.createRef一致

function App(props) {
  const myref = useRef()
  return (
    <>
      <div ref={myref}></div>
    </>
  )
}

获取ref属性也是通过hook对象的current属性

const node = myref.current;

应用场景

  • 对Dom元素的焦点控制、内容选择、控制
  • 对Dom元素的内容设置及媒体播放
  • 对Dom元素的操作和对组件实例的操作
    ( antdpro 中 ProTable使用ref 来进行表格数据的重载)
  • 集成第三方 DOM 库

可以说整体用的UmiJS 中的model,作为数据共享的基础
以函数式组件为主:

react 路由怎么配置

可以说在 umijs中,使用 .umirc.ts 中配置

import { defineConfig } from '@umijs/max';

export default defineConfig({
 antd: {},
 access: {},
 model: {},
 initialState: {},
 request: {},
 layout: {
   title: '@umijs/max',
 },
 routes: [
   {
     path: '/',
     redirect: '/login',
   },
   {
     name: '首页',
     path: '/home',
     component: './Home',
   },
   {
     name: '权限演示',
     path: '/access',
     component: './Access',
   },
   {
     name: '系统管理',
     path: '/system',
     routes:[
       {
         name:'用户管理',
         path:'/system/user',
         component: '@/pages/System/User'
       }
     ]
   },
   {
     name: '登录',
     path: '/login',
     component: './Login',
     layout: false   // 不使用默认布局,左侧菜单不显示
   },
 ],
// 配置代理解决跨域问题
 proxy: {
   '/api': {
     'target': 'https://www.efss.cn/cms',
     'changeOrigin': true,
     'pathRewrite': { '^/api' : '/api' },
   },
   '/auth': {
     'target': 'https://www.efss.cn/cms',
     'changeOrigin': true,
     'pathRewrite': { '^/auth' : '/auth' },
   }
 },
 npmClient: 'yarn',
});

Hooks: React为我们提供很多钩子

最常用的

useState()
useEffect()
其他

不同的钩子为函数引入不同的外部功能,我们发现上面钩子都带有use前缀,React约定,钩子一律使用 use前缀命名。所以,你自己定义的钩子都要命名为useXXX。

  1. userState():状态钩子 - 函数组件局部的状态
import React, {useState} from 'react'
const AddCount = () => {
  const [ count, setCount ] = useState(0)
  const addcount = () => {
    let newCount = count
    setCount(newCount+=1)
  } 
  return (
    <>
      <p>{count}</p>
      <button onClick={addcount}>count++</button>
    </>
  )
}
export default AddCount
  1. useEffect():副作用钩子
    • 第一个参数必须传入函数
    • 第二个参数
      情况1 没有第二个参数,随时(任何状态的变化)都会触发
      情况2 传入空数组[],只在组件初始化(组件渲染完成)的时候触发
      情况3 数组中加入 state,建立一个一一对应的关系。
import React, { useEffect, useState } from "react";

const TownAmount: React.FC = (props:any)=>{
    // useState 是 hook中的一个钩子, 怎么使用:
    // const [状态名字, set状态名字] = useState(状态默认值); 
    // ownerAmount 是一个状态,可以是任何数据类型  setOwnerAmount 是一个方法
    const [ownerAmount, setOwnerAmount] = useState(10);
    const [peoples, setPeoples] = useState(5);  // 常住农业人口
    /**
     * 第一个参数必须传入函数
     * 第二个参数
     * 情况1 没有第二个参数,随时(任何状态的变化)都会触发
     * 情况2 传入空数组[],只在组件初始化(组件渲染完成)的时候触发
     * 情况3 数组中加入 state,建立一个一一对应的关系。
     * 
     */
    useEffect(()=>{
        console.log('副作用发生了')
        // 这里写关联的逻辑
        return ()=>{
            console.log('卸载副作用,类似于clearInterval')
        }
    },[peoples,ownerAmount]);

    const PayAmount = ()=>{
        setOwnerAmount((ownerAmount-2));
    }

    /**
     * 外出1万人
     */
    const OutPeople = ()=>{
        setPeoples(peoples-1);
    }

    return (
        <>  
            <button onClick={PayAmount}>
                支出2万
            </button>
            <button onClick={OutPeople}>
                进城务工 1 万人
            </button>
            <h4>乡镇收到拨款:{props.amount} 目前余额:{ownerAmount} 剩余人口:{peoples}万人</h4>
        </>
    )
}

export default TownAmount;
  1. useContext():共享状态钩子 - 函数组件之间全局的状态
import React,{useContext, useState, createContext} from 'react';
import {Button} from 'antd';
import '../../App.css';
 
const CountContext = createContext();
 
const TestContext = () =>{
    const [count, setCount] = useState(0);
    console.log(CountContext);
    console.log(useContext(CountContext));
    return(
      <div>
          <p>父组件点击次数:{count}</p>
          <Button type={"primary"} onClick={()=>setCount(count+1)}>点击+1</Button>
          <CountContext.Provider value={count}>
            <Counter/>
          </CountContext.Provider>
      </div>
  )
};
 
const Counter = () => {
    const count = useContext(CountContext);
    console.log(CountContext);
    // console.log(count);
    // console.log(useContext(CountContext));
    return (
        <div>
            <p>子组件获得的点击数量:{count}</p>
        </div>
    );
};
  1. useReducer(): 处理复杂的状态,计算一个新的state. setState钩子的底层实现用到了它
const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

const Counter = () => {
  const initialState = {count: 0}
  const [state, dispatch] = useReducer(reducer, initialState);
  
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </>
  );
}

小程序相关

微信登录
image.png

首先在前端调用login()方法获取 code
拿到 code后请求我们自己的后端,登录操作由我们自己的后端完成。
后端接收到 code ,携带 code 、appid 和 appSecret后向微信的服务器发送请求,索取该用户的openid。
返回 openid 后,与我们的数据库进行对比,寻找绑定该微信的用户,进而实现自定义登录功能。

直接登录获取openId

uni.login({
    provider: 'weixin',
    onlyAuthorize:true,
    success: function(loginRes) {
        uni.request({
            url: "http://127.0.0.1/login/wechat/" + loginRes.code, //后台地址,将code传递给后端
            success: (res) => {
                // 处理登录成功
            }
        })
    }
});
uni-app 中的跳转方式
生命周期
image.png

执行过程
应⽤的⽣命周期执行过程:

⻚⾯⽣命周期的执行过程:

微信支付

打开某小程序,点击直接下单

以下是uni-app 调起微信支付的核心代码

e70dcaf81b37ee43a7e926e8d924864.png
上一篇下一篇

猜你喜欢

热点阅读