ES5和ES6函數你不知道的區別【面試篇】

js.jpg

前言

JS 中函數是高等公民,可是function 和 class 的區別你真的清楚嘛?
本文從PolyFill 實現,再到性能分析,再複習哈基礎篇的使用;
另外深圳前端求坑,有坑的大佬麻煩內推一下。

1. PolyFill

1.利用原生 js 擼一個簡單的 class;
2.根據上面的用法知道 class 必須經過 new 調用,不能直接調用;前端

// 阻止直接()調用,直接在ES6運行Parent(),這是不容許的,ES6中拋出Class constructor Parent cannot be invoked without 'new'錯誤
function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
        throw new TypeError("Cannot call a class as a function");
    }
}

3.裏面能夠定義實例的屬性vue

_createClass方法,它調用Object.defineProperty方法去給新建立的Parent添加各類屬性
 defineProperties(Constructor.prototype, protoProps)是給原型添加屬性
defineProperties(Constructor, staticProps)是添加靜態屬性
const _createClass = function () {
    function defineProperties(target, props) {
        for (var i = 0; i < props.length; i++) {
            var descriptor = props[i];
            descriptor.enumerable = descriptor.enumerable || false;
            descriptor.configurable = true;
            if ("value" in descriptor) descriptor.writable = true;
            Object.defineProperty(target, descriptor.key, descriptor);
        }
    }

    return function (Constructor, protoProps, staticProps) {
        if (protoProps) defineProperties(Constructor.prototype, protoProps);
        if (staticProps) defineProperties(Constructor, staticProps);
        return Constructor;
    };
}();

4.實現繼承node

function _inherits(subClass, superClass) {
  // 判斷父類必須是函數
    if (typeof superClass !== "function" && superClass !== null) {
        throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
    }

    subClass.prototype = Object.create(superClass && superClass.prototype, {
        constructor: {
            value: subClass,
            enumerable: false,
            writable: true,
            configurable: true
        }
    });
    //Object.setPrototypeOf(obj, prototype),將一個指定的對象的原型設置爲另外一個對象或者null
    // 等同於 subClass.prototype.__proto__ = superClass.prototype
    if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}

5.完整演示代碼
請戳:,歡迎star!react

2.性能上

2.1 先測試下

4.先用 Google 的開源插件 bench測試下 function 和 class 的性能;
若是不知道 benchMark 是啥的,請戳:;git

2.測試代碼github

const bench = require('benchmark')

const suite = new bench.Suite()

function myFun(i) {
   let baz = 42; 
}

class myClass{
  constructor() {
    this.fol = 42; 
  }
}

suite
.add('function', () => {
  myFun()
})
.add('class', () => {
  myClass()
})
.on('cycle', (evt) => {
  console.log(String(evt.target));
})
.on('complete', function() {
  console.log('Fastest is ' + this.filter('fastest').map('name'));
})
.run()

3.測試結果express

// node 版本v10.16.0
function x 815,978,962 ops/sec ±4.53% (87 runs sampled)
class x 812,855,174 ops/sec ±4.49% (88 runs sampled)
Fastest is function,class
// 能夠看出 class 和 function 速度差很少

2.2 緣由

4.function 的 AST 元素
Functionsless

2.class 的 AST元素:
ClassBody
MethodDefinition
ClassDeclaration
ClassExpression
元屬性 dom

AST 雖然新增了新的 AST 元素,可是內部屬性和方法相對於 function 來講增長了不少,因此兩個性能基本差很少;
可是 class 定義代碼更利於維護;koa

3 hooks和 class 的性能

3.1 先測試下

4.在 2.1 測試中知道 class 比 function 要快好幾倍;
2.假設場景是有一個父組件,包裹一個function子組件和class子組件,class組件在render事後,定義好的function,能夠經過this.func進行調用,而且不會從新再建立,function組件會從新執行一遍,而且從新進行建立須要的function,那是否是 hooks 比 class 更耗性能呢;

const React = require('react')
const ReactDOM = require('react-dom/server.node')
const bench = require('benchmark')

const suite = new bench.Suite()

function Func(){
  return React.createElement('span', {onClick: () => {console.log('click') }}, 'children')
}

class Cls extends React.Component{
  handleP() {
    console.log('click')
  }

  render(){
   return React.createElement('span', {onClick: this.handleP}, 'children')  
  }
}

suite
.add('function component', () => {
  ReactDOM.renderToString(React.createElement(Func))
})
.add('class component', () => {
  ReactDOM.renderToString(React.createElement(Cls))
})
.on('cycle', (evt) => {
  console.log(String(evt.target));
})
.on('complete', function() {
  console.log('Fastest is ' + this.filter('fastest').map('name'));
})
.run()

3.結果

// node 版本v10.16.0
function component x 110,115 ops/sec ±13.91% (44 runs sampled)
class component x 118,909 ops/sec ±12.71% (43 runs sampled)
Fastest is class component,function component

能夠看出 function 和 class 性能基本差很少

3.2 緣由

React官方回答:
1.Hooks避免了類所需的大量開銷,例如在構造器中建立類實例和綁定事件處理程序的開銷。
2.使用Hooks的不須要在使用高階組件,渲染道具和上下文的代碼庫中廣泛存在的深層組件樹嵌套。使用較小的組件樹,React要作的工做更少。
3.傳統上,與React中的內聯函數有關的性能問題與如何在每一個渲染器上傳遞新的回調破壞shouldComponentUpdate子組件的優化有關。Hooks從三個方面解決了這個問題。
該useCallback 的 hooks可讓你保持相同的回調引用之間從新呈現,這樣shouldComponentUpdate繼續工做:

// Will not change unless `a` or `b` changes
const memoizedCallback = useCallback(() => {
  doSomething(a, b);
}, [a, b]);

該useMemo鉤使得它更容易控制,當個別兒童的更新,減小了對純組件的需求;
useReducerHook減小了深刻傳遞迴調的須要

4.用法上

這個是基礎篇,只是帶你們回顧一下用法;
class 是 function 的語法糖;

4.1 定義方法

表達式

const MyClass = class My {
  getClasOsName() {
    return My.name;
  }
};

聲明式

const MyClass = class My {
  getClassName() {
    return My.name;
  }
};

4.2 嚴格模式

內部是默認嚴格模式

// 引用一個未聲明的變量
function Bar() {
  baz = 42; // it's ok
}
const bar = new Bar();

class Foo {
  constructor() {
    fol = 42; // ReferenceError: fol is not defined
  }
}
const foo = new Foo();

4.3 constructor

是 class 的默認方法,默認爲空,經過new命令生成對象實例時,自動調用該方法;
constructor方法是一個特殊的方法,用來建立並初始化一個對象,並默認返回;
在一個class中只能有一個命名爲constructor的特殊方法;
constructor中能夠經過super關鍵字,調用父類的constructor方法;

class Rectangle {
  // 構造函數
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
  get area() {
    return this.calcArea();
  }
  calcArea() {
    return this.height * this.width;
  }
}

const square = new Rectangle(10, 10);

console.log(square.area); // 100

4.4 static

static關鍵字爲一個class建立靜態方法;
static methods的調用無需對class實例化,也不能被實例對象所調用;

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
    
  static distance(a, b) {
    const dx = a.x - b.x;
    const dy = a.y - b.y;

    return Math.hypot(dx, dy);
  }
}

const p1 = new Point(5, 5);
const p2 = new Point(10, 10);

console.log(Point.distance(p1, p2)); // 7.0710678118654755

當static或prototype method被調用的時候,若是沒有對this賦值,那麼this將是undefine狀態;
這和是否採用static模式無關,由於class類體中的代碼已經默認執行static模式;

class Animal { 
  talk() {
    return this;
  }
  static drink() {
    return this;
  }
}

let obj = new Animal();
obj.talk(); // Animal {}
let talk = obj.talk;
talk(); // undefined

Animal.drink() // class Animal
let drink = Animal.drink;
drink(); // undefined

4.5 指向構造函數

class Point {
  // ...
}

typeof Point // "function"
Point === Point.prototype.constructor // true

4.6 必須用 new 調用

function Bar() {
  this.bar = 42;
}
const bar = Bar(); // 正常執行,也能夠同 new 調用

class Foo {
  constructor() {
    this.foo = 42;
  }
}
const foo = Foo(); // 報錯

4.7 內部methods 不可枚舉

// 引用一個未聲明的變量
function Bar() {
  this.bar = 42;
}
Bar.answer = function() {
  return 42;
};
Bar.prototype.print = function() {
  console.log(this.bar);
};
const barKeys = Object.keys(Bar); // ['answer']
const barProtoKeys = Object.keys(Bar.prototype); // ['print']

class Foo {
  constructor() {
    this.foo = 42;
  }
  static answer() {
    return 42;
  }
  print() {
    console.log(this.foo);
  }
}
const fooKeys = Object.keys(Foo); // []
const fooProtoKeys = Object.keys(Foo.prototype); // []

4.8 屬性默認定義在類上

//定義類
class Point {

  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }

}

var point = new Point(2, 3);

point.toString() // (2, 3)

point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') // true

由於屬性 x,y 是顯式定義在 this(實例) 上,而 toString 屬性默認定義在類 Point 上.

4.9 getter 和 setter

和function 同樣,在「類」的內部可使用get和set關鍵字,對某個屬性設置存值函數和取值函數,攔截該屬性的存取行爲

class MyClass {
  constructor() {
    // ...
  }
  get prop() {
    return 'getter';
  }
  set prop(value) {
    console.log('setter: '+value);
  }
}

let inst = new MyClass();

inst.prop = 123;
// setter: 123

inst.prop
// 'getter'

4.10 this 指向

默認指向類的實例

class My {
  printName(name = 'there') {
    this.print(`Hello ${name}`);
  }

  print(text) {
    console.log(text);
  }
}

const my = new My();
const { printName } = logger;
printName(); //  報錯,print未定義

解決方法一:能夠在constructor綁定 this

class My {
  constructor() {
    this.printName = this.printName.bind(this);
  }

  // ...
}

解決方法二:使用Proxy,獲取方法的時候,自動綁定this

function selfish (target) {
  const cache = new WeakMap();
  const handler = {
    get (target, key) {
      const value = Reflect.get(target, key);
      if (typeof value !== 'function') {
        return value;
      }
      if (!cache.has(value)) {
        cache.set(value, value.bind(target));
      }
      return cache.get(value);
    }
  };
  const proxy = new Proxy(target, handler);
  return proxy;
}

const logger = selfish(new Logger());

4.11 super

4.super這個關鍵字,既能夠看成函數使用,也能夠看成對象使用;
2.super做爲函數調用時,表明父類的構造函數;

class Person {}

class Child extends Person {
  constructor() {
    // 調用父類的構造函數
    // 返回子類 Child
    // 等同於Person.prototype.constructor.call(this)
    super();
  }
}

3.做爲對象,普通方法指向父類的原型對象;在靜態方法中,指向父類

// 普通方法
class Person {
  p() {
    return 2;
  }
}

class Child extends Person{
  constructor() {
    super();
    console.log(super.p()); // 2
  }
}

let child = new Child();
// 子類Child當中的super.p(),就是將super看成一個對象使用。這時,super在普通方法之中,指向Person.prototype,因此super.p()就至關於Person.prototype.p()
// 靜態方法
class Parent {
  static myMethod(msg) {
    console.log('static', msg);
  }

  myMethod(msg) {
    console.log('instance', msg);
  }
}

class Child extends Parent {
  static myMethod(msg) {
    super.myMethod(msg);
  }

  myMethod(msg) {
    super.myMethod(msg);
  }
}

Child.myMethod(1); // static 1

var child = new Child();
child.myMethod(2); // instance 2

4.12 extends

父類

class Person{
  constructor(name,birthday){
    this.name = name;
    this.birthday= birthday;
  }
  intro(){
    return `${this.name},${this.birthday}`
  }
}

子類

class Child extends Person{
  constructor(name,birthday){
    super(name,birthday);
  }
}
 
let child = new Child('xiaoming','2020-1-25');
console.log(child.intro()); //zhangsan,1988-04-01

4.13 不存在變量提高

new Foo(); // ReferenceError
class Foo {}

4.14 怎麼實現多繼承

4.function 和 class 單次只能繼承一個;

// 如 A繼承 B和C
class A extends B{}
class A extends C{}

2.這樣寫仍是比較 low,咱們回顧下,Vue 和 React 的 mixin 方法,用來將多個Class的功能複製到一個新的Class上; 咱們能夠簡單來實現一個 mixins,核心是遍歷 B,C原型的屬性,經過Object.defineProperty設置到 A上;

function mixin(constructor) {
  return function (...args) {
    for (let arg of args) {
      for (let key of Object.getOwnPropertyNames(arg.prototype)) {
        if (key === 'constructor') continue // 跳過構造函數
        Object.defineProperty(constructor.prototype, key, Object.getOwnPropertyDescriptor(arg.prototype, key))
      }
    }
  }
}

mixin(A)(B,C)
const a = new A()

5.總結

原創碼字不易,你的 star是我持續創做更新的動力,歡迎 star!另外深圳前端求坑,有坑的大佬麻煩內推一下。

相關文章
相關標籤/搜索