ES6之Class实战封装 | 8月更文挑战

3,016 阅读6分钟
1,构造函数

传统的JavaScript中只有对象,没有类的概念,它是基于原型的面向对象语言,原型上的属性都共享的。如果想生成一个对象实例,需要先定义一个构造函数,然后通过new 操作来完成。

1,传统的创建方式: 构造函数也是函数,区别普通函数是:函数名称第一个单词大写

function Person(name, age) {
   this.name = name;
   this.age = age
}
// 原型链上添加方法
Person.prototype.hello = function() {
   console.log()
}
// 创建对象, 必须使用new
var person = new Person('zlm', 21)
​
// 对象上有原型的属性和方法
person.hello()

在面向对象的编程中,类是一个用于创建对象,并且为对象的状态和行为提供初始值的模板。

2,Class 引入

1, ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

class MyClass{
    constructor(){}
    method(){}
}

然后通过new MyClass() 来创建一个对象实例,而且:通过new操作符创建实例的时候,构造方法(constructor) 是被自动调用,的,这意味着可以在构造方法中做一些初始化的工作。

class MyClass{
    constructor(name){
        this.name = name
    }
    hello(){
        alert(this.name)
    }
}
let p1 = new MyClass('zlm')
p1.hello()

注意:类方法之间是没有逗号的,加了会报错。

ES6的类,完全可以看作构造函数的另一种写法。

class Point {
  // ...
}
​
typeof Point // "function"
Point === Point.prototype.constructor // true

使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致。

上面代码表明,类的数据类型就是函数,类本身就指向构造函数。

3,什么是类

在JavaScript中,类是函数的一种,一些人说在JavaScript中class是一种"语法糖",因为我们实际上可以在没有class关键字的情况下声明一个类。在Es6之前,我们可以通过function去实现一个类,确实可以将类视为一种语法糖来定义构造函数及其原型方法。但与class的方式创建一个类有着重要的差异。由class创建的函数由特殊的内部属性标记,与常规函数不同,如果没有new,则无法调用类构造函数

1, constructor方法

constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。

constructor() {}

constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。

class MyClass {
  constructor() {
    return Object.create(null);
  }
}
​
new MyClass() instanceof MyClass

类的构造函数,不使用new是没法调用的,会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。

上面代码中,constructor函数返回一个全新的对象,结果导致实例对象不是Foo类的实例。

2, 类的实例对象

生成类的实例对象的写法,与ES5完全一样,也是使用new命令。如果忘记加上new,像函数那样调用Class,将会报错。

// 报错
var p1 = MyClass(2, 3);
​
// 正确
var p1 = new MyClass(2, 3);

3,calss的静态方法

类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该
方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
class MyClass {
  static classMethod() {
    return 'hello';
  }
}
​
MyClass.classMethod() // 'hello'var p1 = new MyClass();
p1.classMethod()
​

MyClass类的classMethod方法前有static关键字,表明该方法是一个静态方法,可以直接在MyClass类上调用(MyClass.classMethod()),而不是在MyClass类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。 注意,如果静态方法包含this关键字,这个this指的是类,而不是实例。 Class 的静态属性和实例属性

(1)静态属性

静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。

(2)实例属性

类的实例属性可以用等式,写入类的定义之中
4,项目实战

1, 封装的Http 请求类

真正的项目中,请求头参数可以根据项目需求进行添加,

let ucmsToken = {};
export const setUcmsToken = (tokenName, tokenValue) => {
  ucmsToken = { tokenName, tokenValue };
};
​
/**
 * 合并请求options
 * TODO:目前只支持headers的对象merge
 * @param {*} defOptions
 * @param {*} options
 */
const mergeOptions = (defOptions = {}, options = {}) => {
  const headers = Object.assign({}, defOptions.headers, options.headers);
  return Object.assign({}, defOptions, options, { headers });
};
​
class Http {
  /**
   * http get
   * @param {*} url
   * @param {*} options
   */
  static async get(url, options) {
    const defOptions = {
      headers: {
        "X-Portal-Token": xxx
      },
      mode: "cors",
      credentials: "omit"
    };
    const { tokenName, tokenValue } = ucmsToken;
    if (tokenName && tokenValue) {
      Object.assign(defOptions.headers, {
        "SC-AUTH-TOKEN": tokenValue,
        [tokenName]: tokenValue
      });
    }
    const response = await fetch(url, mergeOptions(defOptions, options));
    return response.json();
  }
  /**
   * http post
   * @param {*} url
   * @param {*} data
   * @param {*} options
   */
  static async post(url, data = {}, options) {
    const defOptions = {
      headers: {
        "Content-Type": "application/json;charset=UTF-8",
        "X-Portal-Token": xxx
      },
      method: "post",
      body: JSON.stringify(data),
      mode: "cors",
      credentials: "omit"
    };
    const { tokenName, tokenValue } = ucmsToken;
    if (tokenName && tokenValue) {
      Object.assign(defOptions.headers, {
        "SC-AUTH-TOKEN": tokenValue, // !登陆成功之后,接口返回的tokenValue
        [tokenName]: tokenValue
      });
    }
    const response = await fetch(url, mergeOptions(defOptions, options));
    return response.json();
  }
  /**
   * http put
   * @param {*} url
   * @param {*} data
   * @param {*} options
   */
  static async put(url,data={},options){
    const defOptions = {
      headers:{
        'Content-Type': 'application/json',
        "X-Portal-Token": xxx
      },
      method:"put",
      body:JSON.stringify(data),
      mode: "cors",
      credentials: "omit"
    }
    const { tokenName, tokenValue } = ucmsToken;
    if (tokenName && tokenValue) {
      Object.assign(defOptions.headers, {
        "SC-AUTH-TOKEN": tokenValue, 
        [tokenName]: tokenValue
      });
    }
    const response = await fetch(url,mergeOptions(defOptions,options))
    return response.json()
  }
  
​

(2) 第二个实战项目:跨域消息传递,postMessage的封装

export const uuid = () => Math.random().toString(36).substr(2) + Date.now().toString(36)
​
​
const handleEmit = function ({ $request, params }) {
    const eventPool = this.eventPool[$request]
    if (eventPool) {
        for (let i = 0; i< eventPool.length; i++) {
            if (eventPool[i].cb) eventPool[i].cb(params)
            if (eventPool[i].once) {
                eventPool.splice(i, 1)
                i--
            }
        }
    }
}
​
const listen = function () {
    const addEventListener = window.addEventListener || window.attachEvent
    const type = window.addEventListener ? 'message' : 'onmessage'
    addEventListener(type, (event) => {
        // 不处理非目标源请求
        if (event.origin !== this.targetOrigin && this.targetOrigin !== '*') return
        const { data: { $type, $request, params } } = event
        switch ($type) {
            case 'emit': {
                handleEmit.call(this, { $request, params })
                break
            }
            default:
        }
    }, false)
}
​
class PostMessage {
    constructor (targetOrigin = '*', targetWindow = window.parent) {
        this.targetOrigin = targetOrigin
        this.targetWindow = () => typeof targetWindow === 'function' ? targetWindow() : 
        targetWindow
        this.eventPool = {}
        listen.call(this)
    }
​
    emit (event, params) {
        const payload = { $request: event, $type: 'emit', params }
        this.targetWindow().postMessage(payload, this.targetOrigin)
    }
​
    on (event, cb) {
        const $uuid = uuid()
        if (!this.eventPool[event]) this.eventPool[event] = []
        if (cb) this.eventPool[event].push({ cb, $uuid })
        return $uuid
    }
​
    off (event, eventId) {
        if (!eventId) {
            delete this.eventPool[event]
        } else if (this.eventPool[event]) {
            const eventIndex = this.eventPool[event].findIndex(item => item.$uuid === eventId)
            if (eventIndex !== -1) this.eventPool[event].splice(eventIndex, 1)
        }
    }
​
    once (event, cb) {
        const $uuid = uuid()
        if (!this.eventPool[event]) this.eventPool[event] = []
        if (cb) this.eventPool[event].push({ cb, $uuid, once: true })
    }
}
export default PostMessage

使用方法: 1,初始化:

    var innerWindow = document.querySelector('#iframe').contentWindow
    var PM = new PostMessage('*', innerWindow)
    var PM = new PostMessage('*', window.parent)
    // 默认参数即是('*', window.parent), 等同于
    var PM = new PostMessage()

2, 实例方法:

方法参数说明
emit(eventName, params)eventName 事件名, 系统双方约定. params 传递给对方的数据
on(eventName, callback)eventName 同上. callback 监听到事件后应该执行的函数, 参数为对方传递的params, 函数体自定义. 特别说明, on方法有一个返回值, 是该次监听的ID
once(eventName, callback)参数同上, 无返回值. once只会监听一次, callback只会执行一次
off(eventName[,ID])eventName 同上. 如果有ID, 关闭该ID的监听行为, 如果无ID, 关闭该事件的全部监听行为