前端面试题复习

一、js

1、promise的理解

 一种异步解决方案,用来解决回调地狱(也叫回调金字塔)

其实是一个构造函数,自己身上有all、reject、resolve这几个方法,原型上有then、catch等方法。

 回调函数中嵌套回调函数的情况就叫做回调地狱。可读性差,不好维护

asyncFunc1(function(result1) {
  asyncFunc2(result1, function(result2) {
    asyncFunc3(result2, function(result3) {
      // ...
    });
  });
});


 

1、对象的状态不受外界影响

Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。

2、一旦状态改变,就不会再变,任何时候都可以得到这个结果。

如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。

function promiseClick() {
    let p = new Promise(function (resolve, reject) {
        setTimeout(function () {
            var num = Math.ceil(Math.random() * 20); //生成1-10的随机数
            console.log('随机数生成的值:', num)
            if (num <= 10) {
                resolve(num);
            }
            else {
                reject('数字太于10了即将执行失败回调');
            }
        }, 2000);
    })
    return p
}

promiseClick().then(
    function (data) {
        console.log('resolved成功回调');
        console.log('成功回调接受的值:', data);
    }
)
    .catch(function (reason, data) {
        console.log('catch到rejected失败回调');
        console.log('catch失败执行回调抛出失败原因:', reason);
    });



 

2、数组乱序怎么实现

function shuffleArray(array) {
    /**sort方法,根据返回值判断调整顺序,没有返回值则按照Unicode排序*/
    //返回值有负值、零和正值,负则a在b前,零则不变,正则a在b后
    //注意:sort()方法默认将数组元素转换为字符串进行比较,如不满足则需自己定义比较规则
    array.sort(() => Math.random() - 0.5);
    return array;
}


function shuffleArray(array) {
    for (let i = array.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [array[i], array[j]] = [array[j], array[i]];
    }
    return array;
}


const shuffledArray = _.shuffle(array);


 

3、深拷贝、浅拷贝、深对比

  1. 什么叫做深拷贝、浅拷贝、深对比?
  1. 分别如何实现
// 1 使用递归:遍历原始对象或数组的每个属性或元素,对于对象属性,递归调用深拷贝函数;
// 对于数组元素,递归调用深拷贝函数并复制到新数组中。

// 2 使用 JSON 序列化和反序列化:使用 JSON.stringify() 将原始对象或数组转换为字符串,
// 然后使用 JSON.parse() 将字符串转换为新的对象或数组。
function deepClone(obj) {
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }

    let clone = Array.isArray(obj) ? [] : {};

    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            clone[key] = deepClone(obj[key]);
        }
    }

    return clone;
}


//深拷贝
let example1 = 12;
let example2 = example1;
example2 = 22;

//浅拷贝
let exampleObj1 = { name:"nihao" ,age: 18 };
let exampleObj2 = exampleObj1;
exampleObj2.name = "cheng-xu-yuan";

console.log(example1, example2);
console.log(exampleObj1, exampleObj2);

//result:
//12 22
//{name: 'cheng-xu-yuan', age: 18},{name: 'cheng-xu-yuan', age: 18}



// 1 使用递归:遍历两个对象或数组的每个属性或元素,递归比较它们的值是否相等。
// 2 使用库函数:许多 JavaScript 库(如 lodash、underscore)提供了深对比的函数,
//   可以直接调用这些函数进行深对比。例如lodash的_.isEqual(a, b);
function deepEqual(obj1, obj2) {
    if (obj1 === obj2) {
        return true;
    }

    if (typeof obj1 !== 'object' || typeof obj2 !== 'object' || obj1 === null || obj2 === null) {
        return false;
    }

    let keys1 = Object.keys(obj1);
    let keys2 = Object.keys(obj2);

    if (keys1.length !== keys2.length) {
        return false;
    }

    for (let key of keys1) {
        if (!obj2.hasOwnProperty(key) || !deepEqual(obj1[key], obj2[key])) {
            return false;
        }
    }

    return true;
}

  1. 对比三者的区别

4、数组怎么去重

const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = [...new Set(array)];
console.log(uniqueArray); // [1, 2, 3, 4, 5]


const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray2 = array.filter((value, index, self) => {
    //indexOf包含指定值返回该值的第一个索引,否则返回-1
    return self.indexOf(value) === index;
});
console.log(uniqueArray2); // [1, 2, 3, 4, 5]


// array.reduce(function(prev, cur, index, arr), init)

// prev (上一次调用回调返回的值,或者是提供的初始值(initialValue))
// cur (数组中当前被处理的元素)
// index (当前元素在数组中的索引)
// arr (调用的数组)
// init (传递给函数的初始值)

//使用 reduce:可以使用数组的 reduce 方法来构建一个新数组,只添加不重复的元素
const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray3 = array.reduce((accumulator, currentValue) => {
    if (!accumulator.includes(currentValue)) {
        accumulator.push(currentValue);
    }
    return accumulator;
}, []);
console.log(uniqueArray3); // [1, 2, 3, 4, 5]


 

5、懒加载

什么叫做懒加载?

懒加载的概念是指在页面初始化时只加载可视区域内的内容,当用户滚动页面时再加载其他内容。这样可以减少页面的初始加载时间,提高用户体验。

/**页面初始加载时只加载了占位图placeholder.jpg,而不会加载所有的图片,
从而减少了页面的加载时间和带宽消耗。当用户滚动页面时,再加载可视区域内的图片*/
<!DOCTYPE html>
<html>
<head>
  <title>Lazy Loading Example</title>
  <style>
    .image {
      width: 300px;
      height: 200px;
      background-color: #ccc;
      margin-bottom: 20px;
    }
  </style>
</head>
<body>
  <h1>Lazy Loading Example</h1>

  <div class="image">
    <img src="placeholder.jpg" data-src="image1.jpg" alt="Image 1">
  </div>

  <div class="image">
    <img src="placeholder.jpg" data-src="image2.jpg" alt="Image 2">
  </div>

  <div class="image">
    <img src="placeholder.jpg" data-src="image3.jpg" alt="Image 3">
  </div>

  <script>
    function lazyLoad() {
      const images = document.querySelectorAll('img[data-src]');

      images.forEach((image) => {
        if (image.getBoundingClientRect().top < window.innerHeight) {
          image.src = image.dataset.src;
          image.removeAttribute('data-src');
        }
      });
    }

    window.addEventListener('scroll', lazyLoad);
    window.addEventListener('resize', lazyLoad);

    lazyLoad();
  </script>
</body>
</html>



function isInViewPortOfOne(element) {
    // 获取可视窗口的高度。兼容所有浏览器
    const screenHeight = window.innerHeight || document.documentElement.clientHeight
        || document.body.clientHeight;
    // 获取滚动条滚动的高度
    const scrollTop = document.documentElement.scrollTop;
    // 获取元素偏移的高度。就是距离可视窗口的偏移量。
    const offsetTop = element.offsetTop;
    // 加100是为了提前加载
    return offsetTop - scrollTop <= screenHeight + 100;
}


 

6、防抖与节流

  1. 什么叫防抖?

 防抖相当于回城,每次被打断,就需要重新执行回城的操作,只有真正不受干扰时才能完成回城;

//事件A每t时间执行一次,中途再次发生A,则从打断时刻重新计时,t后执行
function debounce(func, delay) {
  let timer = null
  return function (...args) {
    // 事件再次触发就清除定时器,打断函数执行
    clearTimeout(timer)
    timer = setTimeout(() => {
      func.apply(this, args)
    }, delay)
  }
}


 

  1. 什么叫节流?

节流相当于技能冷却,在施放了一次技能操作后,只有等到规定时间后才能施放下一次技能,这期间就是键盘或鼠标按烂了也无法施放技能,即施放技能的频率是定死的

//一段周期内的重复事件只执行1次,周期完后再触发才会重新计时
function throttle(func, delay) {
    // 设置节流阀
    let timer = null
    return function (...args) {
        // 只要定时器在执行就关闭节流阀,退出函数执行
        if (timer) return
        timer = setTimeout(() => {
            func.apply(this, args)
            // 等定时时间结束就再次清空定时器,打开节流阀
            timer = null
        }, delay)

    }
}


 

  1. 他们的应用场景有哪些?

防抖(debounce)1.search搜索时,用户在不断输入值时,用防抖来节约请求资源。

节流(throttle)1.鼠标不断点击触发,mousedown(单位时间内只触发一次)2.监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断

react中常用的还是hooks来写防抖和节流,可参考:

typescript - ⑤react-ahooks源码分析之useDebounceFn和useDebounce - 个人文章 - SegmentFault 思否

7、Event Loop事件轮询机制

事件循环(Event Loop)是JavaScript中处理异步操作的机制。它负责管理和调度任务队列,确保任务按照正确的顺序执行。    

JavaScript是单线程的,意味着一次只能执行一个任务。但是在实际开发中,我们经常会遇到需要执行耗时的操作,比如网络请求、文件读写等。为了避免这些操作阻塞主线程的执行,JavaScript引入了事件循环机制。

事件循环由以下几个部分组成:

  1.  调用栈(Call Stack):用于存储当前正在执行的任务。当一个函数被调用时,会将其压入调用栈中,当函数执行完毕后,会将其从调用栈中弹出。
  2. 任务队列(Task Queue):用于存储待执行的任务。当一个异步任务完成后,会将其添加到任务队列中。
  3. 微任务队列(Microtask Queue):用于存储微任务(Promise、MutationObserver等)。微任务的优先级高于普通任务。

事件循环的执行过程如下:

  1.  当调用栈为空时,事件循环会从任务队列中取出一个任务,将其压入调用栈中执行。
  2. 执行任务时,如果遇到异步操作(比如定时器、网络请求等),会将其添加到任务队列中,而不是立即执行。
  3. 当调用栈为空时,事件循环会检查微任务队列,如果有微任务存在,则将其全部取出,按照先进先出的顺序执行。
  4. 当微任务执行完毕后,事件循环会再次从任务队列中取出一个任务,继续执行。

这个过程会一直重复,直到任务队列和微任务队列都为空,事件循环结束。

需要注意的是,任务队列中的任务按照添加的顺序执行,而微任务队列中的任务按照添加的顺序执行,不会被其他任务打断。

事件循环机制保证了JavaScript的异步操作能够按照正确的顺序执行,避免了阻塞主线程的问题。了解事件循环机制对于理解JavaScript中的异步编

程非常重要。

8、箭头函数与普通函数的本质区别

箭头函数和普通函数在语法上有一些差异,这些差异导致了它们在使用上的一些本质区别。下面是箭头函数和普通函数的本质区别:    

1. 语法:箭头函数使用箭头(=>)来定义函数,而普通函数使用function关键字来定义函数。

2. this的指向:箭头函数没有自己的this绑定,它会捕获所在上下文的this值,并且无法通过call()、apply()、bind()等方法来改变this的指向。普通函数的this指向调用它的对象。

3. arguments对象:箭头函数没有自己的arguments对象,它会捕获所在上下文的arguments对象。普通函数有自己的arguments对象,用于访问传入的参数。

4. 构造函数:箭头函数不能用作构造函数,不能使用new关键字来实例化。普通函数可以用作构造函数,可以使用new关键字来创建实例。

5. 原型:箭头函数没有原型属性。普通函数有原型属性,可以通过原型链来实现继承。

6. 返回值:箭头函数可以通过隐式返回来返回值,即直接使用表达式来返回值。普通函数可以使用return关键字来返回值。

需要注意的是,箭头函数和普通函数并不是完全等价的,它们有不同的适用场景和用法。箭头函数适用于简单的函数表达式,可以更简洁地定义函数。普通函数适用于需要使用this、arguments、原型等特性的情况。在选择使用箭头函数还是普通函数时,需要根据具体的需求和场景进行选择。

箭头函数和普通函数的本质区别已经在前面解答中提到了,下面将通过案例来进一步说明它们的区别。

 

1. 语法区别:

普通函数:
function add(a, b) {
  return a + b;
}

console.log(add(2, 3)); // 输出:5

箭头函数:
const add = (a, b) => a + b;

console.log(add(2, 3)); // 输出:5

2. this指向的区别:

普通函数:
const obj = {
  name: 'Alice',
  sayName: function() {
    console.log(this.name);
  }
};

obj.sayName(); // 输出:Alice

箭头函数:
const obj = {
  name: 'Alice',
  sayName: () => {
    console.log(this.name);
  }
};

obj.sayName(); // 输出:undefined


在普通函数中,this指向调用它的对象obj,所以可以正确地输出name属性。
而在箭头函数中,this指向的是定义时的上下文,即全局对象(在浏览器环境中是window对象),
所以无法访问到obj对象的name属性。

3. arguments对象的区别:

普通函数:
function sum() {
  let total = 0;
  for (let i = 0; i < arguments.length; i++) {
    total += arguments[i];
  }
  return total;
}

console.log(sum(1, 2, 3)); // 输出:6

箭头函数:
const sum = (...args) => {
  let total = 0;
  for (let i = 0; i < args.length; i++) {
    total += args[i];
  }
  return total;
}

console.log(sum(1, 2, 3)); // 输出:6




在普通函数中,可以通过arguments对象来获取传入的参数,并进行相应的操作。
而在箭头函数中,没有自己的arguments对象,可以使用剩余参数(rest parameters)来获取传入的参数。

总结:箭头函数和普通函数在语法和功能上有一些差异,
主要体现在this的指向、arguments对象以及构造函数等方面。

根据具体的需求和场景,选择合适的函数类型来使用。

 

9、ES6新特性

es6新特性_知乎

10Redux工作流

Redux是一个用于JavaScript应用程序的状态管理库。它遵循一种称为"Flux"的架构模式,其中数据的流动是单向的,这有助于更好地管理和维护应用程序的状态。    

Redux工作流可以被概括为以下几个步骤:

  1.  创建一个Redux Store:Store是应用程序的状态存储库,它包含了整个应用程序的状态。在Redux中,通过使用createStore函数来创建一个Store,并传入一个根Reducer函数。
  2. 定义Reducers:Reducers是纯函数,它们接收先前的状态和一个动作对象,并返回一个新的状态。Reducers负责处理应用程序中的各种操作,例如添加、删除或更新数据。可以通过combineReducers函数将多个Reducers组合在一起,以处理不同部分的应用程序状态。
  3.  Dispatch Actions:在Redux中,通过调用store.dispatch(action)来触发一个动作。动作是一个包含type属性的简单JavaScript对象,用于描述应用程序中的某个事件或操作。
  4. 处理Actions:当一个动作被分发到Redux Store时,Reducers将根据动作的类型来处理状态的更新。Reducers将先前的状态和动作对象作为参数,根据需要对状态进行修改,并返回一个新的状态。
  5. 更新Store:当Reducers返回一个新的状态时,Redux Store将更新应用程序的状态。所有订阅了Store的组件将会收到新的状态,并根据需要进行重新渲染。
  6. 访问Store中的状态:通过使用store.getState()函数,可以在应用程序中的任何地方访问Redux Store中的当前状态。这使得组件可以根据需要获取和使用状态。

这是一个基本的Redux工作流程。通过将应用程序的状态集中管理,并使用单向数据流,Redux提供了一种可预测和可维护的方式来处理应用程序的状态。它还提供了一些中间件,如Redux Thunk或Redux Saga,用于处理异步操作和副作用。

下面是一个简单的Redux案例,展示了如何使用Redux来管理一个计数器应用程序的状态:

首先,安装Redux和React-Redux库:

```
npm install redux react-redux
```

创建一个名为`counter.js`的文件,并编写以下代码:

```javascript
// 引入Redux库
import { createStore } from 'redux';

// 定义初始状态
const initialState = {
  count: 0
};

// 定义Reducer函数
const counterReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return {
        count: state.count + 1
      };
    case 'DECREMENT':
      return {
        count: state.count - 1
      };
    default:
      return state;
  }
};

// 创建Redux Store
const store = createStore(counterReducer);

export default store;
```

接下来,在应用程序的入口文件中引入Redux库和React-Redux库,并使用`Provider`组件将Redux Store提供给应用程序的组件:

```javascript
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './counter';
import App from './App';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
```

然后,在应用程序的组件中,可以使用`connect`函数从Redux Store中获取状态和分发动作:

```javascript
import React from 'react';
import { connect } from 'react-redux';

const App = ({ count, increment, decrement }) => {
  return (
    <div>
      <h1>Counter: {count}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
};

const mapStateToProps = state => {
  return {
    count: state.count
  };
};

const mapDispatchToProps = dispatch => {
  return {
    increment: () => dispatch({ type: 'INCREMENT' }),
    decrement: () => dispatch({ type: 'DECREMENT' })
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(App);
```

在这个例子中,`mapStateToProps`函数从Redux Store中获取`count`状态,并将其作为`props`传递给`App`组件。`mapDispatchToProps`函数将`increment`和`decrement`动作分发函数作为`props`传递给`App`组件。

最后,可以在应用程序的根组件中使用`count`状态和`increment`、`decrement`动作:

```javascript
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';

const App = () => {
  const count = useSelector(state => state.count);
  const dispatch = useDispatch();

  const increment = () => {
    dispatch({ type: 'INCREMENT' });
  };

  const decrement = () => {
    dispatch({ type: 'DECREMENT' });
  };

  return (
    <div>
      <h1>Counter: {count}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
};

export default App;
```

这是一个简单的Redux计数器应用程序的示例。通过Redux,可以将应用程序的状态集中管理,并通过动作来更新状态,从而实现可预测和可维护的应用程序。

 

二、HTML和样式

1、元素居中显示的方法

/**关于flex布局,可参考:https://zhuanlan.zhihu.com/p/25303493,真心写的很不错*/
.box{
  display: flex;
  alignItems: center;
  justifyContent: center;
}


.container {
  position: relative;
}

.centered-element {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  /**可换成margin-top:自身高度一半,margin-left:自身宽度一半*/
}


.container {
  display: table;
}

.centered-element {
  display: table-cell;
  vertical-align: middle;
  text-align: center;
}


 

2、了解过BFC吗

3、样式覆盖如何处理

在前端开发中,样式覆盖是一个常见的问题。当多个样式规则应用于同一个元素时,可能会出现样式冲突和覆盖的情况。为了解决这个问题,可以采取以下几种处理方式:    

1. 使用更具体的选择器:CSS选择器的优先级是根据其具体性来确定的。通过使用更具体的选择器,可以提高样式规则的优先级,从而覆盖其他样式规则。例如,使用ID选择器`#myElement`比使用类选择器`.myClass`的优先级更高。

2. 使用`!important`关键字:在样式规则中使用`!important`关键字可以将其优先级提升到最高。但是,滥用`!important`会导致样式难以维护,应该谨慎使用。

3. 调整样式规则的顺序:样式规则的顺序也会影响其优先级。后面出现的样式规则会覆盖前面的规则。可以通过调整样式规则的顺序来控制样式的覆盖效果。

4. 使用内联样式:将样式直接写在元素的`style`属性中,内联样式具有最高的优先级,可以覆盖外部样式表和内部样式表中的样式规则。

需要注意的是,样式覆盖可能会导致样式冲突和混乱,因此在开发过程中应尽量避免样式的冲突和覆盖。可以通过合理的命名规范、模块化的样式组织和使用CSS预处理器等方式来提高样式的可维护性和可扩展性。

4、全网置灰

html {
  filter: grayscale(100%);
}


document.addEventListener("DOMContentLoaded", function() {
  var elements = document.getElementsByTagName("*");
  for (var i = 0; i < elements.length; i++) {
    elements[i].style.filter = "grayscale(100%)";
  }
});



 

5、HTML中的Dom操作

//HTML中的DOM(Document Object Model)操作是指使用JavaScript或其他脚本语言来修改HTML文档
//的结构、样式和内容。
DOM提供了一组API,用于访问和操作HTML文档中的元素、属性和事件。

//常见的HTML DOM操作包括以下几个方面:

1. 获取元素:
//可以使用`document.getElementById()`、`document.getElementsByClassName()`、
//`document.getElementsByTagName()`等方法来获取HTML文档中的元素。

var element = document.getElementById("myElement");

2. 修改元素属性:
//可以使用`element.setAttribute()`、`element.getAttribute()`等方法来修改或获取元素的属性。

element.setAttribute("class", "newClass");
var value = element.getAttribute("data-id");


3. 修改元素内容:
//可以使用`element.innerHTML`或`element.textContent`属性来修改元素的内容。

element.innerHTML = "<p>New content</p>";
element.textContent = "New text";


4. 添加和删除元素:
//可以使用`element.appendChild()`、`element.removeChild()`等方法来添加或删除元素。

var newElement = document.createElement("div");
element.appendChild(newElement);

var childElement = element.removeChild(newElement);


5. 修改样式:
//可以使用`element.style`属性来修改元素的样式。

element.style.color = "red";
element.style.fontSize = "16px";


6. 添加事件监听器:
//可以使用`element.addEventListener()`方法来添加事件监听器。

element.addEventListener("click", function() {
  // 处理点击事件
});

// 通过这些DOM操作,可以动态地修改HTML文档的结构、样式和内容,从而实现交互性和动态性的网页效果。

// 需要注意的是,在进行DOM操作时,应遵循良好的性能和可维护性的原则,
// 避免频繁的操作和不必要的重绘。

 

三、网络

1、项目中出现跨域如何处理

在项目中,如果前端与后端的域名、端口或协议不同,就会发生跨域问题

有哪些解决方案    

  1. 代理服务器:在项目中设置一个代理服务器,将前端的请求转发到后端,并在代理服务器中设置相应的跨域头信息。这样可以绕过浏览器的同源策略限制。
  2. JSONP:JSONP 是一种通过动态创建 script 标签来实现跨域请求的方法。通过在前端页面中插入一个 script 标签,将后端返回的数据作为回调函数的参数传递给前端,从而实现跨域请求。
  3. CORS(跨域资源共享)是一种在服务器端设置响应头信息的方法,用于告诉浏览器允许跨域访问。 通过在后端设置 Access - Control - Allow - Origin、Access - Control - Allow - Methods 等头信息,可以允许特定的域名、方法等跨域访问。
  4. WebSocket:WebSocket 是一种基于 TCP 的协议,可以在浏览器和服务器之间建立持久连接。 由于 WebSocket 是在 HTTP 协议之上建立的,它可以绕过浏览器的同源策略限制,实现跨域通信。
  5. Nginx 反向代理:使用 Nginx 反向代理服务器,将前端的请求转发到后端,并在 Nginx 配置中设置相应的跨域头信息。这种方法可以在部署时进行配置,不需要修改项目代码。

2、HTTP协议的理解

主要参考:前端面试题精心整理(二)—HTTP协议_前端 http协议 笔试题_清颖~的博客-CSDN博客

3AxiosAjax的区别

Axios和Ajax是两种用于进行网络请求的技术,它们有一些区别:    

  1.  底层实现:Ajax是一种基于XMLHttpRequest对象的技术,通过原生的JavaScript代码发送HTTP请求和处理响应。而Axios是一个基于Promise的HTTP客户端库,它使用XMLHttpRequest或者浏览器的Fetch API作为底层实现。
  2. 语法和用法:Ajax使用原生的JavaScript代码来发送请求和处理响应,需要手动编写相应的代码来处理异步回调、错误处理等。而Axios提供了简洁的API和更好的语法糖,使得发送请求和处理响应更加方便和易用。
  3. 功能和特性:Axios提供了更多的功能和特性,例如请求和响应的拦截器、请求的取消、自动转换响应数据等。而Ajax相对较为简单,功能相对较少。
  4. 跨域请求:在浏览器环境下,Ajax发送跨域请求需要进行一些额外的配置,如设置请求头、使用JSONP等。而Axios默认支持跨域请求,并提供了更方便的配置选项。

总的来说,Axios相对于Ajax来说更加强大、易用和灵活,提供了更多的功能和特性,并且在处理跨域请求方面更加便捷。因此,使用Axios可以更好地处理网络请求,并提供更好的开发体验。

//下面是一个使用Axios和Ajax的案例,分别展示了发送GET请求和POST请求的示例代码:

//使用Axios发送GET请求的案例:

// 导入Axios库
import axios from 'axios';

// 发送GET请求
axios.get('https://api.example.com/data')
  .then(function (response) {
    // 请求成功,处理响应数据
    console.log(response.data);
  })
  .catch(function (error) {
    // 请求失败,处理错误信息
    console.log(error);
  });



//使用Ajax发送GET请求的案例:

// 发送GET请求
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4 && xhr.status === 200) {
    // 请求成功,处理响应数据
    console.log(JSON.parse(xhr.responseText));
  } else {
    // 请求失败,处理错误信息
    console.log(xhr.statusText);
  }
};
xhr.send();


//使用Axios发送POST请求的案例:


// 导入Axios库
import axios from 'axios';

// 发送POST请求
axios.post('https://api.example.com/data', { name: 'John', age: 25 })
  .then(function (response) {
    // 请求成功,处理响应数据
    console.log(response.data);
  })
  .catch(function (error) {
    // 请求失败,处理错误信息
    console.log(error);
  });


//使用Ajax发送POST请求的案例:

// 发送POST请求
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://api.example.com/data', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4 && xhr.status === 200) {
    // 请求成功,处理响应数据
    console.log(JSON.parse(xhr.responseText));
  } else {
    // 请求失败,处理错误信息
    console.log(xhr.statusText);
  }
};
xhr.send(JSON.stringify({ name: 'John', age: 25 }));

 

4、浏览器的静态资源缓存

览器缓存指的是浏览器将用户请求过的静态资源,存储到本地内存磁盘中,当浏览器再次访问时,就可以直接从本地加载,不用再次发起请求。

好处就是减少了服务器的负担,提高了网站的性能

一、强缓存    

如果缓存资源有效,则直接使用缓存资源,不必再向服务器发起请求。

设置方式:

Expires:服务器通过在响应头中添加 Expires 属性,来指定资源的过期时间。在过期时间以内,该资源可以被缓存使用,不必再向服务器发送请求,这个是HTTP 1.0的方式,因为用绝对时间可能存在误差导致缓存失败。

Cache-Control:因为Expires的一些缺点,在 HTTP 1.1 中提出了这个属性,它提供了对资源的缓存的更精确的控制。通过设置 max-age= XXX 来进行强缓存。

二、协商缓存    

强缓存失效后,浏览器先向服务器发送一个请求,如果资源没有发生修改,则返回一个 304 状态,让浏览器使用本地的缓存副本。如果资源发生了修改,则返回修改后的资源。

协商缓存可以通过下面两种方式来设置:

(1)Last-Modified & If-Modified-Since:服务器通过在响应头中添加 Last-Modified 属性来指出资源最后一次修改的时间,当浏览器下一次发起请求时,会在请求头中添加一个 If-Modified-Since 的属性,属性值为上一次资源返回时的 Last-Modified 的值。当请求发送到服务器后服务器会通过这个属性来和资源的最后一次的修改时间来进行比较,以此来判断资源是否做了修改。如果资源没有修改,那么返回 304 状态,让客户端使用本地的缓存。如果资源已经被修改了,则返回修改后的资源。使用这种方法有一个缺点,就是 Last-Modified 标注的最后修改时间只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,那么文件已将改变了但是 Last-Modified 却没有改变,这样会造成缓存命中的不准确。例如,设置Last-Modified: Wed, 01 Dec 2021 12:00:00 GMT

(2)Etag & If-None-Match:因为 Last-Modified 的这种可能发生的不准确性,http 中提供了另外一种方式,那就是 Etag 属性。服务器在返回资源的时候,在头信息中添加了 Etag 属性,这个属性是资源生成的唯一标识符,当资源发生改变的时候,这个值也会发生改变。在下一次资源请求时,浏览器会在请求头中添加一个 If-None-Match 属性,这个属性的值就是上次返回的资源的 Etag 的值。服务接收到请求后会根据这个值来和资源当前的 Etag 的值来进行比较,以此来判断资源是否发生改变,是否需要返回资源。通过这种方式,比 Last-Modified 的方式更加精确。例如,设置ETag: "abc123"

Etag 的优先级高于 Last-Modified。

5、DNS域名解析

DNS(Domain Name System,域名系统)是互联网中用于将域名解析为对应IP地址的系统。在互联网上,每个设备都有一个唯一的IP地址,而人们更习惯使用易记的域名来访问网站或服务。DNS的作用就是将用户输入的域名转换为对应的IP地址,使得设备能够正确地访问目标网站或服务。    

DNS域名解析的过程如下:

  1.  用户在浏览器中输入一个域名,比如www.example.com。
  2. 浏览器首先会查询本地DNS缓存,看是否已经缓存了该域名的解析结果。如果有,就直接返回解析结果,跳到第8步。
  3. 如果本地DNS缓存中没有该域名的解析结果,浏览器会向本地网络中的DNS服务器发送一个DNS解析请求。
  4. 本地DNS服务器也会首先查询自己的缓存,如果有该域名的解析结果,就返回给浏览器。
  5. 如果本地DNS服务器的缓存中没有该域名的解析结果,它会向根域名服务器发送一个请求。
  6. 根域名服务器是全球互联网中最高级别的DNS服务器,它并不直接返回域名解析结果,而是告诉本地DNS服务器,该域名对应的顶级域名服务器的地址。
  7. 本地DNS服务器再向顶级域名服务器发送请求。
  8. 顶级域名服务器会告诉本地DNS服务器,该域名对应的权威域名服务器的地址。
  9. 本地DNS服务器再向权威域名服务器发送请求。
  10. 权威域名服务器最终返回该域名对应的IP地址给本地DNS服务器。
  11. 本地DNS服务器将IP地址缓存起来,并将解析结果返回给浏览器。
  12. 浏览器拿到IP地址后,就可以向目标服务器发送请求,建立起连接,并显示网页内容。

整个DNS域名解析的过程可能涉及多个层次的DNS服务器,从根域名服务器到顶级域名服务器,再到权威域名服务器,最终返回IP地址给用户设备。这个过程通常是很快的,用户很少会察觉到域名解析的时间延迟。

6、sessionStorage、localStorage、cookie

sessionStorage、localStorage和cookie都是在浏览器中用于存储数据的机制,但它们有一些区别。    

  1. sessionStorage:sessionStorage是HTML5引入的一种存储机制,用于在浏览器会话期间(即用户在关闭浏览器之前)存储数据。它的特点是数据仅在当前会话中有效,关闭浏览器后数据会被清除。sessionStorage只能存储字符串类型的数据,通过setItem()和getItem()方法进行存取。

应用案例:在一个多页面的网站中,可以使用sessionStorage存储用户登录信息,以便在不同页面间共享登录状态。

  1. localStorage:localStorage也是HTML5引入的一种存储机制,用于在浏览器中长期保存数据,即使关闭浏览器后也不会被清除。localStorage的数据是永久性的,除非代码或用户手动删除。与sessionStorage相比,localStorage可以存储更大容量的数据,并且可以存储除了字符串外的其他数据类型,如数字、对象等。

应用案例:在一个待办事项应用中,可以使用localStorage存储用户的待办列表,以便用户关闭浏览器后再次打开时能够看到之前保存的数据。

  1. cookie:cookie是一种在浏览器和服务器之间传递的小型文本文件,用于存储特定网站的用户信息。它通常用于记录用户的登录状态、购物车内容等。cookie的数据会在每次HTTP请求中自动发送到服务器,因此可以用于跟踪用户行为。cookie的大小有限制,一般不能超过4KB。

应用案例:在一个电子商务网站中,可以使用cookie存储用户的购物车信息,以便用户在不同页面间保持购物车的内容。

总结:

根据具体需求,选择合适的存储机制来存储数据。

1. sessionStorage案例代码:

// 存储数据到sessionStorage
sessionStorage.setItem('username', 'John');
sessionStorage.setItem('age', '25');

// 从sessionStorage中获取数据
var username = sessionStorage.getItem('username');
var age = sessionStorage.getItem('age');

console.log(username); // 输出:John
console.log(age); // 输出:25

// 删除sessionStorage中的数据
sessionStorage.removeItem('age');
```

2. localStorage案例代码:

```javascript
// 存储数据到localStorage
localStorage.setItem('username', 'John');
localStorage.setItem('age', '25');

// 从localStorage中获取数据
var username = localStorage.getItem('username');
var age = localStorage.getItem('age');

console.log(username); // 输出:John
console.log(age); // 输出:25

// 删除localStorage中的数据
localStorage.removeItem('age');
```

3. cookie案例代码:

```javascript
// 设置cookie
document.cookie = 'username=John; expires=Fri, 31 Dec 2021 23:59:59 GMT; path=/';

// 获取cookie
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
  var cookie = cookies[i].trim();
  if (cookie.startsWith('username=')) {
    var username = cookie.substring('username='.length);
    console.log(username); // 输出:John
    break;
  }
}

// 删除cookie
document.cookie = 'username=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
```

 

四、react

1、类组件和函数组件有什么区别

 函数组件也可以接收props,但是需要在函数的参数中显式地声明。

类组件和函数组件,也就是无状态组件和有状态组件

对比下来,要做复杂边界值处理就用class,要便于测试写法简洁就用函数组件

2、组件间的通信

父组件通过props将数据传递给子组件子组件可以通过回调函数将数据传递给父组件。

// 父组件
import React from 'react';
import ChildComponent from './ChildComponent';

function ParentComponent() {
  const data = "Hello, child component!";
  const handleData = (data) => {
    console.log(data);
  }
  return (
    <div>
      <ChildComponent data={data}  handleClick={handleData} />
    </div>
  );
}

export default ParentComponent;

// 子组件
import React from 'react';

function ChildComponent(props) {
  const handleClick = () => {
    props.sendData("Hello, parent component!");
  }
  return (
    <div>
      <p>{props.data}</p>
      <button onClick={handleClick}>传递数据给父组件</button>
    </div>
  );
}

export default ChildComponent;


 

// 可以使用共享的父组件状态或使用状态管理库(如Redux)来实现兄弟组件之间的通信。
// 共享的父组件状态
import React, { useState } from 'react';
import SiblingComponent1 from './SiblingComponent1';
import SiblingComponent2 from './SiblingComponent2';

function ParentComponent() {
  const [data, setData] = useState("");
 
  return (
    <div>
      <SiblingComponent1 setData={setData} />
      <SiblingComponent2 data={data} />
    </div>
  );
}

export default ParentComponent;

// 兄弟组件1
import React from 'react';

function SiblingComponent1(props) {
  const handleClick = () => {
    props.setData("Hello, sibling component 2!");
  }
 
  return (
    <div>
      <button onClick={handleClick}>传递数据给兄弟组件2</button>
    </div>
  );
}

export default SiblingComponent1;

// 兄弟组件2
import React from 'react';

function SiblingComponent2(props) {
  return (
    <div>
      <p>{props.data}</p>
    </div>
  );
}

export default SiblingComponent2;

 

//通过 Context,可以在组件树中直接传递数据,而不需要通过 props 逐层传递。
//这在跨多层级的组件通信中非常有用

//1、首先,创建一个Context对象,可以在一个单独的文件中创建:

// MyContext.js
import React from 'react';

const MyContext = React.createContext();

export default MyContext;


//2、然后,创建一个提供Context值的组件:

// ParentComponent.js
import React from 'react';
import MyContext from './MyContext';

const ParentComponent = () => {
  const contextValue = 'Hello from ParentComponent';

  return (
    <MyContext.Provider value={contextValue}>
      <ChildComponent />
    </MyContext.Provider>
  );
};

export default ParentComponent;


// 在这个例子中,`ParentComponent`提供了一个名为`MyContext`的Context,
// 并将`contextValue`作为值传递给它的`Provider`组件。
// `ChildComponent`组件将能够访问到这个Context的值。

//3、最后,创建一个使用Context值的子组件:


// ChildComponent.js
import React from 'react';
import MyContext from './MyContext';

const ChildComponent = () => {
  return (
    <MyContext.Consumer>
      {contextValue => <p>{contextValue}</p>}
    </MyContext.Consumer>
  );
};

export default ChildComponent;

// 在这个例子中,`ChildComponent`通过`MyContext.Consumer`组件来访问`MyContext`的值,
// 并将其渲染为一个`<p>`元素。

// 现在,当`ParentComponent`渲染时,它将提供一个值给`MyContext`,
// 然后`ChildComponent`将能够访问到这个值并将其渲染出来。


 

//下面是一个使用React钩子函数实现组件间通信的案例:


// ParentComponent.js
import React, { useState } from 'react';
import ChildComponent from './ChildComponent';

const ParentComponent = () => {
  const [message, setMessage] = useState('');

  const handleMessageChange = (newMessage) => {
    setMessage(newMessage);
  };

  return (
    <div>
      <ChildComponent onMessageChange={handleMessageChange} />
      <p>Message from child: {message}</p>
    </div>
  );
};

export default ParentComponent;

// 在这个例子中,`ParentComponent`使用`useState`钩子函数来创建一个状态`message`,
// 并提供一个`handleMessageChange`函数来更新该状态。
// 然后,将`handleMessageChange`函数作为`onMessageChange`属性传递给`ChildComponent`。


// ChildComponent.js
import React, { useState } from 'react';

const ChildComponent = ({ onMessageChange }) => {
  const [inputValue, setInputValue] = useState('');

  const handleChange = (e) => {
    setInputValue(e.target.value);
    onMessageChange(e.target.value);
  };

  return (
    <div>
      <input type="text" value={inputValue} onChange={handleChange} />
    </div>
  );
};

export default ChildComponent;

// 在`ChildComponent`中,我们使用`useState`钩子函数来创建一个状态`inputValue`,
// 并提供一个`handleChange`函数来更新该状态,
// 并调用`onMessageChange`函数将新的输入值传递给`ParentComponent`。

// 这样,当`ChildComponent`中的输入框的值发生变化时,
// 它会调用`handleChange`函数来更新`inputValue`状态,
// 并将新的值传递给`ParentComponent`的`handleMessageChange`函数。
// 然后,`ParentComponent`会更新`message`状态,并将其显示在页面上。

 

//`forwardRef`是React中一个高级功能,它允许你将`ref`从父组件传递给子组件。

// ParentComponent.js
import React, { useRef } from 'react';
import ChildComponent from './ChildComponent';

const ParentComponent = () => {
  const childRef = useRef(null);

  const handleClick = () => {
    childRef.current.focus();
  };

  return (
    <div>
      <button onClick={handleClick}>Focus Child</button>
      <ChildComponent ref={childRef} />
    </div>
  );
};

export default ParentComponent;

// 在这个例子中,`ParentComponent`创建了一个`ref`,并将其传递给`ChildComponent`作为`ref`属性。
// 然后,当按钮被点击时,`ParentComponent`调用`childRef.current.focus()`来聚焦`ChildComponent`

// ChildComponent.js
import React, { forwardRef } from 'react';

const ChildComponent = forwardRef((props, ref) => {
  return (
    <input type="text" ref={ref} />
  );
});

export default ChildComponent;

// 在`ChildComponent`中,我们使用`forwardRef`函数来接收`ref`参数并将其传递给`<input>`元素。
// 这样,当`ParentComponent`中的按钮被点击时,`ChildComponent`中的`<input>`元素将被聚焦。

// 注意:使用`forwardRef`时,你需要将其作为父组件的导出组件。
//   这样,父组件才能正确地将`ref`传递给子组件。

 

参考:React forwardRef相关总结_云稽的博客-CSDN博客

3、React Hook的副作用

1. `useState`:使用`useState`钩子函数来管理组件的状态。
  它可以在函数组件中创建和更新状态,并且每次更新状态都会重新渲染组件。

2. `useEffect`:使用`useEffect`钩子函数来处理副作用操作。
 
副作用操作包括订阅事件、请求数据、修改DOM等。
    `useEffect`接收一个回调函数作为参数,这个回调函数会在每次渲染完成后执行。
    你可以在回调函数中执行副作用操作,并且可以通过返回一个清除函数来清理副作用。

3. `useRef`:使用`useRef`钩子函数来创建一个可变的引用。
  它可以在组件的多次渲染之间保持引用的一致性。
  `useRef`返回一个可变的`ref`对象,你可以将其赋值给组件的元素或其他对象,
  并且可以在整个组件的生命周期中保持引用的一致性。


4. `useContext`:使用`useContext`钩子函数来访问React的上下文。
  上下文可以用于在组件树中共享数据,`useContext`允许你在函数组件中访问上下文的值。

5. `useReducer`:使用`useReducer`钩子函数来管理复杂的状态逻辑。它类似于`useState`,
  但是可以处理更复杂的状态更新逻辑,并且可以与`useContext`一起使用来实现全局状态管理。

 

4、React 优化性能

//`useMemo`和`useCallback`是React中用于性能优化的两个钩子函数。

//`useMemo`用于缓存计算结果,避免重复计算。
//它接收一个依赖数组和一个计算函数,并返回计算函数的结果。
//在组件重新渲染时,`useMemo`会检查依赖数组中的值是否发生变化,
//如果没有变化,则会直接返回上一次的计算结果,避免重复计算。



import React, { useMemo } from 'react';

const Component = ({ a, b }) => {
  const result = useMemo(() => {
    // 计算逻辑
    return a + b;
  }, [a, b]);

  return <div>{result}</div>;
};

//在这个例子中,`result`是通过计算`a`和`b`的和得到的。
//使用`useMemo`可以确保只有当`a`或`b`发生变化时,才会重新计算`result`的值。



 

//`useCallback`用于缓存函数,避免不必要的函数重新创建。
//它接收一个依赖数组和一个函数,并返回一个缓存的函数。

//在组件重新渲染时,`useCallback`会检查依赖数组中的值是否发生变化,
//如果没有变化,则会直接返回上一次的缓存函数,避免不必要的函数重新创建。



import React, { useCallback } from 'react';

const Component = ({ onClick }) => {
  const handleClick = useCallback(() => {
    // 处理点击事件
    onClick();
  }, [onClick]);

  return <button onClick={handleClick}>Click</button>;
};

//在这个例子中,`handleClick`是一个处理点击事件的函数。

//使用`useCallback`可以确保只有当`onClick`发生变化时,才会重新创建`handleClick`函数。

//通过使用`useMemo`和`useCallback`,可以避免不必要的计算和函数创建,提高组件的性能。
//但是需要注意的是,过度使用这两个钩子函数可能会导致代码变得复杂和难以理解,
//需要根据具体情况进行权衡和选择。

 

5、React Portal 的理解与使用

// React Portal是React提供的一种机制,用于将组件的内容渲染到DOM树中的其他位置,
// 而不是直接渲染到组件所在的位置。

// 通常情况下,React组件的内容会被渲染到组件所在的位置。

// 但是有时候,我们可能需要将组件的内容渲染到DOM树中的其他位置,
// 比如在一个模态框中渲染一个组件,或者在一个弹出菜单中渲染一个组件。

// 这时候,就可以使用React Portal。

// 使用React Portal非常简单,只需要使用`ReactDOM.createPortal`方法
// 将组件的内容渲染到指定的DOM节点中即可。

import React from 'react';
import ReactDOM from 'react-dom';

const Modal = ({ isOpen, onClose }) => {
  if (!isOpen) {
    return null;
  }

  return ReactDOM.createPortal(
    <div className="modal">
      <div className="modal-content">
        <button onClick={onClose}>Close</button>
        <h1>Modal Content</h1>
      </div>
    </div>,
    document.getElementById('modal-root')
  );
};

const App = () => {
  const [isOpen, setIsOpen] = React.useState(false);

  const handleOpenModal = () => {
    setIsOpen(true);
  };

  const handleCloseModal = () => {
    setIsOpen(false);
  };

  return (
    <div>
      <button onClick={handleOpenModal}>Open Modal</button>
      <Modal isOpen={isOpen} onClose={handleCloseModal} />
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById('root'));


// 在这个例子中,Modal组件使用React Portal将自己的内容渲染到id为`modal-root`的DOM节点中。
// 当点击"Open Modal"按钮时,Modal组件会显示,并且其内容会渲染到`modal-root`节点中。
// 点击"Close"按钮时,Modal组件会关闭,并且其内容会从`modal-root`节点中移除。


// 需要注意的是,React Portal可以将组件的内容渲染到任意的DOM节点中,包括根节点之外的节点。
// 但是需要确保目标节点已经在DOM中存在,否则会报错。

 

6、自定义hook

普通函数相比,自定义hook有什么不同?

自定义hook只能在函数组件中使用,名称必须以use开头,主要用于组件之间共享逻辑和状态的场景,例如表单处理、数据获取等。

import { useState } from "react";

function useForm(initialState) {
  const [values, setValues] = useState(initialState);
  const [errors, setErrors] = useState({});

  const handleChange = (e) => {
    setValues((prevValues) => ({
      ...prevValues,
      [e.target.name]: e.target.value
    }));
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    // 进行表单验证逻辑,根据需要设置errors状态
    if (values.username === "") {
      setErrors((prevErrors) => ({
        ...prevErrors,
        username: "用户名不能为空"
      }));
    } else {
      setErrors({});
      // 提交表单
      // ...
    }
  };

  return {
    values,
    errors,
    handleChange,
    handleSubmit
  };
}

function MyForm() {
  const { values, errors, handleChange, handleSubmit } = useForm({
    username: "",
    password: ""
  });

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        name="username"
        value={values.username}
        onChange={handleChange}
      />
      {errors.username && <span>{errors.username}</span>}
      <input
        type="password"
        name="password"
        value={values.password}
        onChange={handleChange}
      />
      <button type="submit">提交</button>
    </form>
  );
}
// 在上面的例子中,我们定义了一个名为useForm的自定义Hook,
// 它接受一个初始状态initialState作为参数,
// 并返回一个包含表单值、错误信息、值变更和提交处理函数的对象。

// 在MyForm组件中,我们使用useForm自定义Hook来处理表单的逻辑和状态,
// 包括输入值的变更和表单的提交处理。
// 这样可以将表单的逻辑和状态封装在自定义Hook中,使得代码更加清晰和可复用。

 

五、深度问题

1、为团队做过什么前端技术优化

2、封装过哪些通用组件

3、SSR服务端渲染的理解

SSR(Server-Side Rendering,服务端渲染)是一种将网页内容在服务器端生成并发送到客户端的技术。传统的前端渲染方式是在客户端使用浏览器执行JavaScript代码来生成页面内容,而SSR则是在服务器端生成页面内容,然后将生成的HTML发送给客户端。    

SSR的工作流程如下:

  1. 客户端发送请求到服务器。
  2. 服务器接收请求,并执行相应的处理逻辑。
  3. 服务器获取数据,生成页面的HTML结构。
  4. 服务器将生成的HTML结构发送给客户端。
  5. 客户端接收到HTML结构后,进行解析和渲染,显示出完整的页面内容。

SSR的优势包括:

  1. 更好的SEO:由于搜索引擎爬虫可以直接获取到完整的HTML内容,能够更好地理解和索引页面的内容,提升网页的搜索排名。
  2. 更快的首次加载速度:SSR可以在服务器端生成完整的HTML结构,减少客户端的渲染时间,从而提供更快的首次加载速度。
  3. 更好的用户体验:由于SSR能够更快地显示出页面内容,用户可以更快地看到页面的内容,减少等待时间,提升用户体验。

然而,SSR也存在一些限制和挑战:

  1. 服务器压力增加:由于服务器需要执行渲染逻辑并生成HTML结构,会增加服务器的负载压力。
  2. 开发复杂性增加:SSR需要在服务器端和客户端编写和维护不同的代码逻辑,增加了开发的复杂性。
  3. 不适用于动态交互较多的页面:对于有大量动态交互的页面,SSR的优势可能不明显,因为客户端仍然需要执行JavaScript代码来处理交互逻辑。

总的来说,SSR是一种可以提升网页性能和用户体验的技术,尤其适用于需要更好SEO和更快首次加载速度的场景。但它也需要权衡服务器压力和开发复杂性,并且不适用于所有类型的页面。

4、webpack打包优化做过什么吗

Webpack是一个强大的打包工具,可以对项目进行优化以提高打包性能和减小打包文件的大小。以下是一些Webpack打包优化的技巧:    

  1.  使用生产模式:在打包时,使用webpack --mode production命令来启用生产模式。这将自动启用一些优化选项,如代码压缩和去除未使用的代码。
  2. 代码拆分:使用Webpack的代码拆分功能,将代码拆分为多个块。这样可以减小初始加载的文件大小,并在需要时按需加载其他块。
  3. 按需加载:使用Webpack的动态导入功能(如import()require.ensure())来按需加载模块。这样可以减小初始加载的文件大小,并在需要时延迟加载其他模块。
  4. 使用Webpack插件:使用一些优化插件来帮助减小打包文件的大小。例如,使用UglifyJsPlugin插件来压缩JavaScript代码,使用OptimizeCSSAssetsPlugin插件来压缩CSS代码。
  5. 配置缓存:使用Webpack的缓存功能,可以将中间文件缓存起来,以加快重新打包的速度。可以使用cache-loader插件来实现缓存功能。
  6.  使用Tree Shaking:通过使用ES6模块语法和Webpack的Tree Shaking功能,可以消除未使用的代码,减小打包文件的大小。确保在Webpack配置中启用optimization.usedExports选项,并使用ES6模块语法导入和导出模块。
  7. 使用代码分割和懒加载:将应用程序拆分为多个模块,并使用Webpack的动态导入功能按需加载模块。这样可以减小初始加载的文件大小,并在需要时延迟加载其他模块。
  8. 使用Webpack Bundle Analyzer:使用Webpack Bundle Analyzer插件来分析打包后的文件,找出哪些模块占用了较大的空间,并做出相应的优化。
  9. 压缩图片和字体:使用Webpack的url-loaderfile-loader插件来压缩和优化图片和字体文件。可以配置这些插件来自动将较小的文件转换为Base64编码,以减少HTTP请求。
  10. 使用缓存和CDN:使用Webpack的缓存功能和CDN来加速文件的加载。将经常变动的文件(如代码文件)和不变的文件(如依赖库)分别缓存和使用CDN加载,以减少文件的加载时间。

这些是一些常见的Webpack打包优化技巧,可以根据项目的具体情况选择适合的优化方法。通过优化Webpack的配置和使用一些优化插件,可以显著提高打包性能和减小打包文件的大小。

5、前端工程化做过什么贡献

前端工程化是指将前端开发过程中的各种工具、技术和方法整合起来,以提高开发效率、代码质量和团队协作能力的一种开发方式。它包括以下几个方面:    

  1.  构建工具:使用构建工具(如Webpack、Gulp、Grunt等)来自动化构建过程,包括代码编译、压缩、合并、打包等,减少手动操作,提高开发效率。
  2. 模块化开发:使用模块化开发方式(如CommonJS、ES6模块化)来组织代码,将代码拆分为独立的模块,提高代码的可维护性和复用性。
  3. 自动化测试:使用自动化测试工具(如Jest、Mocha、Karma等)来编写和运行自动化测试用例,保证代码的质量和稳定性。
  4. 代码规范和Lint工具:使用代码规范和Lint工具(如ESLint、Prettier等)来强制统一的代码风格和规范,提高代码质量和可读性。
  5. 版本控制和协作:使用版本控制工具(如Git)来管理代码的版本和协作开发,实现多人协作和代码的版本控制。
  6. 自动化部署:使用自动化部署工具(如Jenkins、Travis CI等)来自动化部署代码到服务器,减少手动操作,提高部署效率。
  7. 性能优化:使用性能优化工具和技术(如CDN、缓存优化、代码压缩等)来提高网站的加载速度和性能。
  8. 文档和文档生成工具:使用文档生成工具(如JSDoc、Swagger等)来生成项目的文档,提高项目的可读性和可维护性。

通过前端工程化的方式,可以提高前端开发的效率和质量,减少重复劳动,提高团队协作能力,同时也能够更好地适应项目的变化和需求的变化。

6、微前端的理解

微前端是一种将前端应用拆分为更小、更独立的部分,以实现松耦合、独立开发和部署的架构模式。它的核心思想是将一个大型前端应用拆分为多个小型的子应用,每个子应用都可以独立开发、部署和运行,同时可以组合在一起形成一个完整的前端应用。    

微前端的主要特点包括:

  1.  模块化拆分:将前端应用拆分为多个独立的模块或子应用,每个模块可以由不同的团队独立开发和维护。
  2. 独立部署:每个子应用都可以独立部署和运行,可以使用不同的技术栈和框架,不受其他子应用的影响。
  3. 松耦合集成:通过定义接口和约定,不同的子应用可以相互通信和集成,实现共享数据和共享功能。
  4. 独立开发:不同的子应用可以由不同的团队独立开发,可以使用不同的开发流程和工具。
  5. 增量升级:可以对某个子应用进行独立的升级和迭代,而不影响其他子应用。

微前端的优势包括:

  1.  团队协作:不同的子应用可以由不同的团队独立开发和维护,提高团队协作的效率。
  2. 技术栈灵活性:每个子应用可以使用不同的技术栈和框架,可以选择最适合自己的技术栈。
  3. 可扩展性:可以根据需求增加或删除子应用,实现系统的动态扩展和升级。
  4. 性能优化:可以将不同的子应用部署在不同的服务器上,实现负载均衡和并行加载,提高应用的性能和响应速度。
  5. 故障隔离:当某个子应用出现故障时,不会影响整个系统的正常运行,只会影响到该子应用的功能。

微前端的实现方式有多种,包括基于iframe、Web Components、JavaScript模块加载器等。目前,一些开源框架和工具已经提供了对微前端的支持,如single-spa、qiankun等。

总的来说,微前端是一种将前端应用拆分为更小、更独立的部分,以实现松耦合、独立开发和部署的架构模式,可以提高团队协作效率、技术栈灵活性和系统的可扩展性。

7、白屏时间分析

白屏时间是指用户打开网页后,浏览器开始加载页面内容之前的时间段,也就是用户看到页面内容之前的空白时间。分析白屏时间可以帮助我们了解网页加载性能,并找到优化的方向。    

下面是一些常用的白屏时间分析方法和工具:

  1.  使用浏览器开发者工具:现代浏览器都提供了开发者工具,其中包括网络面板(Network Panel)。通过打开开发者工具,刷新页面并观察网络面板,可以看到每个资源的加载时间和顺序,从而分析白屏时间。
  2. 使用性能分析工具:性能分析工具可以帮助我们更详细地分析页面加载过程中的各个阶段。例如,Chrome浏览器的Performance面板可以记录页面加载过程中的各个事件和时间点,从而帮助我们分析白屏时间和其他性能指标。
  3. 使用页面加载时间检测工具:有一些在线工具可以帮助我们检测页面的加载时间和性能指标。例如,WebPageTest可以提供详细的页面加载时间报告,包括白屏时间、首次渲染时间等。
  4. 使用代码监测和分析工具:通过在网页代码中插入性能监测代码,可以记录页面加载过程中的各个时间点,并生成相应的性能报告。例如,使用Performance API或者第三方库(如Lighthouse、Boomerang等)可以帮助我们记录和分析页面加载时间。

在分析白屏时间时,我们可以关注以下几个关键点:

  •  DNS解析时间:浏览器需要通过DNS解析域名,获取服务器的IP地址。较长的DNS解析时间会延长白屏时间。
  •  TCP连接时间:浏览器需要与服务器建立TCP连接,较长的连接时间会延长白屏时间。
  •  首字节时间(TTFB):首字节时间是指浏览器发送请求后,服务器返回第一个字节的时间。较长的TTFB会延长白屏时间。
  •  HTML下载和解析时间:浏览器需要下载HTML文件并解析其中的内容,较大的HTML文件会增加下载和解析时间。
  •  CSS和JavaScript加载时间:浏览器需要下载和解析CSS和JavaScript文件,较大的文件会增加加载时间。

通过分析这些关键点,我们可以找到白屏时间的瓶颈,并采取相应的优化措施,例如优化DNS解析、减少HTTP请求、压缩和合并文件等。

总的来说,白屏时间分析可以帮助我们了解页面加载性能,并找到优化的方向。通过使用浏览器开发者工具、性能分析工具、页面加载时间检测工具和代码监测和分析工具,我们可以获得详细的页面加载时间报告,并分析白屏时间的瓶颈,从而进行相应的优化。

8、登录功能怎么做(Cookie问题)

实现登录功能的方式有很多,下面是一个常见的登录功能实现步骤:    

  1.  创建用户表:在数据库中创建一个用户表,用于存储用户的相关信息,如用户名、密码、邮箱等。
  2. 创建登录页面:创建一个登录页面,包含用户名和密码的输入框,以及登录按钮。
  3. 后端验证:在后端服务器中,接收前端传来的用户名和密码,在用户表中查询匹配的用户记录,并进行密码验证。可以使用加密算法对密码进行加密存储,以提高安全性。
  4. 会话管理:如果用户名和密码验证通过,服务器可以创建一个会话(session),并将用户的登录状态保存在会话中。可以使用会话ID来标识用户的登录状态。
  5. 前端跳转:在登录成功后,后端服务器可以返回一个登录成功的响应,前端页面可以根据响应进行相应的跳转,例如跳转到用户的个人主页。
  6. 登录状态验证:在后续的页面访问中,可以通过检查会话状态来验证用户的登录状态。如果用户未登录或会话过期,可以跳转到登录页面。

需要注意的是,为了提高安全性,还可以采取以下措施:

总结起来,实现登录功能需要创建用户表、创建登录页面、后端验证、会话管理和前端跳转等步骤。为了提高安全性,还可以采取一些额外的安全措施。

9、大文件上传如何解决

处理大文件上传时,常见的解决方案有以下几种:    

  1.  分片上传:将大文件分割成多个小块(通常为固定大小的块),然后逐个上传这些小块。可以使用前端的File API将文件切割成块,并使用后端的接口逐个上传这些块。在后端,可以将这些块存储在临时文件中,直到所有块都上传完成后再进行合并。分片上传可以提高上传的稳定性和可靠性,同时也减少了单次上传的数据量。
  2. 断点续传:当上传过程中断(如网络中断、浏览器关闭等)时,可以记录已上传的块,并在恢复上传时跳过已上传的块,从断点处继续上传。可以使用后端的文件存储系统或数据库来记录已上传的块信息。断点续传可以提供更好的用户体验,避免重新上传整个文件。
  3. 并发上传:将大文件分割成多个块,并使用多个并发的HTTP请求同时上传这些块。可以使用多线程、多进程或异步任务来实现并发上传。并发上传可以加快上传速度,提高用户体验。
  4. 压缩和优化:在上传前,可以对大文件进行压缩和优化,减小文件大小,从而减少上传时间和带宽消耗。可以使用压缩算法(如Gzip、Deflate等)对文件进行压缩,或者对图片和视频进行优化(如压缩、裁剪、转码等)。
  5. 上传进度显示:在前端页面中实时显示上传进度,可以让用户知道上传的进展情况。可以使用XHR对象或WebSocket等技术来获取上传进度,并将其实时展示给用户。
  6. 上传限制和安全性:为了保护服务器的稳定性和安全性,可以设置上传文件的大小限制、文件类型限制和访问权限等。可以使用后端的验证和过滤机制来检查上传文件的合法性,并对上传文件进行安全处理(如病毒扫描、文件类型转换等)。

以上是一些常见的解决方案,可以根据具体的需求和技术栈选择适合的方案来处理大文件上传。

六、Node.js

https://blog.csdn.net/JackieDYH/article/details/106040463?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522169338661316800192256781%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=169338661316800192256781&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~baidu_landing_v2~default-1-106040463-null-null.142^v93^chatgptT3_2&utm_term=node.js%E9%AB%98%E9%A2%91%E9%9D%A2%E8%AF%95%E9%A2%98&spm=1018.2226.3001.4187

七、Vue

https://blog.csdn.net/weixin_52691965/article/details/121532027?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522169338669816800222820741%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=169338669816800222820741&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-2-121532027-null-null.142^v93^chatgptT3_2&utm_term=vue%E9%AB%98%E9%A2%91%E9%9D%A2%E8%AF%95%E9%A2%98&spm=1018.2226.3001.4187

八、移动端

1、移动端适配怎么做

// 移动端适配是指将网页或应用程序在不同的移动设备上展示出更好的效果,
// 使其在不同屏幕尺寸和分辨率下都能够正常显示和操作。以下是几种常用的移动端适配方法:

// 1. 使用响应式布局(Responsive Layout):
// 响应式布局是一种根据屏幕尺寸和分辨率自动调整布局和样式的方法。
// 通过使用CSS媒体查询和弹性布局等技术,可以使网页在不同设备上自适应调整布局和样式。

// 2. 使用视口(Viewport)标签:视口标签可以设置网页在移动设备上的可见区域大小。

// 通过设置`<meta name="viewport">`标签中的`width`、`initial-scale`、
// `maximum-scale`和`minimum-scale`等属性,可以控制网页的缩放和布局。


html
<meta name="viewport" content="width=device-width, initial-scale=1.0">

// 3. 使用流式布局(Fluid Layout):流式布局是一种相对于父容器大小自动调整的布局方式。

// 通过设置元素的宽度为百分比或`max-width`属性,可以使元素根据父容器的大小自动调整宽度。

// 4. 使用媒体查询(Media Query):媒体查询可以根据屏幕尺寸和分辨率来应用不同的样式。
// 通过在CSS中使用`@media`规则和`min-width`、`max-width`等属性,
// 可以根据屏幕大小来设置不同的样式。


/* PC端样式 */
@media (min-width: 1024px) {
  /* PC端样式 */
}

/* 手机端样式 */
@media (max-width: 1023px) {
  /* 手机端样式 */
}

//5. 使用框架或库:使用现有的移动端框架或库,如Bootstrap、Foundation、Ant Design Mobile等,
// 可以快速实现移动端适配,减少开发工作量。


 

2、H5 与手机是如何通信的

H5与手机通信主要通过以下几种方式:    

  1.  通过浏览器提供的JavaScript API:浏览器提供了一些JavaScript API,可以让H5页面与手机进行通信。例如,可以使用navigator.geolocation获取手机的地理位置信息,使用navigator.camera调用手机的摄像头进行拍照,使用navigator.contacts获取手机的联系人信息等。
  2. 使用AJAX或Fetch进行网络请求:H5页面可以使用AJAX或Fetch等技术与服务器进行网络请求,获取或提交数据。通过与服务器的交互,可以实现与手机的通信。例如,可以发送请求获取手机的数据,或者提交数据到服务器进行处理。
  3. 使用WebSocket进行实时通信:WebSocket是一种在浏览器和服务器之间建立持久连接的协议,可以实现双向的实时通信。H5页面可以通过WebSocket与手机的服务器进行通信,实现实时的消息推送、聊天等功能。
  4. 使用WebRTC进行音视频通话:WebRTC是一种支持浏览器之间进行实时音视频通话的技术。H5页面可以使用WebRTC与手机进行音视频通话,实现实时的视频聊天、会议等功能。
  5. 使用原生插件或桥接技术:H5页面可以通过调用原生插件或使用桥接技术与手机进行通信。例如,可以使用Cordova或React Native等框架,在H5页面中调用原生插件的功能,实现与手机的交互。

需要注意的是,不同的手机平台和浏览器可能支持的API和技术有所不同,开发时需要根据目标手机平台和浏览器的要求进行选择和适配。另外,为了保证通信的安全性,需要注意对通信数据进行加密和验证。

3、如何判断是手机端还是PC端

要判断是手机端还是PC端,可以通过以下几种方式:

// 1. 使用媒体查询:可以通过CSS的媒体查询来根据屏幕宽度来判断是手机端还是PC端。

// 例如,可以使用`@media`查询来设置不同的样式,如隐藏或显示某些元素。

```css
/* PC端样式 */
@media (min-width: 1024px) {
  /* PC端样式 */
}

/* 手机端样式 */
@media (max-width: 1023px) {
  /* 手机端样式 */
}
```

// 2. 使用JavaScript检测屏幕宽度:可以使用`window.innerWidth`属性来获取浏览器窗口的宽度,
// 然后根据宽度的阈值来判断是手机端还是PC端。

```javascript
if (window.innerWidth <= 1023) {
  // 手机端
} else {
  // PC端
}
```

// 3. 使用用户代理(User Agent)字符串:
// 可以通过检测浏览器的用户代理字符串来判断是手机端还是PC端
// 用户代理字符串中包含了浏览器的信息,包括操作系统和设备类型。
但是需要注意的是,
// 用户代理字符串可以被修改,不是完全可靠的方式。


```javascript
const userAgent = navigator.userAgent.toLowerCase();

if (userAgent.match(/android|iphone|ipad|ipod|blackberry|iemobile|opera mini/i)) {
  // 手机端
} else {
  // PC端
}
```

 

4、微信小程序

https://blog.csdn.net/weixin_45102270/article/details/113064362?ops_request_misc=&request_id=&biz_id=102&utm_term=%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E9%AB%98%E9%A2%91%E9%9D%A2%E8%AF%95%E9%A2%98&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-0-113064362.142^v93^chatgptT3_2&spm=1018.2226.3001.4187

九、TypeScript

1、泛型

泛型可以与其他TypeScript的特性(如接口、类、函数重载等)结合使用,从而提供更强大和灵活的类型支持。

使用泛型可以让我们编写更通用、可重用和类型安全的代码,同时也提供了更好的类型推断和类型检查。

十、补充

1、面试题补充

牛客网公司真题_免费面试题库_企业面试|面试真题

2、umi框架

Umi 介绍

3、lodash工具库

Lodash 简介 | Lodash中文文档 | Lodash中文网

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值