Skip to content

前端技术细节和原理总结 ​

1. css相关 ​

1.1. BFC ​

什么叫BFC? ​

Bfc: 块格式化上下文(Block Formatting Context),你可以将BFC看作是页面中的一个迷你布局。一旦元素创建了一个BFC,它其中的所有元素都会被它包裹

BFC有什么用? ​

包裹浮动元素,阻止父元素高度坍塌[tān tā]问题、 阻止外边距叠加 阻止类似于文字围绕浮动的图片元素效果。

如何构建BFC布局? ​

css
overflow:auto/scroll
position:absolute/fixed,
display:inline-block/table-cell/table-caption,

其中table-cell和table-caption是表格相关元素对应默认CSS值,所以当你创建表格时,每个表格的单元格都会自动创建BFC`` 另外当使用多列布局时,使用了column-span:all也可以创建BFC。 Flexbox和Grid布局中的元素也会自动创建类似BFC的机制,只是它们被称为FFC(弹性格式上下文)和GFC(网格格式上下文)。这反映了它们所参与的布局类型。一个BFC表明他内部的元素参与了块级布避,一个FFC意味着它内部的元素参与了Flexbox`布局。在实践中,这几种布局的结果是相似的,浮动元素会被包裹,外边距不会叠加。

构建BFC布局的标准方式? ​

display:flow-root , 不过兼容性还不是太好

https://www.w3cplus.com/css/understanding-css-layout-block-formatting-context.html

1.2. 其他css布局方式 ​

2. js基础语法相关 ​

2.1. setImmediate ​

该特性是非标准的,请尽量不要在生产环境中使用它!

该方法用来把一些需要长时间运行的操作放在一个回调函数里,在浏览器完成后面的其他语句后,就立刻执行这个回调函数。

注意: 该方法可能不会被批准成为标准,目前只有最新版本的 Internet Explorer 和Node.js 0.10+实现了该方法。它遇到了 Gecko(Firefox) 和Webkit (Google/Apple) 的阻力.

语法

js
var immediateID = setImmediate(func, [param1, param2, ...]);

var immediateID = setImmediate(func);

immediateID 是这次setImmediate方法设置的唯一ID,可以作为 window.clearImmediate 的参数. func 是将要执行的回调函数,所有参数都会直接传给你的函数。

https://developer.mozilla.org/zh-CN/docs/Web/API/Window/setImmediate

2.2. process.nextTick ​

process.nextTick()的意思就是定义出一个动作,并且让这个动作在下一个事件轮询的时间点上执行。我们来看一个例子。例子中有一个foo(),你想在下一个时间点上调用他,可以这么做:

js
function foo() {
  console.error("foo");
}

process.nextTick(foo);
console.error("bar");

// bar
// foo

运行上面的代码,你从下面终端打印的信息会看到,"bar"的输出在“foo”的前面。这就验证了上面的说法,foo()是在下一个时间点运行的。

你也可以使用setTimeout()函数来达到貌似同样的执行效果:

js
setTimeout(foo, 0);
console.log('bar');

但在内部的处理机制上,process.nextTick()和setTimeout(fn, 0)是不同的,process.nextTick()不是一个单纯的延时,他有更多的 特性。

2.3. 当async里面await后的代码 和 new Promise里面的代码同时存在队列里面时,浏览器里面是谁先进入队列就先执行谁。(在nodejs环境下then代码先执行,promise代码后执行) ​

2.3.1. 先执行promise,在执行async ​

js
new Promise(function (resolve) {
  console.log("9");

  resolve();

  console.log("10");
}).then(function () {
  console.log("11");
});

async1();

async function async1() {
  console.log("1");

  await async2();

  console.log("2");
}

async function async2() {
  console.log("3");
}

2.3.2. 先执行async,在执行promise ​

js
async function async1() {
  console.log("1");

  await async2();

  console.log("2");
}

async function async2() {
  console.log("3");
}

async1();

new Promise(function (resolve) {
  console.log("9");

  resolve();

  console.log("10");
}).then(function () {
  console.log("11");
});

2.4. 同步异步执行顺序的综合练习 ​

请说出node执行以下代码的输出结果并解释原因 ​

js
async function async1() {
  console.log("1");

  await async2();

  console.log("2");
}

async function async2() {
  console.log("3");
}

console.log("4");

setTimeout(function () {
  console.log("5");
}, 0);

setTimeout(function () {
  console.log("6");
}, 3);

setImmediate(() => console.log("7")); //这个只会在最后执行

process.nextTick(() => console.log("8")); //这句话就是执行的意思,他会在settimeout之前执行

async1();

new Promise(function (resolve) {
  console.log("9");

  resolve();

  console.log("10");
}).then(function () {
  console.log("11");
});

console.log("12");

// 4 1 3 9 10 12 ,8 11 2 5 6 7 (11与2如果在浏览器环境下执行顺序会调换,then里面的先执行)

2.5. ["1","2","3"].map(parseInt) 的结果是什么? ​

由于parseInt接收两个参数,参数一:要转换为十进制的数,参数二:这个数来自多少进制

当parseInt的第二个参数为0时表示10进制字符串转为10进行数字

js
["1", "2", "3"].map((ele, i) => {
  //元素,下标
  return parseInt(ele, i); //分别返回parseInt(“1”,0),分别返回parseInt(“2”,1),分别返回parseInt(“3”,2),
});

提示;如果反了,那么就是如下的新结果

2.6. promise里面写console.log和外面的console.log谁先执行? ​

js
//结果是按照顺序执行,都是同步
console.log(1111);
new Promise((resolve, reject) => {
  console.log(222);
});

console.log(333);

2.7. promise里面0秒定时器和then里面的代码谁先执行? ​

js
// 如果resolve直接同步返回,而不是在某个http请求的结果中进行返回,
// 则then会比promise里面的定时器先执行
new Promise((resolve, reject) => {
  setTimeout(() => console.log(1111), 0);

  resolve();
}).then(() => console.log(222));

2.8. 不包含异步请求的async function和其后面的同步代码谁先执行? ​

js
//async函数里面如果含有异步,那么就会在下一次的事件队列进行执行

console.log(111);

async function fn() {
  setTimeout(() => {
    console.log(222);
  }, 0);
}

fn();

console.log(333);

//async函数里面如果只有同步代码,那么就会在同步执行

console.log(111);

async function fn() {
  console.log(222);
}

fn();

console.log(333);

2.9. async函数里面如果await后面是返回promise的async函数,但是此async函数里面只有同步代码,该如何执行? ​

js
async function fn() {
  await fn2();
}

async function fn2() {
  console.log(222);
}

//async函数里面如果await后面是返回promise的async函数,但是此async函数里面只有同步代码,则最终会在同步执行

console.log(111);

fn();

console.log(333);

2.10. 当自执行函数名和变量名冲突时会怎么样? ​

js
// 下面函数是匿名函数,函数中的b在非严格模式下为全局变量,所以b值会覆盖为20

var b = 10;

(function () {
  b = 20;

  console.log(b);
})();

// 下面在最外面有个全局变量b, 自执行函数名也为b,函数中的b在非严格模式下为全局变量

// 按照函数预编译规则,变量提升规则来看:最终b的值应该为函数function

var b = 10;

(function b() {
  b = 20;
  console.log(b);
})();

2.11. 当函数接收一个对象作为参数,然后在函数里面又new了一个新的对象赋值给它时,打印出来的值是多少?

js
//当函数接收一个对象作为参数,然后在函数里面又new了一个新的对象赋值给它时,取新对象的值

function fn(o) {
  var o = new Object();

  o.url = "http://gogle.com";

  console.log(o.url);
}

var obj = { url: "https://www.baidu.com" };

fn(obj);

2.12. var a=b=c=d=10; ​

下面执行后a,b,c,d的值分别为多少?

js
function fn() {
  var a = (b = c = d = 10);
}

fn();

console.log(d);

console.log(c);

console.log(b);

console.log(a);

在fn中var a=b=c=d=10中a是var声明的局部变量,b,c,d则是没有用var声明的全局变量,所在在函数外面打印只有b,c,d而没有a

2.13. 函数预编译过程 ​

共分为五步: 生成环境--> 查找形参---> 赋值实参--> 查找变量---> 查找函数

  1. 函数在运行的瞬间,生成一个执行期环境上下文 (Active Object),简称AO;
  2. 查找是否具有形参,找到后添加到AO的属性,值为undefined,例如AO.age=undefined;
  3. 接收实参,添加到AO的属性,覆盖之前的undefined;
  4. 查找函数内是否具有变量声明,如var age;或var age=23;,如果找到了且AO上还没有这个属性,就把他添加AO上,值为undefined。如果找到了但AO上已经有了这个属性,则不作任何修改;
  5. 查找函数内是否具有其他函数的声明,如:function age(){};如果找到了就把函数赋给AO.age ,而不管AO上是否已经存在此属性。如果找到了多个同名函数声明,那么只会以最后一个函数声明为准

3. js同步和异步 ​

Promise原理,

同步异步相关方案

4. 原生js和算法相关 ​

4.1. 深拷贝和浅拷贝,说出三种方式以上 ​

  1. 使用Function进行构造
  2. 使用JSON.parse和JSON.stringfiy进行转换
  3. 使用immutable的相关方法:fromJS与toJS方法
  4. 使用jQuery的相关方法 extend方法

浅拷贝 $.extend(a,b) a使用b的属性

深拷贝 $.extend(true,a,b) a使用b的属性

extend方法详解: https://www.cnblogs.com/NTWang/p/6175773.html

$.extend({}) ,为jQuery类添加方法,可以理解为扩展静态方法

$.fn.extend({}) 插件,对jQuery.prototype进行扩展

自己写循环和递归实现

4.2. 对一个对象数组进行去重 ​

4.2.1. set方法可以实现普通的去重 ​

js
var objAr = [
  { id: 1, name: "张三", age: 18 },

  { id: 1, name: "李四", age: 18 },

  { id: 2, name: "王五", age: 18 },

  { id: 2, name: "赵六", age: 18 },

  { id: 3, name: "孙七", age: 18 },

  { id: 3, name: "丘八", age: 18 },
];

var itemsAr = []; //创建一个数组盛放每个对象的唯一标识属性:id

objAr.filter((ele, index, self) => {
  if (!itemsAr.includes(ele.id)) {
    //如果id重复了就过滤

    itemsAr.push(ele.id);

    return true;
  } else {
    false;
  }
});

4.2.3. 使用filter方法实现对象数组的去重--把数组的每个对象转为字符串进行比较然后去重 ​

需要注意:此方法只要对象有一个属性有一个不一样那么就不相同

js
var objAr = [
  { id: 1, name: "张三", age: 18 },

  { id: 1, name: "张三", age: 18 },

  { id: 2, name: "王五", age: 18 },

  { id: 2, name: "王五", age: 18 },

  { id: 3, name: "孙七", age: 18 },

  { id: 3, name: "孙七", age: 18 },
];

//把数组的每个对象转为字符串进行比较

var itemsAr = [];

objAr.filter((ele) => {
  var str = JSON.stringify(ele);

  if (!itemsAr.includes(str)) {
    itemsAr.push(str);

    return true;
  } else false;
});

5. 算法和数据结构 ​

5.1. 递归 ​

5.1.1. 手写递归函数,实现深拷贝 ​

5.1.2. 手写递归函数,实现求m,n之间所有数的和 ​

5.2. 排序算法 ​

5.2.1. 手写快速排序算法 ​

5.2.2. 写法一: ​

http://www.ruanyifeng.com/blog/2011/04/quicksort_in_javascript.html?bsh_bid=124324679

"快速排序"的思想很简单,整个排序过程只需要三步:

在数据集之中,选择一个元素作为"基准"(pivot)。 所有小于"基准"的元素,都移到"基准"的左边;所有大于"基准"的元素,都移到"基准"的右边。 对"基准"左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止。

js
var quickSort = function (arr) {
  if (arr.length <= 1) {
    return arr;
  }
  var pivotIndex = Math.floor(arr.length / 2);
  var pivot = arr[pivotIndex];
  var left = [];
  var right = [];
  for (var i = 0; i < arr.length; i++) {
    if (arr[i] < pivot) {
      left.push(arr[i]);
    } else if (arr[i] > pivot) {
      right.push(arr[i]);
    }
  }
  return quickSort(left).concat([pivot], quickSort(right));
};

var arr = [11, 2, 5, 3, 21, 4, 8, 7, 90];

console.log(quickSort(arr));

5.2.3. 写法二: ​

https://blog.csdn.net/xuyangxinlei/article/details/81062015

js
/**
 * 快速排序(递归)
 * @param num 待排序数组
 * @param left 左指针
 * @param right 右指针
 */

function _quickSort(num, left, right) {
  if (left >= right) return; // 若左右指针相遇,待排序数组长度小宇1,即递归的终点,return(注意不能写成left==right,这里left是有可能大于right的)。
  var i = left,
    j = right,
    flag = left; // 定义可移动的左右指针 i,j,定义flag为基数下标。
  while (i < j) {
    // 在i<j时不断循环,i一旦与j碰头,则跳出循环。
    while (num[j] >= num[flag] && i < j) j--; // j不断左移,找到在num[flag]右侧且比它大的数。
    if (i >= j) {
      break; // 由于j可能已被改变,需再次判断i与j是否碰头。
    }
    while (num[i] <= num[flag] && i < j) i++; // i不断右移,找到且比基数小的数,且i不能与j碰头。(由于两次交换已合并,此处不需要使得i在flag左侧)
    // num[flag] num[j] num[i]三者换位,可用ES6语法糖[num[flag],num[j],num[i]] = [num[j],num[i],num[flag]];
    let temp = num[flag];
    num[flag] = num[j];
    num[j] = num[i];
    num[i] = temp;
    flag = i; // 基数已经在原num[i]的位置,flag同时也要赋值成i。
  }

  _quickSort(num, left, flag - 1); // 将flag左边数组作为待排序数组,递归调用。
  _quickSort(num, flag + 1, right); // 将flag右边数组作为待排序数组,递归调用。
}

var arr = [11, 2, 5, 3, 21, 4, 8, 7, 90];
_quickSort(arr, 0, arr.length - 1); //快速排序
console.log(arr);

5.3. 自执行函数 ​

5.3.1. 手写代码,用闭包加自执行函数实现let效果 ​

5.4. 查找算法 ​

5.5. 栈,队列,树 ​

6. 设计模式 ​

原生js手写一个订阅发布模式的事件监听对象

7. Es6语法相关 ​

7.1. 箭头函数特性 ​

  1. 箭头函数没有自己的this、arguments、super
  2. this指向当前所处上下文,如果嵌套了多层箭头函数,最里面函数会指向最外面
  3. 箭头函数不能作为构造函数,和 new 一起用就会抛出错误
  4. 箭头函数没有原型属性
  5. 不能简单返回对象字面量,要用括号包裹起来
  6. 箭头函数不能当做Generator函数,不能使用yield关键字

7.2. map和对象的不同之处 ​

object的键的类型是字符串,map的键的类型是 可以是任意类型; object获取键值使用Object.keys(返回数组),Map获取键值使用 map变量.keys() (返回迭代器)。

7.3. javascript实现继承的集中方式 ​

7.3.1. 原型链继承--修改prototype指向 ​

让当前对象的prototype指向要继承的构造函数的实例

myObj.prototype=new Par();

缺点:

在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性,并且会被所有的实例共享。 在创建子类型的实例时,不能向超类型的构造函数中传递参数

7.3.2. 构造函数继承--调用被继承函数,修改this指向 ​

(也称伪造对象或经典继承)

在当前的函数里面执行其他要继承的函数,并且通过call/bind/apply等方法修改this指向。

如果调用了多个其他函数,那么就等价于是多继承。

缺点:

这种继承方式只能继承对应构造函数的实例属性和方法。

7.3.3. 组合继承--原型链继承+构造函数继承 ​

(也称伪经典继承)

即原型链继承和构造函数继承同时使用

通过构造函数继承修改this,拿到父构造函数的实例内容 通过原型链继承,让子构造函数的原型对象的__proto__指向父构造函数的原型 从而实现,子实例(会合并父实例内容)---》子原型---》父原型

js
function SuperType(name) {
  this.name = name;
  this.colors = ["red", "green", "blue"];
}

SuperType.prototype.sayName = function () {
  alert(this.name);
};

function SubType(name, age) {
  // 借用构造函数方式继承属性
  SuperType.call(this, name);
  this.age = age;
}
SubType.prototype.sayAge = function () {
  alert(this.age);
};

// 原型链方式继承方法
SubType.prototype.__proto__ = SuperType.prototype;
var instance1 = new SubType("luochen", 22);

7.3.4. 通过Object.create实现继承 ​

Object.create方法会返回一个新对象,其原型为Object.create方法的第一个参数。

这种方法和原型链继承很相似,都是直接修改原型对象的指向

js
var person = {
  name: "luochen",
  colors: ["red", "green", "blue"],
};

var anotherPerson1 = Object.create(person, {
  name: {
    value: "tom",
    writable: true, //加了这个属性,name就可以被更改,没加就不能被更改
  },
});

7.3.5. 寄生式继承 ​

将Object.create方法封装一下,也能实现,实现所谓的寄生式继承

7.4. es6的class原理--组合继承 ​

原理(组合继承):

  1. 先通过修改this,将父构造函数的实例上的所有的属性和方法合并到子构造函数实例中(构造函数继承)
  2. 在通过把子构造函数的的原型对象的__proto__属性指向父构造函数的原型,从而继承父构造函数的原型(原型链继承) 结果:子实例(已经合并了父实例内容)--》子原型--》父原型
js
class A {
  name = "zs";
  sayName() {
    console.log(this.name);
  }
}

class B extends A {
  age = "zs";
  sayAge() {
    console.log(this.age);
  }
}

8. react相关 ​

8.1. ajax请求执行到一半时组件被销毁 ​

解决方法: 在React组件销毁时清理异步操作和取消请求

##8.1.1. 报错 通常是 react 组件已销毁,但是组件中一些异步操作还未结束会报如下这个错误:

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.

警告:无法对未装载的组件执行反应状态更新。这是一个no操作,但它表示应用程序内存泄漏。要修复此问题,请取消componentWillUnmount方法中的所有订阅和异步任务。

8.1.2. 具体解决方法 ​

情况一: 阻止异步操作 ​

jsx
componentWillUnmount() {
    this.setState = (state, callback) => {
        return
    }
}

情况二: 阻止请求(axios) ​

js
...

constructor(props) {
   this.state=store.getState()
   this.source = axios.CancelToken.source() //生成取消令牌用于组件卸载阻止axios请求
}

...

componentDidMount = () => {
  const _t = this
  const url="xxxx";
  axios.get(url, {
    cancelToken: _t.source.token
  })
  .then(res => {
  ...
  })
  .catch(function(thrown) {
    if (axios.isCancel(thrown)) {
      console.log('Request canceled', thrown.message);
    } else {
      console.log(thrown)
    }
  })
}
componentWillUnMount = () => {
  //阻止请求
  this.source.cancel('组件卸载,取消请求');
}

情况三: 终止请求(原生ajax) ​

XMLHttpRequest.abort(): 如果该请求已被发出,该方法将终止该请求。当一个请求被终止,它的 readyState 属性将被置为0( UNSENT )。

js
var xhr = new XMLHttpRequest(),
method = "GET",
url = "https://developer.mozilla.org/";
xhr.open(method,url,true);
xhr.send();
xhr.abort();

情况四: 清除定时 ​

js
var timer;
...
componentDidMount = () => {
    timer = setTimeout(() => {
        this.setState({a:123})
    },1000)
}

componentWillUnMount = () => {
    clearTimeout(timer)
}

8.2. React.js组件卸载如何自动取消异步请求 ​

https://blog.csdn.net/weixin_34024034/article/details/87994618?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-6&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-6

8.2.1. React--直接不执行成功会的代码 ​

jsx
componentDidMount(){
    this.mounted = true;
    this.props.fetchData().then((response) => {
        if(this.mounted) {
            this.setState({ data: response })
        }
    })
}
    
componentWillUnmount(){
    this.mounted = false;
}

8.2.2. 原生--只有reject的结果 ​

jsx
return Promise.race([cancellation, window.fetch(input, init)]);

这里的cancellation其实是另一个Promise,这个Promise负责注册一个abort事件,当我们组件卸载的时候,主动触发这个abort事件,这样最后如果组件卸载之前,fetch请求已经响应完毕,就走正常逻辑,否则就因为我们触发了abort事件返回了一个reject的响应结果。

8.2.3. race和all方法 ​

https://www.jianshu.com/p/7e60fc1be1b2

race: 顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。

all: Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组(顺序一致),而失败的时候则返回最先被reject失败状态的值。

8.3. React-内存泄漏出现一些原因和解决方法 ​

原因:如果在组件销毁后执行setState方法,就可能出现内存泄漏

解决:我们应该在组件销毁的时候将异步请求撤销,不执行setState

https://blog.csdn.net/weixin_33755649/article/details/88681399?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-2&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-2

8.4. vue购物车,react的todolist如何设计(组件结构和state结构) ​

8.5. react父子组件-生命周期函数顺序与过程? ​

8.5.1. 加载渲染过程 ​

Parent constructor

Parent componentWillMount

Parent render

Child constructor

Child componentWillMount

Child render

Child componentDidMount

Parent componentDidMount

总结:当执行render父组件的时候,才会进入子组件的生命周期,子组件的周期结束后,再回到上级的周期。

8.5.2. 组件更新过程 ​

主动更新:组件通过setState修改自己的状态 Child shouldComponentUpdate(nextProps, nextState)

Child componentWillUpdate(nextProps, nextState)

Child render

Child componetDidUpdate(prevProps, prevState)

被动更新:父组件通过props把自己的state传递给子组件,父组件执行setState更新状态 Parent shouldComponentUpdate(nextProps, nextState)

Parent componentWillUpdate(nextProps, nextState)

Parent render

Child componentWillReceiveProps(nextProps)

Child shouldComponentUpdate(nextProps, nextState)

Child componentWillUpdate(nextProps, nextState)

Child render

Child componetDidUpdate(prevProps, prevState)

Parent componetDidUpdate(prevProps, prevState)

总结 :不管父组件有没有把数据传递给子组件,只要父组件setState,都会走一遍子组件的更新周期。而且子组件被动更新会比主动更新所执行的流程多出来一个componentWillReceiveProps 方法。这样会比较损耗性能,可以使用PureComponent解决。

8.6. PureComponent ​

https://www.jianshu.com/p/ff993656a66b

8.7. redux-saga ​

https://www.jianshu.com/p/e84493c7af35

8.8. setState原理 ​

setState是通过一个队列机制实现的state更新,当执行setState时,会将需要更新的state的合并后放入队列,而不是立即更新,这种机制能够高效的批量更新state 如果不使用setState更新state的话(比如直接赋值),那么该state就不会放入到状态队列中,当下次调用setState并对状态进行合并时,就会忽略之前直接修改的state,从而照成无法预知的错误 也因为队列机制,state的更新就如同异步一样,会根据一个标志判断执行是否执行,一般会放在后面进行执行 setState可以接收第二个参数,一个回调函数,会在state更新后执行 setState可以接收函数作为参数,就可以实现类似于同步执行state的效果 setState之所以能够有异步执行的效果,是因为他处于react的事件系统机制下,如果我们使用原生的addeventlister来绑定,那么setState就会同步执行,(在定时器和一些回调函数里面也是如此)

8.9. react事件机制 ​

react的所以绑定的事件都会自动的绑定在最外层,即自动代理 react的事件是做了处理的,符合w3c标准,不用担心ie的兼容问题 react的事件和原生浏览器事件一样拥有同样的接口,同样支持事件冒泡机制,可以使用stopPrapagation,preventDefault来阻止事件冒泡和默认行为 如果要访问原生事件对象,需要使用nativeEvent属性 在react组件渲染完成的生命周期函数里面,我们可以绑定原生事件,但是这个事件一定要在组件卸载时进行手动移除。否则会照成内存泄漏。(使用react的合成事件系统则不需要,因为react内部已经帮我们妥善的处理了) react合成事件和原生事件尽量不要混用 react合成事件系统只是原生DOM事件系统的一个子集,仅实现了DOM Level3的事件接口,并统一了浏览器的兼容问题。(有些事件react并没有实现,比如resize) React的合成事件并没有实现事件捕获,只支持事件冒泡。 在react合成事件中阻止事件冒泡只能用于react合成事件系统,而不能用于原生事件,但是原生事件中阻止冒泡行为却可以阻止react合成事件

8.10. react性能优化 ​

减少http请求 减少文件大小 缓存机制 key值 Css 精灵/雪碧图 防抖节流 shouldComponentUpdate 懒加载,按需加载 immutable不可变数据类型 代码层次优化:if-else/switch-case 使用 CDN 加速

8.11. immutable的使用和一些api ​

8.11.1 fromJS() ​

作用:将一个js数据转换为Immutable类型的数据。

用法:fromJS(value, converter)

简介:value是要转变的数据,converter是要做的操作。第二个参数可不填,默认情况会将数组准换为List类型,将对象转换为Map类型,其余不做操作。

代码实现:

js
const obj = Immutable.fromJS(
  { a: "123", b: "234" },
  function (key, value, path) {
    console.log(key, value, path);
    return isIndexed(value) ? value.toList() : value.toOrderedMap();
  }
);

8.11.2 toJS() ​

作用:将一个Immutable数据转换为JS类型的数据。

用法:value.toJS()

8.12. 高阶组件实现和使用 ​

高阶组件是react中复用组件逻辑的一项高级技术,高阶组件是函数,它接受一个组件作为参数,然后返回一个新的组件。

高阶组件是用来复用组件逻辑的,即高阶组件解决了组件逻辑难以复用的问题

8.12.1. 几种写法 ​

函数接收组件作为参数,然后在函数里面重新写一个组件,这个组件把接收的参数组件作为渲染的部分内容,同时加上其他内容。 这个内容可能是:在里面重新继承/或者直接注入到组件上面去

9. webpack打包优化? ​

如何解决webpack打包慢和打包出来体积大的问题

配置依赖时确保区分生产依赖和开发依赖,让生产环境只打包生产依赖 文件压缩 按需加载 去掉开发环境的配置,比如热加载之类的 将css提取到单独的文件中,这样访问时可以进行并行下载,可以更快地加载样式和脚本 提取通用模块文件、让提取的公共js的hash值不要改变 将生产环境配置和开发环境配置分成不同的文件,确保生产环境没有不必要的插件 提取第三方库,不要让他和源码一起打包 打包时去掉map文件

10. http请求问题? ​

10.1. 如何捕获超时错误 ​

设置timeout的时间,通过检测complete时status的值判断请求是否超时,如果超时执行相应的操作。

js
var ajaxTimeoutTest = $.ajax({
  url: "", //请求地址
  timeout: 1000, //超时时间设置, 单位毫秒
  type: "GET", //get或post
  data: {}, //请求所传参数,json格式
  dataType: "json", //返回的数据格式
  success: function (data) {
    //请求成功的回调函数
    alert("成功");
  },
  complete: function (XMLHttpRequest, status) {
    //请求完成后最终执行参数
    if (status == "timeout") {
      //超时,status还有success,error等值的情况
      ajaxTimeoutTest.abort();
      alert("超时");
    }
  },
});

10.2. https实现原理 ​

10.2.1. 概念 ​

HTTP协议是一种使用明文数据传输的网络协议,在传输过程中数据是不加密的。

HTTPS协议是在HTTP的基础上增加了数据加密。在数据进行传输之前,对数据进行加密,然后再发送。

这样,就算数据被第三者所截获,但是由于数据是加密的,所以你的个人信息让然是安全的。这就是HTTP和HTTPS的最大区别。

10.2.2. HTTP和HTTPS的其他不同 ​

数据加密传输,是HTTP和HTTPS之间的本质性区别,其实除了这个之外,HTTPS网站和HTTP网站还有其他地方不同。

当你使用Chrome浏览器访问一个HTTP网站的时候,你会发现浏览器会对该HTTP网站显示“不安全”的安全警告,提示用户当前所访问的网站可能会存在风险。

IMG_256

而假如你访问的是一个HTTPS网站时,情况却是完全不一样。你会发现浏览器的地址栏会变成绿色,企业名称会展示在地址栏中,地址栏上面还会出现一把“安全锁”的图标。这些都会给与用户很大的视觉上的安全体验。以下是EV证书在不同浏览器中的展现。

IMG_257

除了浏览器视觉上不同以外,HTTPS网站和HTTP网站还有一个很重要的区别,就是对搜索排名的提升,这也是很多站长所关注的地方。

百度和谷歌两大搜索引擎都已经明确表示,HTTPS网站将会作为搜索排名的一个重要权重指标。也就是说HTTPS网站比起HTTP网站在搜索排名中更有优势。

HTTPS网站相比起HTTP网站拥有着多种的优势,HTTP明显已经不能适应当今这个互联网时代,可以预见到HTTP在不久的将来将会全面被HTTPS所取代。

10.2.3. HTTPS实现过程 ​

https://blog.csdn.net/weixin_42139044/article/details/99069054

https://blog.csdn.net/cxp863/article/details/88758784?depth_1-utm_source=distribute.pc_relevant.none-task-blog-OPENSEARCH-4&utm_source=distribute.pc_relevant.none-task-blog-OPENSEARCH-4

首先服务端会有一个私钥,和数字证书,证书里面含有公钥(非对称加密算法,RSA) 当客户端第一次请求服务端时,服务端会把数字证书传给客户端,客户端会检查数字证书是否是服务器的,是否无误 如果数字证书是无误的,那么客户端从数字证书里面取出公钥,并且发一个随机字符串给服务器,让服务器使用私钥加密后传回来,客户端在使用公钥解密查看是否和自己传的一致,如果一致,那么就认为对方是服务器而不是伪造的。(可以只加密hash值,更安全) 当客户端确认了对方是服务器,然后就开始生成一个对称加密算法和密钥,用于后面的通信的加密和解密。 “客户”自己选择一个对称加密算法和一个密钥,把这个对称加密算法和密钥一起用公钥加密后发送给“服务器”。注意,由于对称加密算法和密钥是用公钥加密的,就算这个加密后的内容被“黑客”截获了,由于没有私钥,“黑客”也无从知道对称加密算法和密钥的内容。

由于是用公钥加密的,只有私钥能够解密,这样就可以保证只有服务器可以知道对称加密算法和密钥,而其它人不可能知道(这个对称加密算法和密钥是“客户”自己选择的,所以“客户”自己当然知道如何解密加密)。这样“服务器”和“客户”就可以用对称加密算法和密钥来加密通信的内容了。

11. vue相关? ​

11.1. vue运行的一些问题 ​

vue项目中有些隐藏的配置文件,这些文件如果复制时漏掉了那么就不能正常的运行起来了 开发环境运行时,如果地址是:0.0.0.0:端口, 这时在mac上时正常的,但是在windows上可能就无法正常的运行了

11.2. vue父子组件-生命周期函数顺序与过程? ​

加载渲染过程   父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted

子组件更新过程   父beforeUpdate->子beforeUpdate->子updated->父updated

父组件更新过程   父beforeUpdate->父updated

销毁过程   父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

11.3. data 为什么是个函数而不是个对象? ​

如果 data 是个对象,那么所有的组件实例将共享一份数据,也就是各个组件实例间可以随意修改其他组件实例的值,

但是 data 定义成一个函数,将会 return 出一个唯一的对象,不会和其他组件共享一个对象。

11.4. 如何往vue实例上添加我们自己的属性 ​

方式一: 直接在Vue.prototype上添加(这种方式任何组件都可以获取到,直接通过this.进行调用)

方式二: 在构建组件或者vue根实例时,直接在最外层添加你要的属性。(这种方式只有当前组件能获取到,通过this.$options.的形式进行调用)

11.5. this.data修改数据是异步的吗? ​

vue 自身维护 一个 更新队列。当你设置 this.a = 'new value',DOM 并不会马上更新,也就是下一个事件循环开始时执行更新时才会进行必要的DOM更新。 所以应该算 延时异步的,只不过时间太短,感应不出来。所以 for 循环 10000次 this.a = i vue只会更新一次,而不会更新10000次,也是为了性能。

当执行this.a = 'new value'时,会触发Object.defineProperty的set方法,

11.6. vue中如何对data数据进行双向绑定的? ​

原理:

Data-->observer--->Object.defineProperty(get/set)--->watcher---->UI observer方法介绍data数据对象,通过循环加递归,给每个key都绑定了get/set方法。 在vue中,为每个key都定义了一个依赖实例对象Dep,目的是在get方法中依次保存所有使用了此key的元素(以数组形式存储),并且在set时去触发这些存储的元素,从而实现双向绑定操作 对于ui层而言,get/set并没有直接进行操作,而是定义了一个中间的watcher对象,watcher是ui层和data层的中间层,靠他来实现两者的一致 注意事项:

由于get/set只能监听数据(key)是否被修改,而不能监听新增属性和删除属性,所以给data添加和删除属性是无法触发双向绑定的。

11.7. 执行数组的push方法时想要让他打印一个console.log,该如何做? ​

方式一: 直接找到Array原型上的push方法,然后进行重写覆盖。(不推荐,这会污染全局Array)

方式二: 直接修改当前数组的__proto__的指向,执行一个队原Array原型对象的拷贝,并且修改此拷贝中的push方法,及只修改这一个数组的原型指向即可,而不会污染全局Array

方式三: 直接在数组本身上加一个push方法,这个方法里面执行consol.log,由于执行优先级是本身的方法大于原型的方法,所以会先执行本身的push方法。

如果个别浏览器不支持,__proto__属性,那么就方法二三同时使用,已进行兼容,实际上vue就是这样干的。

proto 是 Object.getPrototypeOf(obj) / Object.setPrototypeOf(obj)的早期实现

11.8. 在vue中对于数组直接通过下标改值为什么不能触发双向绑定? ​

在vue中

对象的变化监听,双向绑定使用的是 Object.defineProperty 方法的数据描述符形式---即get/set 数组的变化监听,双向绑定使用的是Object.defineProperty 方法的存取描述符形式---即 { value: function(){return v} },在此方法中重写了数组的各个方法。 在vue中,在observe方法中,当监听到是数组时,会先判断__proto__属性是否可用, 如果可以用就修改当前数组的__proto__原型指向,在这个新的原型对象中,数组的各个方法都被重写了,添加了变化通知的能力。

如果不能用,就直接把新的原型对象的方法直接,写在数组本身上,这样当我们调用push等方法时,会优先调用数组本身上的方法。

由于是重写的数组方法,所以,当我们直接通过下标修改了数组元素内容时,无法监听到,也就无法做双向绑定。

11.9. vue中的虚拟DOM ​

虚拟dom是将状态映射成视图的一种解决方案,原理是先使用状态生成虚拟DOM节点,然后使用虚拟阶段渲染视图。

在vue中,使用模板来描述状态和dom之间的映射关系: 模板---》渲染函数---》虚拟节点树---》渲染页面

为了避免不必要的DOM操作,当状态发生变化时,变化后的虚拟节点会和上一刻的虚拟节点进行对比,找到真正需要更新的虚拟节点进行操作。 vue中的虚拟dom操作是以组件作为一个单位。即状态变化,通知到组件级别,组件内使用虚拟DOM渲染视图

在vue中,有一个Vnode类,他可以实例化不同的Vnode类,每个实例对应一个具体的DOM元素,本质上vnode实例只是一个js对象 在vue中,对比两个vnode,并且渲染新的视图的操作叫做patch,也称之为patch算法。主要做:创建/修改/删除节点等相关操作

11.10. vue中的模板编译 ​

在vue中,使用模板来描述状态和dom之间的映射关系, 过程: 模板--》模板编译--》渲染函数--》vnode--》ui界面

模板变为渲染函数的过程,我们称之为模板编译, 模板编译主要做三件事: 将模板解析为AST(抽象语法树)---》解析器 遍历AST标记静态节点---》优化器 使用AST生成渲染函数---》代码生成器

11.11. computed原理 ​

仍然是通过dep收集依赖,watcher监听通知并更新视图

11.12. watch原理 ​

watch实际上是对watcher一种封装 Watcher是在双向绑定是链接数据和ui的一个中间层, Watcher作用为监听数据变化,当watch被执行时,会执行一个get方法。里面会执行getter方法,即触发Object.defineProperty中定义get,然后就即自动的添加到dep数组中,并且在set执行时进行触发

12. 事件对象坐标问题? ​

事件对象:e e.screenX e.screenY 鼠标相对屏幕的左上角的坐标(包含了浏览器的地址栏和选项卡,收藏栏等),

同时,不受滚动条影响。

e.clientX,e.clientY(与e.x,e.y等价) 鼠标相对屏幕的左上角的坐标(不包含浏览器的地址栏和选项卡,收藏栏等)

同时,不受滚动条影响。

e.pageX,e.pageY 鼠标相对document文档左上角的位置(不包含浏览器的地址栏和选项卡,收藏栏等,但是会含有滚动条滚动的距离值)

e.offsetX,e.offsetY 鼠标相对于当前元素的左上角的位置坐标(没有包含边框大小)

e.layerX,e.layerY 鼠标相对于当前元素的左上角的位置坐标(包含了边框大小)

e.target.offsetTop,e.offsetLeft 当前元素的左上角 相对 于父元素的左上角的坐标(如果没有父元素那就是相对于最外层左上角)

e.target.offsetWidth , e.target.offsetHeight 当前元素的宽高大小,包含了padding,border

13. 直接获取元素位置,坐标,大小问题 ​

js
element.getBoundingClientRect()

//x/y:元素的左上角和父元素左上角的距离

//width/height:边框 + 内边距 + 内容框

//top:元素的上边界和父元素上边界的距离

//left:元素的左边界和父元素左边界的距离

//right:元素的右边界和父元素的左边界的距离

//bottom:元素的下边界和父元素上边界的距离

14. 项目业务问题? ​

14.1. 映像最深的项目? ​

mes系统

14.2. 这个项目技术栈是什么? ​

(react+redux+router) + electon + axios + nodejs-puppeteer做e2e测试 + echarts做图表 + socket做推送 + jwt token(没有使用session机制)

14.3. 你在这个项目中负责什么? ​

前端页面,数据展示,存在于前端的业务逻辑等都是我负责

14.4. 你遇到的技术难点是什么(业务能力体现)? ​

所谓业务能力:就是对软件系统业务功能的处理与实现能力

14.4.1. 权限管理 ​

路由权限 + UI权限

路由权限:

先分角色,每个角色设置相关权限 在关键的路由跳转的页面中,给上权限属性 在所有路由跳转前,进行拦截(类似于beforeEach中判断是否允许通过) UI权限:

通过类似于自定义指令或高阶组件的形式实现,UI层的控制 Vue+angular ---》 通过自定义指令判断 React可以通过高阶组件,这个高阶组件接收一个组件参数,然后注入一个权限验证方法,方法中获取传入的props参数,同时通过ref拿到当前组件节点DOM元素,最后根据props来决定具体的DOM处理方式。

14.4.2. 后端信息推送 ​

Socket.io

4.4.3. 系统心跳检测 ​

定时器

Socket.io

14.4.4. GraphQL ​

14.4.5. 数据加密验证 ​

14.4.6. 富文本上传失败 ​

14.4.7. 跨域操作header ​

14.4.8. c++打印机驱动 ​

14.4.9. 离线缓存 ​

14.4.10. 自动登录如何实现 ​

方式一:

把用户信息username,sessionid之类的使用公钥进行加密放入cookie中,下次打开登录页面时,自动发到后端,后端进行校验

14.4.11. 进入页面时根据token是否有效绝对是否同时起多个请求 ​

14.4.12. OAuth2.0 中访问令牌(access_token)和刷新令牌(refresh_token ) ​

refresh_token存在的目的是为了获取到access_token

14.4.13. 登录时的身份验证 ​