最新整理的25道前端面试真题(含答案与解析)

Hi~ 很久不见。javascript

上一次整理的100道前端面试题在掘金火了以后,不少小伙伴反馈看答案不方便,其实主要是由于我整理的答案和解析内容很是全面,致使每一道题的篇幅都很长,阅读体验不太好,因此才给你们把答案放到github上。css

最近解锁了掘金的新功能——折叠内容,我将在这篇文章中尽量多的的放置答案和解析。html

1.如何获取 html 元素实际的样式值?

公司:京东前端

分类:JavaScriptvue

查看解析

代码实现

实际的样式值 能够理解为 浏览器的计算样式java

style 对象中包含支持 style 属性的元素为这个属性设置的样式信息,但不包含从其余样式表层叠继承的一样影响该元素的样式信息。node

DOM2 Style 在 document.defaultView 上增长了 getComputedStyle() 方法。这个方法接收两个参数:要取得计算样式的元素和伪元素字符串(如":after")。若是不须要查询伪元素,则第二个参数能够传 null。getComputedStyle()方法返回一个 CSSStyleDeclaration 对象(与 style 属性的类型同样),包含元素的计算样式。react

<!DOCTYPE html>
<html>
  <head>
    <title>Computed Styles Example</title>
    <style type="text/css"> #myDiv { background-color: blue; width: 100px; height: 200px; } </style>
  </head>
  <body>
    <div id="myDiv" style="background-color: red; border: 1px solid black" ></div>
  </body>
  <script>
    let myDiv = document.getElementById("myDiv");
    let computedStyle = document.defaultView.getComputedStyle(myDiv, null);

    console.log(computedStyle.backgroundColor); // "red"
    console.log(computedStyle.width); // "100px"
    console.log(computedStyle.height); // "200px"
    console.log(computedStyle.border); // "1px solid black"(在某些浏览器中)

    /* 兼容写法 */
    function getStyleByAttr(obj, name) {
       return window.getComputedStyle
         ? window.getComputedStyle(obj, null)[name]
         : obj.currentStyle[name];
     }
     let node = document.getElementById("myDiv");
     console.log(getStyleByAttr(node, "backgroundColor"));
     console.log(getStyleByAttr(node, "width"));
     console.log(getStyleByAttr(node, "height"));
     console.log(getStyleByAttr(node, "border
  </script>
</html>
复制代码

2.说一下对 React Hook 的理解,它的实现原理,和生命周期有哪些区别?

公司:高德、头条nginx

分类:Reactgit

查看解析

1、React Hook

1.1 什么是 React Hook

Hook 是 React 16.8 的新增特性。它可让你在不编写 class 的状况下使用 state 以及其余的 React 特性。

从官网的这句话中,咱们能够明确的知道,Hook 增长了函数式组件中state的使用,在以前函数式组件是没法拥有本身的状态,只能经过props以及context来渲染本身的UI,而在业务逻辑中,有些场景必需要使用到state,那么咱们就只能将函数式组件定义为class组件。而如今经过Hook,咱们能够轻松的在函数式组件中维护咱们的状态,不须要更改成class组件。

React16.8 加入 hooks,让 React 函数式组件更加灵活

hooks 以前,React 存在不少问题

  1. 在组件间复用状态逻辑很难
  2. 复杂组件变得难以理解,高阶组件和函数组件的嵌套过深。
  3. class 组件的 this 指向问题
  4. 难以记忆的生命周期

hooks 很好的解决了上述问题,hooks 提供了不少方法

  1. useState 返回有状态值,以及更新这个状态值的函数
  2. useEffect 接受包含命令式,可能有反作用代码的函数。
  3. useContext 接受上下文对象(从 React.createContext 返回的值)并返回当前上下文值,
  4. useReducer useState 的替代方案。接受类型为(state,action) => newState 的 reducer,并返回与 dispatch 方法配对的当前状态。
  5. useCallback 返回一个回忆的 memoized 版本,该版本仅在其中一个输入发生更改时才会更改。纯函数的输入输出肯定性
  6. useMemo 纯的一个记忆函数
  7. useRef 返回一个可变的 ref 对象,其.current 属性被初始化为传递的参数
  8. useImperativeMethods 自定义使用 ref 时公开给父组件的实例值
  9. useMutationEffect 更新兄弟组件以前,它在 React 执行其 DOM 改变的同一阶段同步触发
  10. useLayoutEffect DOM 改变后同步触发。使用它来从 DOM 读取布局并同步从新渲染

1.2.React Hook 要解决什么问题

React Hooks要解决的问题是状态共享,这里的状态共享是指只共享状态逻辑复用,并非指数据之间的共享。咱们知道在React Hooks以前,解决状态逻辑复用问题,咱们一般使用higher-order componentsrender-props

既然已经有了这两种解决方案,为何React开发者还要引入React Hook?对于higher-order componentsrender-propsReact Hook的优点在哪?

PS:Hook 最大的优点其实仍是对于状态逻辑的复用便捷,还有代码的简洁,以及帮助函数组件加强功能,

咱们先来看一下React官方给出的React Hookdemo

import { useState } from "React";

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);

  return (
    <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> </div>
  );
}
复制代码

咱们再来看看不用React Hook的话,如何实现

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }

  render() {
    return (
      <div> <p>You clicked {this.state.count} times</p> <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> </div>
    );
  }
}
复制代码

能够看到,在React Hook中,class Example组件变成了函数式组件,可是这个函数式组件却拥有的本身的状态,同时还能够更新自身的状态。这一切都得益于useState这个HookuseState 会返回一对值:当前状态和一个让你更新它的函数,你能够在事件处理函数中或其余一些地方调用这个函数。它相似 class 组件的 this.setState,可是它不会把新的 state 和旧的 state 进行合并

1.3 实现原理

Hooks 的基本类型:

type Hooks = {
  memoizedState: any, // 指向当前渲染节点 Fiber
  baseState: any, // 初始化 initialState, 已经每次 dispatch 以后 newState
  baseUpdate: Update<any> | null, // 当前须要更新的 Update ,每次更新完以后,会赋值上一个 update,方便 react 在渲染错误的边缘,数据回溯
  queue: UpdateQueue<any> | null, // UpdateQueue 经过
  next: Hook | null, // link 到下一个 hooks,经过 next 串联每一 hooks
};

type Effect = {
  tag: HookEffectTag, // effectTag 标记当前 hook 做用在 life-cycles 的哪个阶段
  create: () => mixed, // 初始化 callback
  destroy: (() => mixed) | null, // 卸载 callback
  deps: Array<mixed> | null,
  next: Effect, // 同上
};
复制代码

React Hooks 全局维护了一个 workInProgressHook 变量,每一次调取 Hooks API 都会首先调取 createWorkInProgressHooks 函数。

function createWorkInProgressHook() {
  if (workInProgressHook === null) {
    // This is the first hook in the list
    if (firstWorkInProgressHook === null) {
      currentHook = firstCurrentHook;
      if (currentHook === null) {
        // This is a newly mounted hook
        workInProgressHook = createHook();
      } else {
        // Clone the current hook.
        workInProgressHook = cloneHook(currentHook);
      }
      firstWorkInProgressHook = workInProgressHook;
    } else {
      // There's already a work-in-progress. Reuse it.
      currentHook = firstCurrentHook;
      workInProgressHook = firstWorkInProgressHook;
    }
  } else {
    if (workInProgressHook.next === null) {
      let hook;
      if (currentHook === null) {
        // This is a newly mounted hook
        hook = createHook();
      } else {
        currentHook = currentHook.next;
        if (currentHook === null) {
          // This is a newly mounted hook
          hook = createHook();
        } else {
          // Clone the current hook.
          hook = cloneHook(currentHook);
        }
      }
      // Append to the end of the list
      workInProgressHook = workInProgressHook.next = hook;
    } else {
      // There's already a work-in-progress. Reuse it.
      workInProgressHook = workInProgressHook.next;
      currentHook = currentHook !== null ? currentHook.next : null;
    }
  }
  return workInProgressHook;
}
复制代码

假设咱们须要执行如下 hooks 代码:

function FunctionComponet() {

  const [ state0, setState0 ] = useState(0);
  const [ state1, setState1 ] = useState(1);
  useEffect(() => {
  	document.addEventListener('mousemove', handlerMouseMove, false);
    ...
    ...
    ...
    return () => {
      ...
      ...
      ...
    	document.removeEventListener('mousemove', handlerMouseMove, false);
    }
  })

  const [ satte3, setState3 ] = useState(3);
  return [state0, state1, state3];
}
复制代码

当咱们了解 React Hooks 的简单原理,获得 Hooks 的串联不是一个数组,可是是一个链式的数据结构,从根节点 workInProgressHook 向下经过 next 进行串联。这也就是为何 Hooks 不能嵌套使用,不能在条件判断中使用,不能在循环中使用。不然会破坏链式结构。

2、和生命周期的区别

函数组件 的本质是函数,没有 state 的概念的,所以不存在生命周期一说,仅仅是一个 render 函数而已。

可是引入 Hooks 以后就变得不一样了,它能让组件在不使用 class 的状况下拥有 state,因此就有了生命周期的概念,所谓的生命周期其实就是 useState、 useEffect() 和 useLayoutEffect() 。

即:Hooks 组件(使用了 Hooks 的函数组件)有生命周期,而函数组件(未使用 Hooks 的函数组件)是没有生命周期的。

下面,是具体的 class 与 Hooks 的生命周期对应关系:

hook & lifcycle

3.移动端适配方案具体实现以及对比

公司:头条

分类:Css

查看解析

常见的移动端适配方案

  • media queries
  • flex 布局
  • rem + viewport
  • vh vw
  • 百分比

1、Meida Queries

meida queries 的方式能够说是我早期采用的布局方式,它主要是经过查询设备的宽度来执行不一样的 css 代码,最终达到界面的配置。

核心语法:

@media only screen and (max-width: 374px) {
  /* iphone5 或者更小的尺寸,以 iphone5 的宽度(320px)比例设置样式*/
}
@media only screen and (min-width: 375px) and (max-width: 413px) {
  /* iphone6/7/8 和 iphone x */
}
@media only screen and (min-width: 414px) {
  /* iphone6p 或者更大的尺寸,以 iphone6p 的宽度(414px)比例设置样式 */
}
复制代码

优势:

  • media query 能够作到设备像素比的判断,方法简单,成本低,特别是针对移动端和 PC 端维护同一套代码的时候。目前像 Bootstrap 等框架使用这种方式布局
  • 图片便于修改,只需修改 css 文件
  • 调整屏幕宽度的时候不用刷新页面便可响应式展现

缺点:

  • 代码量比较大,维护不方便
  • 为了兼顾大屏幕或高清设备,会形成其余设备资源浪费,特别是加载图片资源
  • 为了兼顾移动端和 PC 端各自响应式的展现效果,不免会损失各自特有的交互方式

2、Flex 弹性布局

以天猫的实现方式进行说明:

它的 viewport 是固定的:<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">

高度定死,宽度自适应,元素都采用 px 作单位。

随着屏幕宽度变化,页面也会跟着变化,效果就和 PC 页面的流体布局差很少,在哪一个宽度须要调整的时候使用响应式布局调调就行(好比网易新闻),这样就实现了『适配』。

3、rem+viewport 缩放

实现原理:

根据 rem 将页面放大 dpr 倍, 而后 viewport 设置为 1/dpr.

  • 如 iphone6 plus 的 dpr 为 3, 则页面总体放大 3 倍, 1px(css 单位)在 plus 下默认为 3px(物理像素)
  • 而后 viewport 设置为 1/3, 这样页面总体缩回原始大小. 从而实现高清。

这样整个网页在设备内显示时的页面宽度就会等于设备逻辑像素大小,也就是 device-width。这个 device-width 的计算公式为:

设备的物理分辨率/(devicePixelRatio * scale),在 scale 为 1 的状况下,device-width = 设备的物理分辨率/devicePixelRatio

4、rem 实现

rem是相对长度单位,rem方案中的样式设计为相对于根元素font-size计算值的倍数。根据屏幕宽度设置html标签的font-size,在布局时使用 rem 单位布局,达到自适应的目的。

viewport 是固定的:<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">

经过如下代码来控制 rem 基准值(设计稿以 720px 宽度量取实际尺寸)

!(function (d) {
  var c = d.document;
  var a = c.documentElement;
  var b = d.devicePixelRatio;
  var f;
  function e() {
    var h = a.getBoundingClientRect().width,
      g;
    if (b === 1) {
      h = 720;
    }
    if (h > 720) h = 720; //设置基准值的极限值
    g = h / 7.2;
    a.style.fontSize = g + "px";
  }
  if (b > 2) {
    b = 3;
  } else {
    if (b > 1) {
      b = 2;
    } else {
      b = 1;
    }
  }
  a.setAttribute("data-dpr", b);
  d.addEventListener(
    "resize",
    function () {
      clearTimeout(f);
      f = setTimeout(e, 200);
    },
    false
  );
  e();
})(window);
复制代码

css 经过 sass 预编译,设置量取的 px 值转化 rem 的变量$px: (1/100)+rem;

优势:

  • 兼容性好,页面不会由于伸缩发生变形,自适应效果更佳。

缺点:

  • 不是纯 css 移动适配方案,须要在头部内嵌一段 js脚本监听分辨率的变化来动态改变根元素的字体大小,css样式和 js 代码有必定耦合性,而且必须将改变font-size的代码放在 css 样式以前。
  • 小数像素问题,浏览器渲染最小的单位是像素,元素根据屏幕宽度自适应,经过 rem 计算后可能会出现小数像素,浏览器会对这部分小数四舍五入,按照整数渲染,有可能没那么准确。

5、纯 vw 方案

视口是浏览器中用于呈现网页的区域。

  • vw : 1vw 等于 视口宽度1%
  • vh : 1vh 等于 视口高度 的 **1% **
  • vmin : 选取 vwvh最小 的那个
  • vmax : 选取 vwvh最大 的那个

虽然 vw 能更优雅的适配,可是仍是有点小问题,就是宽,高无法限制。

$base_vw = 375;
@function vw ($px) {
    return ($px/$base_vw) * 100vw
};
复制代码

优势:

  • css 移动端适配方案,不存在脚本依赖问题。
  • 相对于 rem 以根元素字体大小的倍数定义元素大小,逻辑清晰简单。

缺点:

  • 存在一些兼容性问题,有些浏览器不支持

6、vw + rem 方案

// scss 语法
// 设置html根元素的大小 750px->75 640px->64
// 将屏幕分红10份,每份做为根元素的大小。
$vw_fontsize: 75
@function rem($px) {
    // 例如:一个div的宽度为100px,那么它对应的rem单位就是(100/根元素的大小)* 1rem
    @return ($px / $vw_fontsize) * 1rem;
}
$base_design: 750
html {
    // rem与vw相关联
    font-size: ($vw_fontsize / ($base_design / 2)) * 100vw;
    // 同时,经过Media Queries 限制根元素最大最小值
    @media screen and (max-width: 320px) {
        font-size: 64px;
    }
    @media screen and (min-width: 540px) {
        font-size: 108px;
    }
}

// body 也增长最大最小宽度限制,避免默认100%宽度的 block 元素跟随 body 而过大太小
body {
    max-width: 540px;
    min-width: 320px;
}

复制代码

7、百分比

使用百分比%定义宽度,高度用px固定,根据可视区域实时尺寸进行调整,尽量适应各类分辨率,一般使用max-width/min-width控制尺寸范围过大或者太小。

优势:

  • 原理简单,不存在兼容性问题

缺点:

  • 若是屏幕尺度跨度太大,相对设计稿过大或者太小的屏幕不能正常显示,在大屏手机或横竖屏切换场景下可能会致使页面元素被拉伸变形,字体大小没法随屏幕大小发生变化。
  • 设置盒模型的不一样属性时,其百分比设置的参考元素不惟一,容易使布局问题变得复杂。

4.var arr =[[‘A’,’B’],[‘a’,’b’],[1,2]] 求二维数组的全排列组合 结果:Aa1,Aa2,Ab1,Ab2,Ba1,Ba2,Bb1,Bb2

公司:美团

分类:算法

查看解析

参考代码实现

  • 实现方式一
function foo(arr) {
  // 用于记录初始数组长度, 用于将数组前两组已经获取到全排列的数组进行截取标识
  var len = arr.length;
  // 当递归操做后, 数组长度为1时, 直接返回arr[0], 只有大于1继续处理
  if (len >= 2) {
    // 每次只作传入数组的前面两个数组进行全排列组合, 即arr[0]和arr[1]的全排列组合
    var len1 = arr[0].length;
    var len2 = arr[1].length;
    var items = new Array(len1 * len2); // 建立全排列组合有可能次数的数组
    var index = 0; // 记录每次全排列组合后的数组下标
    for (var i = 0; i < len1; i++) {
      for (var j = 0; j < len2; j++) {
        if (Array.isArray(arr[0])) {
          // 当第二次进来后, 数组第一个元素一定是数组包着数组
          items[index] = arr[0][i].concat(arr[1][j]); // 对于已是第二次递归进来的全排列直接追加便可
        } else {
          items[index] = [arr[0][i]].concat(arr[1][j]); // 这里由于只须要去arr[0]和arr[1]的全排列, 因此这里就直接使用concat便可
        }
        index++; // 更新全排列组合的下标
      }
    }
    // 若是数组大于2, 这里新的newArr作一个递归操做
    var newArr = new Array(len - 1); // 递归的数组比传进来的数组长度少一, 由于上面已经将传进来的数组的arr[0]和arr[1]进行全排列组合, 因此这里的newArr[0]就是上面已经全排列好的数组item
    for (var i = 2; i < arr.length; i++) {
      // 这里的for循环是为了截取下标1项后的数组进行赋值给newArr
      newArr[i - 1] = arr[i];
    }
    newArr[0] = items; // 由于上面已经将传进来的数组的arr[0]和arr[1]进行全排列组合, 因此这里的newArr[0]就是上面已经全排列好的数组item
    // 从新组合后的数组进行递归操做
    return foo(newArr);
  } else {
    // 当递归操做后, 数组长度为1时, 直接返回arr[0],
    return arr[0];
  }
}
var arr = [
  ["A", "B"],
  ["a", "b"],
  [1, 2],
];
console.log(foo(arr));
复制代码
  • 实现方式二
const getResult = (arr1, arr2) => {
  if (!Array.isArray(arr1) || !Array.isArray(arr2)) {
    return;
  }
  if (!arr1.length) {
    return arr2;
  }
  if (!arr2.length) {
    return arr1;
  }
  let result = [];
  for (let i = 0; i < arr1.length; i++) {
    for (let j = 0; j < arr2.length; j++) {
      result.push(String(arr1[i]) + String(arr2[j]));
    }
  }
  return result;
};

const findAll = (arr) =>
  arr.reduce((total, current) => {
    return getResult(total, current);
  }, []);
var arr = [
  ["A", "B"],
  ["a", "b"],
  [1, 2],
];
console.log(findAll(arr));
复制代码
  • 实现方式三
var arr = [
  ["A", "B"],
  ["a", "b"],
  [1, 2],
];
let res = [],
  lengthArr = arr.map((d) => d.length);
let indexArr = new Array(arr.length).fill(0);
function addIndexArr() {
  indexArr[0] = indexArr[0] + 1;
  let i = 0;
  let overflow = false;
  while (i <= indexArr.length - 1) {
    if (indexArr[i] >= lengthArr[i]) {
      if (i < indexArr.length - 1) {
        indexArr[i] = 0;
        indexArr[i + 1] = indexArr[i + 1] + 1;
      } else {
        overflow = true;
      }
    }
    i++;
  }
  return overflow;
}
function getAll(arr, indexArr) {
  let str = "";
  arr.forEach((item, index) => {
    str += item[indexArr[index]];
  });
  res.push(str);
  let overflow = addIndexArr();
  if (overflow) {
    return;
  } else {
    return getAll(arr, indexArr);
  }
}
getAll(arr, indexArr);
console.log(res);
复制代码

5.说下工做流程(开发流程、代码规范、打包流程等)

公司:腾讯微视

分类:工程化

查看解析

1、拿到原型图,先自我解析需求,画出思惟导图,流程图

  • 在未拿到 UI 给定的 PSD 时,能够先理清咱们的需求
    • 依赖的外部资源
      • 后端提供的接口
      • UI 出图的大概布局
      • 后期频繁改动的地方
  • 须要实现的效果
    • 下拉刷新
    • 动画效果
    • 吸顶效果
    • 懒加载、预加载、防抖、节流

2、产品召集项目相关人员,开需求讨论会,产品讲解原型

  1. 理解产品的需求,提出质疑:这是什么功能,怎么作,为啥这么作
  2. 评估实现难度和实现成本,是否有潜在技术问题/风险
  3. 对比本身整理的需求图,若是有和本身想的不符合的,提出疑问
  4. 理解 PM 提出这次需求的目的,明白哪些内容是重点,哪些次要,能够适当取舍
  5. 若是产品要求提供时间,简单项目能够预估,复杂项目不可立刻给出时间,须要仔细评估,评估时间包含开发、自测、测试人员测试、修复 bug、上线准备

3、会后进一步整理需求

  1. 细化细节,整理有疑问的地方,与产品、设计等其余人进行确认
  2. 评估项目完成时间--影响因素:须要的人力、 中间插入的需求、 开发、 自测、 测试人员测试、 修复 bug、 上线准备、 其余风险(如技术选型错误等)
  3. 初步制定排期表

4、需求二次确认(开发中遇到不肯定的,依旧须要找相关人员进行需求确认,杜绝作无用功)

  1. IM 工具沟通确认
  2. 邮件确认
  3. 小型需求/项目相关讨论会
  4. 肯定最终排期表

5、开发

  1. 技术选型
  2. 搭建开发环境:工具链
  3. 搭建项目架构
  4. 业务模块划分
    • 优先级排序
    • 新项目介入,须要当前项目和介入项目的相关负责人 Pk 优先级,随后调整项目排期
    • 开发过程当中发现工做量与预期有严重出入,须要尽早向其余项目人员反馈,方便其修改时间安排
  5. 定制开发规范
    • 开发规范
      • commit 提交格式:[改动文件类型]:[改动说明]
      • 单分支开发或者多分支开发
        1. 小项目、并行开发少,则只在 master 主分支开发
        2. 中大项目,需求复杂,并行功能多,则须要分为 master、developer、开发者分支;须要开发者自创一个分支开发,合并到 developer,确认无问题后,发布到 master,最后上线
    • 代码规范
      1. jsconfig.json
      2. .postcssrc.js
      3. .babelrc
      4. .prettierrc(vscode 插件 prettier-code fomatter)— 注意与 eslint 要保持一致
      5. .editorconfig
      6. .eslintrc.js(强制开启验证模式)
    • 源码管理
    • 版本管理
    • 安全管理

6、自测

  1. 手动测试
  2. 单元测试
  3. 集成测试

7、提测---测试人员测试

  1. 开发人员修复 bug
  2. 期间不可接手耗时大的需求
  3. 有不肯定优先级高低的需求,须要各个需求方互相 pk 优先级,再肯定作与不作,不能所以拖延项目完成点
  4. 测试修复 bug 时间可能比开发时间还长,所以开发者预估开发时间不能乐观

8、上线

  1. 上线准备 a. 域名申请 b. 备案申请 c. 服务器申请 d. 部署

  2. 测试线上环境

    • 有 bug 回到修复 bug 环节
  3. 日志监控

    1. 调用栈
    2. sourcemap
    3. 本地日志
    4. 用户环境、IP
    5. 低成本接入
    6. 统计功能
    7. 报警功能

9、维护

  1. 技术创新(对现有的技术领域以及具体项目实现方法进行优化)
    1. 提升效率:例如 jenkins 构建部署
    2. 减小成本
    3. 提高稳定性
    4. 安全性

6.Vue 中父组件能够监听到子组件的生命周期吗?

公司:水滴筹

分类:Vue

查看解析

实现方式

1、使用 on 和 emit

// Parent.vue
<Child @mounted="doSomething"/>
// Child.vue
mounted() {
  this.$emit("mounted");
}
复制代码

2、使用 hook 钩子函数

// Parent.vue
<Child @hook:mounted="doSomething" ></Child>

doSomething() {
   console.log('父组件监听到 mounted 钩子函数 ...');
},
// Child.vue
mounted(){
   console.log('子组件触发 mounted 钩子函数 ...');
},
// 以上输出顺序为:
// 子组件触发 mounted 钩子函数 ...
// 父组件监听到 mounted 钩子函数 ...
复制代码

7.怎么给 Vue 定义全局方法

分类:Vue

查看解析

实现方式

1、将方法挂载到 Vue.prototype 上面

缺点:调用这个方法的时候没有提示

// global.js
const RandomString = (encode = 36, number = -8) => {
  return Math.random() // 生成随机数字, eg: 0.123456
    .toString(encode) // 转化成36进制 : "0.4fzyo82mvyr"
    .slice(number);
},
export default {
	RandomString,
  ...
}
复制代码
// 在项目入口的main.js里配置
import Vue from "vue";
import global from "@/global";
Object.keys(global).forEach((key) => {
  Vue.prototype["$global" + key] = global[key];
});
复制代码
// 挂载以后,在须要引用全局变量的模块处(App.vue),不需再导入全局变量模块,而是直接用this就能够引用了,以下:
export default {
  mounted() {
    this.$globalRandomString();
  },
};
复制代码

2、利用全局混入mixin

优势:由于mixin里面的methods会和建立的每一个单文件组件合并。这样作的优势是调用这个方法的时候有提示

// mixin.js
import moment from 'moment'
const mixin = {
  methods: {
    minRandomString(encode = 36, number = -8) {
      return Math.random() // 生成随机数字, eg: 0.123456
        .toString(encode) // 转化成36进制 : "0.4fzyo82mvyr"
        .slice(number);
    },
    ...
  }
}
export default mixin
复制代码
// 在项目入口的main.js里配置
import Vue from 'vue'
import mixin from '@/mixin'
Vue.mixin(mixin)
复制代码
export default {
 mounted() {
   this.minRandomString()
 }
}
复制代码

3、使用Plugin方式

Vue.use的实现没有挂载的功能,只是触发了插件的install方法,本质仍是使用了Vue.prototype。

// plugin.js
function randomString(encode = 36, number = -8) {
  return Math.random() // 生成随机数字, eg: 0.123456
    .toString(encode) // 转化成36进制 : "0.4fzyo82mvyr"
    .slice(number);
}
const plugin = {
  // install 是默认的方法。
  // 当外界在 use 这个组件或函数的时候,就会调用自己的 install 方法,同时传一个 Vue 这个类的参数。
  install: function(Vue){
    Vue.prototype.$pluginRandomString = randomString
    ...
  },
}
export default plugin
复制代码
// 在项目入口的main.js里配置
import Vue from 'vue'
import plugin from '@/plugin'
Vue.use(plugin)
复制代码
export default {
 mounted() {
   this.$pluginRandomString()
 }
}
复制代码

4、任意 vue 文件中写全局函数

// 建立全局方法
this.$root.$on("test", function () {
  console.log("test");
});
// 销毁全局方法
this.$root.$off("test");
// 调用全局方法
this.$root.$emit("test");
复制代码

8.说一下 vm.$set 原理

公司:极光推送

分类:Vue

查看解析

vm.$set()解决了什么问题

在 Vue.js 里面只有 data 中已经存在的属性才会被 Observe 为响应式数据,若是你是新增的属性是不会成为响应式数据,所以 Vue 提供了一个 api(vm.$set)来解决这个问题。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Vue Demo</title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  </head>
  <body>
    <div id="app">
      {{user.name}} {{user.age}}
      <button @click="addUserAgeField">增长一个年纪字段</button>
    </div>
    <script> const app = new Vue({ el: "#app", data: { user: { name: "test", }, }, mounted() {}, methods: { addUserAgeField() { // this.user.age = 20 这样是不起做用, 不会被Observer this.$set(this.user, "age", 20); // 应该使用 }, }, }); </script>
  </body>
</html>
复制代码

原理

vm.$set()在 new Vue()时候就被注入到 Vue 的原型上。

源码位置: vue/src/core/instance/index.js

import { initMixin } from "./init";
import { stateMixin } from "./state";
import { renderMixin } from "./render";
import { eventsMixin } from "./events";
import { lifecycleMixin } from "./lifecycle";
import { warn } from "../util/index";

function Vue(options) {
  if (process.env.NODE_ENV !== "production" && !(this instanceof Vue)) {
    warn("Vue is a constructor and should be called with the `new` keyword");
  }
  this._init(options);
}

initMixin(Vue);
// 给原型绑定代理属性$props, $data
// 给Vue原型绑定三个实例方法: vm.$watch,vm.$set,vm.$delete
stateMixin(Vue);
// 给Vue原型绑定事件相关的实例方法: vm.$on, vm.$once ,vm.$off , vm.$emit
eventsMixin(Vue);
// 给Vue原型绑定生命周期相关的实例方法: vm.$forceUpdate, vm.destroy, 以及私有方法_update
lifecycleMixin(Vue);
// 给Vue原型绑定生命周期相关的实例方法: vm.$nextTick, 以及私有方法_render, 以及一堆工具方法
renderMixin(Vue);

export default Vue;
复制代码
  • stateMixin()
Vue.prototype.$set = set;
Vue.prototype.$delete = del;
复制代码
  • set()

源码位置: vue/src/core/observer/index.js

export function set(target: Array<any> | Object, key: any, val: any): any {
  // 1.类型判断
  // 若是 set 函数的第一个参数是 undefined 或 null 或者是原始类型值,那么在非生产环境下会打印警告信息
  // 这个api原本就是给对象与数组使用的
  if (
    process.env.NODE_ENV !== "production" &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(
      `Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`
    );
  }
  // 2.数组处理
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // 相似$vm.set(vm.$data.arr, 0, 3)
    // 修改数组的长度, 避免索引>数组长度致使splcie()执行有误
    //若是不设置length,splice时,超过本来数量的index则不会添加空白项
    target.length = Math.max(target.length, key);
    // 利用数组的splice变异方法触发响应式, 这个前面讲过
    target.splice(key, 1, val);
    return val;
  }
  //3.对象,且key不是原型上的属性处理
  // target为对象, key在target或者target.prototype上。
  // 同时必须不能在 Object.prototype 上
  // 直接修改便可, 有兴趣能够看issue: https://github.com/vuejs/vue/issues/6845
  if (key in target && !(key in Object.prototype)) {
    target[key] = val;
    return val;
  }
  // 以上都不成立, 即开始给target建立一个全新的属性
  // 获取Observer实例
  const ob = (target: any).__ob__;
  // Vue 实例对象拥有 _isVue 属性, 即不容许给Vue 实例对象添加属性
  // 也不容许Vue.set/$set 函数为根数据对象(vm.$data)添加属性
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== "production" &&
      warn(
        "Avoid adding reactive properties to a Vue instance or its root $data " +
          "at runtime - declare it upfront in the data option."
      );
    return val;
  }
  //5.target是非响应式数据时
  // target自己就不是响应式数据, 直接赋值
  if (!ob) {
    target[key] = val;
    return val;
  }
  //6.target对象是响应式数据时
  //定义响应式对象
  defineReactive(ob.value, key, val);
  //watcher执行
  ob.dep.notify();
  return val;
}
复制代码
  • 工具函数
// 判断给定变量是不是未定义,当变量值为 null时,也会认为其是未定义
export function isUndef(v: any): boolean %checks {
  return v === undefined || v === null;
}

// 判断给定变量是不是原始类型值
export function isPrimitive(value: any): boolean %checks {
  return (
    typeof value === "string" ||
    typeof value === "number" ||
    // $flow-disable-line
    typeof value === "symbol" ||
    typeof value === "boolean"
  );
}

// 判断给定变量的值是不是有效的数组索引
export function isValidArrayIndex(val: any): boolean {
  const n = parseFloat(String(val));
  return n >= 0 && Math.floor(n) === n && isFinite(val);
}
复制代码
  • 关于(ob && ob.vmCount)
export function observe(value: any, asRootData: ?boolean): Observer | void {
  // 省略...
  if (asRootData && ob) {
    // vue已经被Observer了,而且是根数据对象, vmCount才会++
    ob.vmCount++;
  }
  return ob;
}
复制代码
  • 在初始化 Vue 的过程当中有
export function initState(vm: Component) {
  vm._watchers = [];
  const opts = vm.$options;
  if (opts.props) initProps(vm, opts.props);
  if (opts.methods) initMethods(vm, opts.methods);
  if (opts.data) {
    //opts.data为对象属性
    initData(vm);
  } else {
    observe((vm._data = {}), true /* asRootData */);
  }
  if (opts.computed) initComputed(vm, opts.computed);
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch);
  }
}
复制代码
  • initData(vm)
function initData(vm: Component) {
  let data = vm.$options.data;
  data = vm._data = typeof data === "function" ? getData(data, vm) : data || {};

  // 省略...

  // observe data
  observe(data, true /* asRootData */);
}
复制代码

从源码能够看出 set 主要逻辑以下:

  1. 类型判断
  2. target 为数组:调用 splice 方法
  3. target 为对象,且 key 不是原型上的属性处理:直接修改
  4. target 不能是 Vue 实例,或者 Vue 实例的根数据对象,不然报错
  5. target 是非响应式数据时,咱们就按照普通对象添加属性的方式来处理
  6. target 为响应数据,且key 为新增属性,咱们 key 设置为响应式,并手动触发其属性值的更新

总结

vm.$set(target、key、value)

  • 当 target 为数组时,直接调用数组方法 splice 实现;
  • 若是目标是对象,会先判读属性是否存在、对象是不是响应式
  • 最终若是要对属性进行响应式处理,则是经过调用 defineReactive 方法进行响应式处理
    • defineReactive 方法就是 Vue 在初始化对象时,给对象属性采用 Object.defineProperty 动态添加 getter 和 setter 的功能所调用的方法

9.深拷贝如何解决循环引用?

公司:极光推送

分类:JavaScript

查看解析

循环引用问题

看个例子

function deepCopy(obj){
    const res = Array.isArray(obj) ? [] : {};
    for(let key in obj){
        if(typeof obj[key] === 'object'){
            res[key] = deepCopy(obj[key]);
        }else{
            res[key] = obj[key];
        }
    }
    return res
}
var obj = {
    a:1,
    b:2,
    c:[1,2,3],
    d:{aa:1,bb:2},
};
obj.e = obj;
console.log('obj',obj); // 不会报错

const objCopy = deepCopy(obj);
console.log(objCopy); //Uncaught RangeError: Maximum call stack size exceeded
复制代码

从例子能够看到,当存在循环引用的时候,deepCopy会报错,栈溢出。

  • obj对象存在循环引用时,打印它时是不会栈溢出
  • 深拷贝obj时,才会致使栈溢出

循环应用问题解决

  • 即:目标对象存在循环应用时报错处理

你们都知道,对象的key是不能是对象的。

{{a:1}:2}
// Uncaught SyntaxError: Unexpected token ':'
复制代码

参考解决方式一:使用weekmap:

解决循环引用问题,咱们能够额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系

这个存储空间,须要能够存储key-value形式的数据,且key能够是一个引用类型,

咱们能够选择 WeakMap  这种数据结构:

  • 检查 WeakMap  中有无克隆过的对象
  • 有,直接返回
  • 没有,将当前对象做为key,克隆对象做为value进行存储
  • 继续克隆
function isObject(obj) {
    return (typeof obj === 'object' || typeof obj === 'function') && obj !== null
}
function cloneDeep(source, hash = new WeakMap()) {
  if (!isObject(source)) return source;
  if (hash.has(source)) return hash.get(source); // 新增代码,查哈希表

  var target = Array.isArray(source) ? [] : {};
  hash.set(source, target); // 新增代码,哈希表设值

  for (var key in source) {
    if (Object.prototype.hasOwnProperty.call(source, key)) {
      if (isObject(source[key])) {
        target[key] = cloneDeep(source[key], hash); // 新增代码,传入哈希表
      } else {
        target[key] = source[key];
      }
    }
  }
  return target;
}
复制代码

参考解决方式二:

能够用 Set,发现相同的对象直接赋值,也可用 Map

const o = { a: 1, b: 2 };
o.c = o;

function isPrimitive(val) {
    return Object(val) !== val;
}
const set = new Set();
function clone(obj) {
    const copied = {};
    for (const [key, value] of Object.entries(obj)) {
        if (isPrimitive(value)) {
            copied[key] = value;
        } else {
            if (set.has(value)) {
                copied[key] = { ...value };
            } else {
                set.add(value);
                copied[key] = clone(value);
            }
        }
    }
    return copied;
}
复制代码

10.单元测试如何测试?代码覆盖率如何?

公司:编程猫

分类:工程化

查看解析

1、为何要单元测试?

有单元测试加持能够保障交付代码质量,加强本身和他人的信心。咱们选择第三方库的时候不也是会优先选择有测试保障的吗?将来对代码进行改动时也能够节省回归测试的时间。

2、怎么测?

在作单元测试时尽可能以集成测试为主,对少许难以被集成测试覆盖或须要分发的代码作单元测试,同时也能够有少许的端到端测试辅助。

尽可能不测试代码实现,测试代码实现可能会让测试用例很快就失效。好比断言变量,当变量名发生变动时会致使测试不经过,可是可能功能并无发生改变。

2.1 要写些什么样的测试?

以用户视角测试程序的功能,而非上帝视角。

对一个组件,传入不一样参数渲染 dom ,对用户而言可能能够看到某些特定文字或能够作某些操做。此时能够断言 dom 是否出现了某些字,作动做(如点击、输入 、提交表单等)是否有正确的响应。

2.2 不要写什么样的测试?

不要测试实现细节。好比以上帝视角检查  redux store  上的数据、state  的数据等,而这些在最终用户眼里是不存在的,用户能感知的只是所表现的功能。

3、测试框架和周边配套

Jest  是 facebook 出品的测试框架。开箱即用,自带断言库、mock、覆盖率报告等功能。

因为前端要测试 UI,须要在模拟浏览器环境中渲染出 dom,因此须要一个这样的库。存在不少这样的库,经常使用的有  Enzyme@testing-library/react

4、测试覆盖/效率报告

Jest  自带测试报告,可是众多的项目分散在 gitlab 中给查看报告带来了麻烦。须要考虑有一个集中的地方查看测试报告。这里结合了  sonar  和  reportportal  归集测试报告,能够经过一个集中的地方查看全部项目的测试报告。

其中结合  sonar  的代码扫描功能能够查看测试覆盖率等报告信息。reportportal  能够查看测试执行率,另外官方宣称自带 AI 分析报告,能够得出多维度的统计信息。

11.说说 React 状态逻辑复用问题

公司:编程猫

分类:React

查看解析

React 状态逻辑复用

1、Mixins

虽然 React 自己有些函数式味道,但为了迎合用户习惯,早期只提供了 React.createClass() API 来定义组件: 天然而然地,(类)继承就成了一种直觉性的尝试。而在 JavaScript 基于原型的扩展模式下,相似于继承的 Mixin 方案就成了首选:

// 定义Mixin
var Mixin1 = {
  getMessage: function () {
    return "hello world";
  },
};
var Mixin2 = {
  componentDidMount: function () {
    console.log("Mixin2.componentDidMount()");
  },
};
// 用Mixin来加强现有组件
var MyComponent = React.createClass({
  mixins: [Mixin1, Mixin2],
  render: function () {
    return <div>{this.getMessage()}</div>;
  },
});
复制代码

但存在诸多缺陷

组件与 Mixin 之间存在隐式依赖(Mixin 常常依赖组件的特定方法,但在定义组件时并不知道这种依赖关系)多个 Mixin 之间可能产生冲突(好比定义了相同的 state 字段)Mixin 倾向于增长更多状态,这下降了应用的可预测性(The more state in your application, the harder it is to reason about it.),致使复杂度剧增。

隐式依赖致使依赖关系不透明,维护成本和理解成本迅速攀升:难以快速理解组件行为,须要全盘了解全部依赖 Mixin 的扩展行为,及其之间的相互影响。组件自身的方法和 state 字段不敢轻易删改,由于难以肯定有没有 Mixin 依赖它 Mixin 也难以维护,由于 Mixin 逻辑最后会被打平合并到一块儿,很难搞清楚一个 Mixin 的输入输出。

毫无疑问,这些问题是致命的 因此,React v0.13.0 放弃了 Mixin(继承),转而走向 HOC(组合)。

2、Higher - Order Components

// 定义高阶组件
var Enhance = (ComposedComponent) =>
  class extends Component {
    constructor() {
      this.state = { data: null };
    }
    componentDidMount() {
      this.setState({ data: "Hello" });
    }
    render() {
      return <ComposedComponent {...this.props} data={this.state.data} />;
    }
  };
class MyComponent {
  render() {
    if (!this.data) return <div>Waiting...</div>;
    return <div>{this.data}</div>;
  }
}
// 用高阶组件来加强普通组件,进而实现逻辑复用
export default Enhance(MyComponent);
复制代码

理论上,只要接受组件类型参数并返回一个组件的函数都是高阶组件((Component, ...args) => Component),但为了方便组合,推荐Component => Component形式的 HOC,经过偏函数应用来传入其它参数,例如:React Redux's connect const ConnectedComment = connect(commentSelector, commentActions)(CommentList);

优势:

  • 组件树结构 下降耦合和复杂度;
  • 代码复用,逻辑抽象化
  • 渲染劫持,属性代理,劫持组件的 props 和 state
  • 装饰器,能够做为装饰器来使用;
  • 函数柯里化

缺点:HOC 虽然没有那么多致命问题,但也存在一些小缺陷:

  • 扩展性限制
  • 不要在 render 中使用,每次 render 会从新建立一个高阶组件,致使组件和子组件状态丢失,影响性能;
  • 静态方法会丢失,新组件没有静态方法,须要手动处理;
  • refs 不会往下传递,须要使用 forwardRef
  • 屡次嵌套,增长复杂度和理解成本;
  • 未使用命名空间的话,可能出现命名冲突,覆盖旧属性;
  • 不可见性,不知道外面包了啥,黑盒;

3、Render Props

“render prop” 是指⼀种在 React 组件之间使⽤⼀个值为函数的 prop 共享代码的简单技术;

class Mouse extends React.Component {
  constructor(props) {
    super(props);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.state = { x: 0, y: 0 };
  }
  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY,
    });
  }
  render() {
    return (
      <div style={{ height: "100%" }} onMouseMove={this.handleMouseMove}> {/* Instead of providing a static representation of what <Mouse> renders, use the `render` prop to dynamically determine what to render. */} {this.props.render(this.state)} </div> ); } } 复制代码

优势:数据共享、代码复⽤,将组件内的 state 做为 props 传递给调⽤者,将渲染逻辑交给调⽤者

缺点:⽆法在 return 语句外访问数据、嵌套写法不够优雅;

4、Hooks

function MyResponsiveComponent() {
  const width = useWindowWidth();
  // Our custom Hook
  return <p>Window width is {width}</p>;
}
function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);
  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener("resize", handleResize);
    return () => {
      window.removeEventListener("resize", handleResize);
    };
  });
  return width;
}
复制代码

比起上面提到的其它方案,Hooks 让组件内逻辑复用再也不与组件复用捆绑在一块儿,是真正在从下层去尝试解决(组件间)细粒度逻辑的复用问题。

此外,这种声明式逻辑复用方案将组件间的显式数据流与组合思想进一步延伸到了组件内,契合 React 理念。

优势以下:

  • 解决嵌套问题,简洁,代码量更少: React Hooks 解决了 HOC 和 Render Props 的嵌套问题,更加简洁
  • 解耦: React Hooks 能够更方便地把 UI 和状态分离,作到更完全的解耦
  • 组合: Hooks 中能够引用另外的 Hooks 造成新的 Hooks,组合变化万千
  • 解决类组件的 3 个问题: React Hooks 为函数组件而生,从而解决了类组件的几大问题:
    • this 指向容易错误
    • 业务逻辑被分割在不一样声明周期中,使得代码难以理解和维护
    • 代码复用成本高(高阶组件容易使代码量剧增)

Hooks 也并不是完美,只是就目前而言,其缺点以下:

  • 还有两个类组件的生命周期函数不能用 hooks 替代,getSnapshotBeforeUpdate 和 componentDidCatch
  • 额外的学习成本(Functional Component 与 Class Component 之间的困惑)
  • 写法上有限制(不能在条件、循环、嵌套函数中使用),只能在函数顶层使用,增长了重构旧代码的成本;由于 react 须要利用调用顺序来更新状态和调用钩子函数;放到循环或条件分支中,可能致使调用顺序不一致,致使奇怪的 bug;
  • 破坏了 PureComponent、React.memo 浅比较的性能优化效果(为了取最新的 props 和 state,每次 render()都要从新建立事件处函数)
  • 在闭包场景可能会引用到旧的 state、props 值内部实现上不直观(依赖一份可变的全局状态,再也不那么“纯”)
  • React.memo 并不能彻底替代 shouldComponentUpdate(由于拿不到 state change,只针对 props change)
  • useState API 设计上不太完美
  • 使用 useState 时,数组对象,使用 push、pop、splice 直接更新,无效;好比 let [nums, setNums] = useState([0,1,2]); nums.push(1) 无效,必须使用 nums=[...nums, 1],再 setNums(nums);类组件中直接 push 是没问题的
  • 不能使用装饰器
  • 函数组件 ref 须要 forwardRef

12.组件库设计有什么原则?

公司:编程猫

分类:JavaScript

查看解析

组件库设计原则

1.1 标准性

任何一个组件都应该遵照一套标准,能够使得不一样区域的开发人员据此标准开发出一套标准统一的组件

1.2 独立性

  • 描述了组件的细粒度,遵循单一职责原则,保持组件的纯粹性
  • 属性配置等API对外开放,组件内部状态对外封闭,尽量的少与业务耦合

1.3 复用与易用

  • UI差别,消化在组件内部(注意并非写一堆if/else)
  • 输入输出友好,易用

1.4 适用SPOT法则

Single Point Of Truth,就是尽可能不要重复代码,出自《The Art of Unix Programming》

1.5 避免暴露组件内部实现

1.6 避免直接操做DOM,避免使用ref

使用父组件的 state 控制子组件的状态而不是直接经过 ref 操做子组件

1.7 入口处检查参数的有效性,出口处检查返回的正确性

1.8 无环依赖原则(ADP)

1.9 稳定抽象原则(SAP)

  • 组件的抽象程度与其稳定程度成正比,
  • 一个稳定的组件应该是抽象的(逻辑无关的)
  • 一个不稳定的组件应该是具体的(逻辑相关的)
  • 为下降组件之间的耦合度,咱们要针对抽象组件编程,而不是针对业务实现编程

1.10 避免冗余状态

  • 若是一个数据能够由另外一个 state 变换获得,那么这个数据就不是一个 state,只须要写一个变换的处理函数,在 Vue 中能够使用计算属性
  • 若是一个数据是固定的,不会变化的常量,那么这个数据就如同 HTML 固定的站点标题同样,写死或做为全局配置属性等,不属于 state
  • 若是兄弟组件拥有相同的 state,那么这个state 应该放到更高的层级,使用 props 传递到两个组件中

1.11合理的依赖关系

父组件不依赖子组件,删除某个子组件不会形成功能异常

1.12 扁平化参数

除了数据,避免复杂的对象,尽可能只接收原始类型的值

1.13 良好的接口设计

  • 把组件内部能够完成的工做作到极致,虽然提倡拥抱变化,但接口不是越多越好
  • 若是常量变为 props 能应对更多的场景,那么就能够做为 props,原有的常量可做为默认值。
  • 若是须要为了某一调用者编写大量特定需求的代码,那么能够考虑经过扩展等方式构建一个新的组件。
  • 保证组件的属性和事件足够的给大多数的组件使用。

1.14 API尽可能和已知概念保持一致

13.写出代码输出值,并说明缘由

function F() {
  this.a = 1;
}
var obj = new F();
console.log(obj.prototype);
复制代码

公司:富途

分类:JavaScript

查看解析

答案

undefined
复制代码

参考分析

构造函数实例通常没有prototype属性。除了Function构造函数

只有函数才有prototype属性,这个属性值为一个object对象 实例对象时没有这个属性

实例对象经过__proto__这个内部属性([[prototype]])来串起一个原型链的,经过这个原型链能够查找属性,

方法 经过new操做符初始化一个函数对象的时候就会构建出一个实例对象,

函数对象的prototype属性指向的对象就是这个实例对象的原型对象,也就是__proto__指向的对象

经典与原型链图

prototype

14.把 10 万次 for 循环的代码插到 html 中间,会有什么现象?出现卡顿现象怎么解决?添加 defer 属性以后脚本会在何时执行?采用 defer 以后,用户点击页面会怎么样?若是禁用 WebWoker,还有其余方法吗?

公司:富途

分类:JavaScript

查看解析

1、十万次循环代码插入 body 中,页面会出现卡顿

十万次循环代码插入 body 中,页面会出现卡顿,代码后的 DOM 节点加载不出来

2、解决

设置 script 标签 defer 属性,浏览器其它线程将下载脚本,待到文档解析完成脚本才会执行。

3、采用 defer 以后,用户点击问题

  • 若 button 中的点击事件在 defer 脚本前定义,则在 defer 脚本加载完后,响应点击事件。

  • 若 button 中的点击事件在 defer 脚本后定义,则用户点击 button 无反应,待脚本加载完后,再次点击有响应。

  • 代码示例

<!-- test.html -->
<!DOCTYPE html>
<html>
  <head>
    <title></title>
  </head>

  <body>
    <div class="test1">test1</div>
    <div id="hello"></div>
    <script> // 待defer脚本下载完成后响应 function alertMsg() { alert("123"); } </script>
    <input type="button" id="button1" onclick="alertMsg()" />
    <script src="./test.js" defer></script>
    <div class="test2">test2</div>
  </body>
  <style> .test1 { color: red; font-size: 50px; } .test2 { color: yellow; font-size: 50px; } </style>
</html>
复制代码
// test.js

for (let i = 0; i < 100000; i++) {
  console.log(i);
}
document.getElementById("hello").innerHTML = "hello world";
复制代码

4、若是禁用 WebWoker,还有其余方法吗?

4.1 使用 Concurrent.Thread.js

  • Concurrent.Thread.js 用来模拟多线程,进行多线程开发。
Concurrent.Thread.create(function () {
  $("#test").click(function () {
    alert(1);
  });
  for (var i = 0; i < 100000; i++) {
    console.log(i);
  }
});
复制代码

4.2 使用虚拟列表

若该情形是渲染十万条数据的状况下,则能够使用虚拟列表。虚拟列表即只渲染可视区域的数据,使得在数据量庞大的状况下,减小 DOM 的渲染,使得列表流畅地无限滚动。

实现方案:

基于虚拟列表是渲染可视区域的特性,咱们须要作到如下三点

  1. 需计算顶部和底部不可视区域留白的高度,撑起整个列表高度,使其高度与没有截断数据时同样,这两个高度分别命名为 topHeight、bottomHeight
  2. 计算截断开始位置 start 和结束位置 end,则可视区域的数据为 list.slice(start,end)
  3. 滚动过程当中需不断更新 topHeight、bottomHeight、start、end,从而更新可视区域视图。固然咱们须要对比老旧 start、end 来判断是否须要更新。

topHeight 的计算比较简单,就是滚动了多少高度,topHeight=scrollTop。

start 的计算依赖于 topHeight 和每项元素的高度 itemHeight,假设咱们向上移动了两个列表项,则 start 为 2,如此,咱们有 start = Math.floor(topHeight / itemHeight)

end 的计算依赖于屏幕的高度能显示多少个列表项,咱们称之为 visibleCount,则有 visibleCount = Math.ceil(clientHeight / itemHeight),向上取整是为了不计算偏小致使屏幕没有显示足够的内容,则 end = start + visibleCount。 bottomHeight 须要咱们知道整个列表没有被截断前的高度,减去其顶部的高度,计算顶部的高度有了 end 就很简单了,假设咱们的整个列表项的数量为 totalItem,则 bottomHeight = (totalItem - end - 1) \* itemHeight

会出现的问题:

可是当这样实现的时候,会发现有两个问题:

  1. 滚动的时候可视区域的顶部或者底部会出现留白。
  2. 每次滚动到须要把空白处替换成实际的列表项的时候,页面会出现抖动,这个缘由是每一个列表项高度不一致,要替换的时候,替换的列表项比 itemHeight 大或者小,而且是在可见区域内替换的,浏览器就会抖动下,这个解决办法能够经过把替换的时机提早,即在咱们不可见的顶部进行替换。

来分析下,对于第一个问题,会出现留白的状况,那么咱们能够在顶部或者底部预留必定的位置,而第二个问题,也是能够经过在顶部和底部预留必定的空间,因此解决这个问题只要一个方案就能够解决了,那就是顶部和底部都预留必定的位置。

假设 reserveTop 为顶部预留的位置数,reserveBottom 为底部预留的位置数,那么咱们上面的数据的计算就要从新定义了,具体如何计算,请看下图。

avatar

reserveTop 和 reserveBottom 尽可能大点(固然也不要太大),或者知道列表项的最高高度为多少,就按这个最高高度来。当你发现你滚动的时候顶部有留白,就调大 reserveTop 的数值,当你发现滚动的时候底部有留白,那就调大 reserveBottom 的数值。

15.有 100 瓶水,其中有一瓶有毒,小白鼠只要尝一点带毒的水 3 天后就会死亡,至少要多少只小白鼠才能在 3 天内鉴别出哪瓶水有毒?

公司:富途

分类:其它

查看解析

答案

7复制代码

分析

每一个老鼠只有死或活 2 种状态,所以每一个老鼠能够看做一个 bit,取 0 或 1N 个老鼠能够看做 N 个 bit,能够表达 2^N 种状态(其中第 n 个状态表明第 n 个瓶子有毒)所以全部老鼠能表示的状态数能大于等于 100 便可。

代码实现

let n = 1;
while (Math.pow(2, n) < 100) {
  n++;
}
console.log(n);
复制代码

通俗点的理解:

给 100 个瓶分别标上以下标签(7 位长度): 0000001 (第 1 瓶) 0000010 (第 2 瓶) 0000011 (第 3 瓶) ...... 1100100 (第 100 瓶)

从编号最后 1 位是 1 的全部的瓶子里面取出 1 滴混在一块儿(好比从第一瓶,第三瓶,。。。里分别取出一滴混在一块儿)并标上记号为 1。以此类推,从编号第一位是 1 的全部的瓶子里面取出 1 滴混在一块儿并标上记号为 7。如今获得有 7 个编号的混合液,小白鼠排排站,分别标上 7,6,。。。1 号,并分别给它们灌上对应号码的混合液。三天过去了,过来验尸吧:

从左到右,死了的小白鼠贴上标签 1,没死的贴上 0,最后获得一个序号,把这个序号换成 10 进制的数字,就是有毒的那瓶水的编号。

检验一下:假如第一瓶有毒,按照 0000001 (第 1 瓶),说明第 1 号混合液有毒,所以小白鼠的生死符为 0000001(编号为 1 的小白鼠挂了),0000001 二进制标签转换成十进制=1 号瓶有毒;假如第三瓶有毒,0000011 (第 3 瓶),第 1 号和第 2 号混合液有毒,所以小白鼠的生死符为 0000011(编号为 1,2 的鼠兄弟挂了),0000011 二进制标签转换成十进制=3 号瓶有毒。

因此结果就是 2^7 = 128 >= 100,至少须要 7 只小白鼠。

16.Promise.allSettled 了解吗?手写 Promise.allSettled

公司:快手

分类:JavaScript

查看解析

Promise.allSettled(iterable)概念

const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);

const allSettledPromise = Promise.allSettled([resolved, rejected]);

allSettledPromise.then(function (results) {
  console.log(results);
});
// [
// { status: 'fulfilled', value: 42 },
// { status: 'rejected', reason: -1 }
// ]
复制代码
  1. Promise.allSettled()  方法接受一组 Promise  实例做为参数,返回一个新的 Promise 实例。
  2. 只有等到全部这些参数实例都返回结果,无论是 fulfilled  仍是 rejected ,包装实例才会结束。
  3. 返回的新 Promise  实例,一旦结束,状态老是 fulfilled ,不会变成 rejected 。
  4. 新的 Promise  实例给监听函数传递一个数组 results 。该数组的每一个成员都是一个对象,对应传入 Promise.allSettled的 Promise 实例。每一个对象都有 status 属性,对应着 fulfilled  和 rejected 。 fulfilled  时,对象有 value  属性, rejected  时有 reason  属性,对应两种状态的返回值。
  5. 有时候咱们不关心异步操做的结果,只关心这些操做有没有结束时,这个方法会比较有用。

手写实现

const formatSettledResult = (success, value) =>
  success
    ? { status: "fulfilled", value }
    : { status: "rejected", reason: value };

Promise.all_settled = function (iterators) {
  const promises = Array.from(iterators);
  const num = promises.length;
  const resultList = new Array(num);
  let resultNum = 0;

  return new Promise((resolve) => {
    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then((value) => {
          resultList[index] = formatSettledResult(true, value);
          if (++resultNum === num) {
            resolve(resultList);
          }
        })
        .catch((error) => {
          resultList[index] = formatSettledResult(false, error);
          if (++resultNum === num) {
            resolve(resultList);
          }
        });
    });
  });
};

const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);
Promise.all_settled([resolved, rejected]).then((results) => {
  console.log(results);
});
复制代码

17.浏览器为何要阻止跨域请求?如何解决跨域?每次跨域请求都须要到达服务端吗?

公司:快手

分类:JavaScript

查看解析

1、什么是跨域

跨域是针对浏览器的“同源策略”提出的说法。之因此有“同源策略”这种模式是基于网络安全方面的考虑。所谓的同源策略关注三点:

  1. 协议 (http:www.baidu.com & https.www.baidu.com http 协议不一样,跨域)
  2. 域名 (https://www.aliyun.com & https://developer.aliyun.com 域名不一样,跨域)
  3. 端口 (http://localhost:8080 & http://localhost:8000 端口号不一样,跨域)

2、哪些网络资源涉及到跨域

“同源策略”对于跨域网络资源的设定很是的清晰。

这些场景涉及到跨域禁止操做:

  1. 没法获取非同源网页的 cookie、localstorage 和 indexedDB。
  2. 没法访问非同源网页的 DOM (iframe)。
  3. 没法向非同源地址发送 AJAX 请求 或 fetch 请求(能够发送,但浏览器拒绝接受响应)。

为何要阻止跨域呢?上文咱们说过是基于安全策略:好比一个恶意网站的页面经过 iframe 嵌入了银行的登陆页面(两者不一样源),若是没有同源限制,恶意网页上的 javascript 脚本就能够在用户登陆银行的时候获取用户名和密码。

3、如何解决跨域

针对跨越问题咱们该如何解决,主流的方案有如下:

一、 经过 jsonp 跨域 二、 document.domain + iframe 跨域 三、 location.hash + iframe 四、 window.name + iframe 跨域 五、 postMessage 跨域 六、 跨域资源共享(CORS) 七、 nginx 代理跨域 八、 nodejs 中间件代理跨域 九、 WebSocket 协议跨域

4、关于跨域须要明确的问题

跨域并不是浏览器限制了发起跨站请求,而是跨站请求能够正常发起,可是返回结果被浏览器拦截了。

每次需求都会发出,服务器端也会作出响应,只是浏览器端在接受响应的时候会基于同源策略进行拦截。

注意:有些浏览器不容许从 HTTPS 的域跨域访问 HTTP,好比 Chrome 和 Firefox,这些浏览器在请求还未发出的时候就会拦截请求,这是一个特例。

18.浏览器缓存了解吗?强缓存通常存放在哪里?计算整个文件获得 etag 会耗费性能,怎么解决?若是我不想要使用缓存了,每次都请求最新的,怎么作?no-store 和 no-cache 的区别是什么?

公司:快手

分类:网络&安全

查看解析

1、浏览器缓存

浏览器缓存主要分为四个阶段:

  1. 强制缓存阶段:先在本地查找该资源,若是有发现该资源,并且该资源尚未过时,就使用这一个资源,彻底不会发送 http 请求到服务器。
  2. 协商缓存阶段:若是在本地缓存找到对应的资源,可是不知道该资源是否过时或者已通过期,则发一个 http 请求到服务器,而后服务器判断这个请求,若是请求的资源在服务器上没有改动过,则返回 304,让浏览器使用本地找到的那个资源。
  3. 启发式缓存阶段:当缓存过时时间的字段一个都没有的时候,浏览器下次并不会直接进入协商阶段,而是先进入启发式缓存阶段,它根据响应头中 2 个时间字段 Date 和 Last-Modified 之间的时间差值,取其值的 10%做为缓存时间周期。也就是说,当存有 Last-Modified 字段的时候,即便是断网,且强缓存都失效后,也有必定时间是直接读取缓存文件的。etag 是没有这个阶段的。
  4. 缓存失败阶段:当服务器发现请求的资源已经修改过,或者这是一个新的请求(再原本没有找到资源),服务器则返回该资源的数据,而且返回 200, 固然这个是指找到资源的状况下,若是服务器上没有这个资源,则返回 404。

2、强缓存通常放在哪里

强缓存通常存放于 Memory Cache 或者 Disk Cache。

3、计算整个文件获得 etag 会耗费性能,怎么解决

etag 能够经过文件的 Last-Modified 和 content-length 计算。

Nginx官方默认的ETag计算方式是为"文件最后修改时间16进制-文件长度16进制"。例:ETag: “59e72c84-2404”

注意:

无论怎么样的算法,在服务器端都要进行计算,计算就有开销,会带来性能损失。所以为了榨干这一点点性能,很多网站彻底把Etag禁用了(好比Yahoo!),这其实不符合HTTP/1.1的规定,由于HTTP/1.1老是鼓励服务器尽量的开启Etag。

4、不使用缓存的方式,让每次请求都是最新的

不使用缓存常见的方法是经过 url 拼接 random 的方式或者设置 Cache-Control 设置 no-cache。

5、no-stroe & no-cache

  • no-store 禁止浏览器和中间服务器缓存。每次都从服务器获取。
    • 注意,no-store 才是真正的完彻底全的禁止本地缓存。
  • no-cache 每次请求都会验证该缓存是否过时。能够在本地缓存,能够在代理服务器缓存,可是这个缓存要服务器验证才能够使用
    • 注意,no-cache 不是不缓存的意思。

19.说一下平时项目是怎么优化的?优化以后是怎么度量的?首屏时间是怎么计算的?

公司:快手

分类:其它

查看解析

针对每一个过程进行优化

网页从加载到呈现会经历一系列过程,针对每一个过程进行优化

  • 网络链接
  • 请求优化
  • 响应优化
  • 浏览器渲染

image.png

经过 performance.timing API,能够获取各个阶段的执行时间:

{
  navigationStart: 1578537857229; //上一个文档卸载(unload)结束时的时间戳
  unloadEventStart: 1578537857497; //表征了unload事件抛出时的时间戳
  unloadEventEnd: 1578537857497; //表征了unload事件处理完成时的时间戳
  redirectStart: 0; // 重定向开始时的时间戳
  redirectEnd: 0; //重定向完成时的时间戳
  fetchStart: 1578537857232; //准备好HTTP请求来获取(fetch)文档的时间戳
  domainLookupStart: 1578537857232; //域名查询开始的时间戳
  domainLookupEnd: 1578537857232; //域名查询结束的时间戳
  connectStart: 1578537857232; //HTTP请求开始向服务器发送时的时间戳
  connectEnd: 1578537857232; //浏览器与服务器之间的链接创建时的时间戳
  secureConnectionStart: 0; //安全连接的握手时的U时间戳
  requestStart: 1578537857253; //HTTP请求(或从本地缓存读取)时的时间戳
  responseStart: 1578537857491; //服务器收到(或从本地缓存读取)第一个字节时的时间戳。
  responseEnd: 1578537857493; //响应结束
  domLoading: 1578537857504; //DOM结构开始解析时的时间戳
  domInteractive: 1578537858118; //DOM结构结束解析、开始加载内嵌资源时的时间戳
  domContentLoadedEventStart: 1578537858118; //DOMContentLoaded 事件开始时间戳
  domContentLoadedEventEnd: 1578537858118; //当全部须要当即执行的脚本已经被执行(不论执行顺序)时的时间戳
  domComplete: 1578537858492; //当前文档解析完成的时间戳
  loadEventStart: 1578537858492; //load事件被发送时的时间戳
  loadEventEnd: 1578537858494; //当load事件结束时的时间戳
}
复制代码

1.1 网络链接方面优化

主要是针对重定向、DNS、TCP 链接进行优化

  • 避免重定向
  • DNS 查找优化:页面采用预解析 dns-prefetch ,同时将同类型的资源放到一块儿,减小 domain  数量也是能够减小 DNS 查找
  • 使用 CDN(内容分发网络)
  • HTTP/1.1 版本,客户端能够经过 Keep-Alive 选项和服务器创建长链接,让多个资源经过一个 TCP 链接传输。

1.2 请求方面优化

减小浏览器向浏览器发送的请求数目以及请求资源的大小是请求优化的核心思想

  • 合理使用文件的压缩和合并
    • 合理运用浏览器对于资源并行加载的特性,在资源的加载的数量和资源的大小之间作一个合理的平衡
    • 在移动端页面中,将首屏的请求资源控制在 5 个之内,每一个资源在 Gzip 以后的大小控制在 28.5KB 以内,能够显著的提高首屏时间。
  • 压缩图片,使用雪碧图,小图片使用 Base64 内联
  • 组件延迟加载
  • 给 Cookie 瘦身
    • 静态资源使用 CDN 等方式放在和当前域不一样的域上,以免请求静态资源时携带 Cookie
  • 善用 CDN 提高浏览器资源加载能力
    • 资源分散到多个不一样的 CDN 中,最大化的利用浏览器的并行加载能力
  • 合理运用缓存策略缓存静态资源,Ajax 响应等
    • 利用 Manifest + 本地存储作持久化缓存
    • 将对访问实时性要求不高的其余资源,如图片、广告脚本等内容存放在 IndexDB 或 WebSQL 中,IndexDB 后 WebSQL 的存储容量比 LocalStorage 大得多,能够用来存放这些资源。
    • 使用 localForage 操做持久化缓存
    • 库文件放入 CDN 或者开启强缓

1.3 响应优化

  • 优化服务端处理流程,如使用缓存、优化数据库查询、减小查询次数
  • 优化响应资源的大小,如对响应的资源开启 Gzip 压缩等。

1.3.1 页面加载的核心指标

  • TTFB
    • 首个字节
  • FP
    • 首次绘制,只有 div 跟节点,对应 vue 生命周期的 created
  • FCP
    • 首次有内容的绘制,页面的基本框架,可是没有数据内容,对应 vue 生命周期的 mounted
  • FMP
    • 首次有意义的绘制,包含全部元素和数据内容,对应 vue 生命周期的 updated
  • TTI
    • 首次能交互时间
  • Long Task
    • >=50ms 的任务
  • SSR&CSR
    • 服务端渲染和客户端渲染
  • Isomorphic javascript
    • 同构化

1.4 浏览器首屏渲染优化

1.4.1 首屏时间:

指用户打开网站开始,到浏览器首屏内容渲染完成的时间。对于用户体验来讲,首屏时间是用户对一个网站的重要体验因素。一般一个网站,若是首屏时间在 5 秒之内是比较优秀的,10 秒之内是能够接受的,10 秒以上就不可容忍了。超过 10 秒的首屏时间用户会选择刷新页面或马上离开。

1.4.2首屏时间计算:

  • 首屏模块标签标记法

一般适用于首屏内容不须要经过拉取数据才能生存以及页面不考虑图片等资源加载的状况,咱们会在 HTML 文档中对应首屏内容的标签结束位置,使用内联的 JavaScript 代码记录当前时间戳。以下所示:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>首屏</title>
    <script type="text/javascript"> window.pageStartTime = Date.now(); </script>
    <link rel="stylesheet" href="common.css" />
    <link rel="stylesheet" href="page.css" />
  </head>
  <body>
    <!-- 首屏可见模块1 -->
    <div class="module-1"></div>
    <!-- 首屏可见模块2 -->
    <div class="module-2"></div>
    <script type="text/javascript"> window.firstScreen = Date.now(); </script>
    <!-- 首屏不可见模块3 -->
    <div class="module-3"></div>
    <!-- 首屏不可见模块4 -->
    <div class="module-4"></div>
  </body>
</html>
复制代码

此时首屏时间等于  firstScreen - performance.timing.navigationStart

事实上首屏模块标签标记法   在业务中的状况比较少,大多数页面都须要经过接口拉取数据才能完整展现

  • 统计首屏内加载最慢的图片的时间:

一般咱们首屏内容加载最慢的就是图片资源,所以咱们会把首屏内加载最慢的图片的时间当作首屏的时间。

DOM 树构建完成后将会去遍历首屏内的全部图片标签,而且监听全部图片标签 onload 事件,最终遍历图片标签的加载时间的最大值,并用这个最大值减去 navigationStart 便可得到近似的首屏时间。

此时首屏时间等于 加载最慢的图片的时间点 - performance.timing.navigationStart

  • 自定义首屏内容计算法

因为统计首屏内图片完成加载的时间比较复杂。所以咱们在业务中一般会经过自定义模块内容,来简化计算首屏时间。以下面的作法:

  • 忽略图片等资源加载状况,只考虑页面主要 DOM
  • 只考虑首屏的主要模块,而不是严格意义首屏线以上的全部内容

1.4.3首屏优化方案:

  • 页面直出:骨架屏或者 SSR
  • 首帧渲染优化
  • 资源动态加载
  • 浏览器缓存
  • 优化 JavaScript 脚本执行时间
  • 减小重排重绘
  • 硬件加速提高动画性能等页面渲染方面的优化方案

1.5浏览器渲染优化

  • 优化 JavaScript 脚本执行时间
  • 减小重排重绘
  • 硬件加速提高动画性能等

20.怎么计算组件在视口内出现了几回?IntersectionObserver 怎么使用的?怎么知道一个 DOM 节点出如今视口内?

公司:快手

分类:JavaScript

查看解析

1、监听 Scroll

要了解某个元素是否进入了"视口"(viewport),即用户能不能看到它,传统的实现方法是,监听到scroll事件后,调用目标元素的getBoundingClientRect()方法,获得它对应于视口左上角的坐标,再判断是否在视口以内。而后声明一个全局变量,每出现一次就加一,就能够得出在视口出现了几回。这种方法的缺点是,因为scroll事件密集发生,计算量很大,容易形成性能问题。

因而便有了 IntersectionObserver API

2、IntersectionObserver

2.1 API

var io = new IntersectionObserver(callback, option);
复制代码

上面代码中,IntersectionObserver是浏览器原生提供的构造函数,接受两个参数:callback是可见性变化时的回调函数,option是配置对象(该参数可选)。

构造函数的返回值是一个观察器实例。实例的observe方法能够指定观察哪一个 DOM 节点。

// 开始观察
io.observe(document.getElementById("example"));

// 中止观察
io.unobserve(element);

// 关闭观察器
io.disconnect();
复制代码

上面代码中,observe的参数是一个 DOM 节点对象。若是要观察多个节点,就要屡次调用这个方法。

io.observe(elementA);
io.observe(elementB);
复制代码

2.2 Callback 参数

目标元素的可见性变化时,就会调用观察器的回调函数callback

callback通常会触发两次。一次是目标元素刚刚进入视口(开始可见),另外一次是彻底离开视口(开始不可见)。

var io = new IntersectionObserver((entries) => {
  console.log(entries);
});
复制代码

callback函数的参数(entries)是一个数组,每一个成员都是一个IntersectionObserverEntry对象。若是同时有两个被观察的对象的可见性发生变化,entries数组就会有两个成员。

IntersectionObserverEntry对象提供目标元素的信息,一共有六个属性。

{
  time: 3893.92,
  rootBounds: ClientRect {
    bottom: 920,
    height: 1024,
    left: 0,
    right: 1024,
    top: 0,
    width: 920
  },
  boundingClientRect: ClientRect {
     // ...
  },
  intersectionRect: ClientRect {
    // ...
  },
  intersectionRatio: 0.54,
  target: element
}
复制代码

每一个属性的含义以下。

  • time:可见性发生变化的时间,是一个高精度时间戳,单位为毫秒
  • target:被观察的目标元素,是一个 DOM 节点对象
  • rootBounds:根元素的矩形区域的信息,getBoundingClientRect()方法的返回值,若是没有根元素(即直接相对于视口滚动),则返回null
  • boundingClientRect:目标元素的矩形区域的信息
  • intersectionRect:目标元素与视口(或根元素)的交叉区域的信息
  • intersectionRatio:目标元素的可见比例,即intersectionRectboundingClientRect的比例,彻底可见时为1,彻底不可见时小于等于0

2.3 Option 对象

IntersectionObserver构造函数的第二个参数是一个配置对象。它能够设置如下属性。

2.3.1 threshold 属性:

threshold属性决定了何时触发回调函数。它是一个数组,每一个成员都是一个门槛值,默认为[0],即交叉比例(intersectionRatio)达到0时触发回调函数。

new IntersectionObserver(
  (entries) => {
    /* ... */
  },
  {
    threshold: [0, 0.25, 0.5, 0.75, 1],
  }
);
复制代码

用户能够自定义这个数组。好比,[0, 0.25, 0.5, 0.75, 1]就表示当目标元素 0%、25%、50%、75%、100% 可见时,会触发回调函数。

2.3.2 root 属性、rootMargin 属性:

不少时候,目标元素不只会随着窗口滚动,还会在容器里面滚动(好比在iframe窗口里滚动)。容器内滚动也会影响目标元素的可见性。

IntersectionObserver API 支持容器内滚动。root属性指定目标元素所在的容器节点(即根元素)。注意,容器元素必须是目标元素的祖先节点。

var opts = {
  root: document.querySelector(".container"),
  rootMargin: "500px 0px",
};

var observer = new IntersectionObserver(callback, opts);
复制代码

上面代码中,除了root属性,还有rootMargin属性。后者定义根元素的margin,用来扩展或缩小rootBounds这个矩形的大小,从而影响intersectionRect交叉区域的大小。它使用 CSS 的定义方法,好比10px 20px 30px 40px,表示 top、right、bottom 和 left 四个方向的值。

这样设置之后,无论是窗口滚动或者容器内滚动,只要目标元素可见性变化,都会触发观察器。

---------------------------------- 一条讲武德的分割线 ------------------------------

因掘金发文的字数限制,剩下的答案你们扫码便可查看。

21.versions 是一个项目的版本号列表,因多人维护,不规则,动手实现一个版本号处理函数

var versions = ["1.45.0", "1.5", "6", "3.3.3.3.3.3.3"];
// 要求从小到大排序,注意'1.45'比'1.5'大
function sortVersion(versions) {
  // TODO
}
// => ['1.5','1.45.0','3.3.3.3.3.3','6']
复制代码

公司:头条

分类:JavaScript

22.什么是微服务,微服务跟单体应用的区别是啥,用微服务有啥好处?

分类:Node

23.动手实现一个 repeat 方法

function repeat(func, times, wait) {
  // TODO
}
const repeatFunc = repeat(alert, 4, 3000);
// 调用这个 repeatFunc ("hellworld"),会alert4次 helloworld, 每次间隔3秒
复制代码

公司:头条

分类:JavaScript

24.请列出目前主流的 JavaScript 模块化实现的技术有哪些?说出它们的区别?

公司:玄武科技

分类:JavaScript

25.请描述下 JavaScript 中 Scope、Closure、Prototype 概念,并说明 JavaScript 封装、继承实现原理。

公司:玄武科技

分类:JavaScrip

相关文章
相关标签/搜索