1. 实现一个构造函数 Foo,构造函数里面实现一个属性 a,和一个方法 getA,属性 a 能够在构造函数被实例化的时候通过参数设置

// Foo 构造函数
function Foo(a){
    this.a = a;
}
//  在 Foo 的原型对象上定义 getA 方法
Foo.prototype.getA = function(){
    return this.a;
}

var foo1 = new Foo(1);
var foo2 = new Foo(2);
// 下面的输出是:
// 1, [Function: Foo], true
// 2, [Function: Foo], true
console.log(foo1.getA(), foo1.constructor, foo1 instanceof Foo);
console.log(foo2.getA(), foo2.constructor, foo2 instanceof Foo);

2. 写一个构造函数 Bar 继承上面的 Foo, 并且它有一个方法 getB,能够获取到 Bar 实例对象的 b 属性

// 构造函数 Bar 继承自 Foo
function Bar(b){
    Foo.call(this);
    this.a = 3;
    this.b = b;
}

Bar.prototype.getB = function(){
    return this.b;
}

// Bar.prototype.__proto__  = Foo.prototype;
Object.setPrototypeOf(Bar.prototype, Foo.prototype);
Bar.prototype.constructor = Bar;

var bar1 = new Bar(1);
var bar2 = new Bar(2);
// 下面两行输出:
// Bar { a: 3, b: 1 } 1 [Function: Bar] true
// Bar { a: 3, b: 2 } 2 [Function: Bar] true
log(bar1, bar1.getB(), Bar.prototype.constructor, bar1 instanceof Foo);
log(bar2, bar2.getB(), bar2.constructor, bar2 instanceof Foo);

3. 讲一讲 new 的工作原理

new 做的三件事

  1. 让实例可以访问到私有属性;
  2. 让实例可以访问到构造函数原型所在原型链上的属性;
  3. 如果构造函数返回的不是引用类型,则返回自己创建的 obj;

代码实现

function myNew(f, ...args){
    if(typeof f !== 'function'){
        throw 'f 必须是函数';
    }
    let obj = {};
    let ret = f.call(obj, ...args);
    // 将  obj 的 __proto__ 属性指向 f 的原型对象
    Object.setPrototypeOf(obj, f.prototype);
    // 判断 ret 是否为引用类型,如果是引用类型,那么返回它
    let retIsObject = (typeof ret === "object") && (ret !== null);
    let retIsFunction = typeof ret === "function";
    if(retIsFunction || retIsObject){
        return ret;
    } else {
        return obj;
    }
}

var foo = myNew(Foo, 3);
// 输出
// 3, true
log(foo.getA(), foo instanceof Foo);

2. 看代码说输出

const obj1 = {
  key: 'value1'
}
const obj2 = {
  key: 'value2'
}
function foo(obj1) {
  obj1.key = 'value11'
  return obj1
}

function bar(obj2) {
  obj2 = {
    key: 'value22'
  }
  return obj2
}

// 下面一行输出
// {key: 'value11'}   {key: 'value22'}
console.log(foo(obj1), bar(obj2))
// {key: 'value11'}   {key: 'value2'}
console.log(obj1, obj2)

3. 看代码说输出

var Item = {
  id: 1,
  getId: function() {
    return this.id;
  }
};
// 下面一行输出
// 1
console.log(Item.getId());
var func = Item.getId;
// 下面一行输出
// undefined( 我回答的“报错”😂 )
console.log(func());

//再提问:怎么不修改 Item,使调用 Item 能够输出Item.id
func = func.bind(Item);
console.log(func()); // 这时就会输出 1

4. React 生命周期函数

1. Mounting

constructor() -> static getDerivedStateFromProps() -> render() -> componentDidUpdate();

2. Updating

static getDerivedStateFromProps() -> shouldComponentUpdate() -> render() -> getSnapshotBeforeUpdate() -> componentDidUpdate();

3. Unmounting

componentWillUnmount();

5. (1)React 类组件的优化怎么实现? (2) React 有封装一个类组件,可以不再需要自己写shouldComponentUpdate(),讲一下

(1) 利用 shouldComponentUpdate()这个生命周期函数实现 CounetrButton 这个组件只有当 props.color 或者 state.count 的值发生改变时才需要更新

import React, { Component } from "react";

class CounterButton extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 1 };
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (this.props.color !== nextProps.color) {
      return true;
    }
    if (this.state.count !== nextState.count) {
      return true;
    }
    return false;
  }

  render() {
    console.log("render");
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState((state) => ({ count: state.count + 1 }))}
      >
        Count: {this.state.count}
      </button>
    );
  }
}

export default CounterButton;

(2)React 官方提供了 PureComponent 来代替手写 shouldComponentUpdate。与上述代码块同样的效果,你只需要继承 PureComponent 就好。

import React, { PureComponent } from "react";

class CounterButton extends PureComponent {
  constructor(props) {
    super(props);
    this.state = { count: 1 };
  }

  render() {
    console.log("render");
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState((state) => ({ count: state.count + 1 }))}
      >
        Count: {this.state.count}
      </button>
    );
  }
}

export default CounterButton;

值得注意的是,PureComponent 只进行浅比较,如果浅比较引用类型的话,那么它只能检测到引用地址是否有变化。

6. React Hooks 了解多少

7. 讲一下 Redux

8. 怎么实现将一个 div 从左到右移动的动画

关键帧制作动画

9. 通过绝对定位,修改 left 来实现动画与 transform 实现动画的性能比较

浏览器的渲染流程

接收到文档后,渲染引擎会对 HTML 文档进行解析生成 DOM 树、对 CSS 文件进行解析生成 CSSOM 树,同时执行页面中的 Javascript 代码;最终根据 DOM 树和 CSSOM 树计算样式生成渲染树,渲染树中只会包含即将显示在页面中的元素及其样式信息(如 head\ display 为 hidden 的元素就不会包含在渲染树中);根据渲染树需要进行布局 (layout) 来计算每个元素在页面上的位置;

接下来渲染引擎开始绘制(paint),这一步分为若干阶段:根据渲染树将每层(layer)的各个元素绘制,然后将绘制出的若干连续图像进行栅格化(Rasterization),最后将栅格化后的图像合成(composite)为要显示在屏幕上的图像。对每一层的绘制是由浏览器完成的;最后的合成由 GPU 来完成;而栅格化过程取决于浏览器的设置,Chrome 默认开启 GPU栅格化,否则由 CPU 进行。

当首次将 DOM树构建完成后,每次页面发生改变时进行的主要流程是:

  • Javascript -> style -> Layout -> Paint -> Composite
    其中 CSS 动画不会调用 Javascrip, 我们知道,在渲染中主要消耗时间的是 Layout/Reflow 和 Paint/Repaint 的过程,因此要尽量避免和减少这两个阶段的时间。

使用 GPU 加速

浏览器的加速功能是将需要进行动画的元素提升到一个独立的层(layer),这样就可以避免浏览器进行重新布局 (Reflow) 和 (Repaint),将原先的浏览器使用 CPU 绘制位图来实现的动画效果转化为让 GPU 使用图层合成 (composite) 来实现,如果两张图层内部没有发生改变,浏览器就不再进行布局和绘制,直接使用 GPU 的缓存来绘制每个图层,GPU 只负责将各个图层合成来实现动画,这就可以充分利用 GPU 的资源和优势,减轻 CPU 的负载,可以使动画更流畅。通过改变两张图片之间的相对位置代替绘制一张图片的每一帧实现动画,虽然视觉效果相同,但省去了许多绘制的时间。

为了让浏览器将动画元素提升到一个独立的层,可以使用 transform 和 opacity 属性实现动画,当设置了这两个属性之一时,浏览器会自动进行这一优化操作。

结论

tranform 相对于绝对定位的方法,绘制每帧动画的总时间减少,主要体现在绘制 (Painting) 过程中;composite layers 时间的减少,应该主要是由于需要绘制的图层的减小,导致纹理上传 GPU 和调用 OpenGL 绘制接口的时间减少了。