第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

全部開發(fā)者教程

ES6-10 入門教程

首頁(yè) 慕課教程 ES6-10 入門教程 ES6-10 入門教程 ES6實(shí)戰(zhàn)1-實(shí)現(xiàn)Vue3 reactive 源碼

ES6實(shí)戰(zhàn)1-實(shí)現(xiàn) Vue3 reactive 源碼

1. 前言

本節(jié)開始我們將進(jìn)入 ES6 實(shí)戰(zhàn)課程,首先會(huì)花費(fèi)兩節(jié)的時(shí)間來學(xué)習(xí) Vue3 響應(yīng)式原理,并實(shí)現(xiàn)一個(gè)基礎(chǔ)版的 Vue3 響應(yīng)式系統(tǒng);然后通過 Promise 來封裝一個(gè)真實(shí)業(yè)務(wù)場(chǎng)景中的 ajax 請(qǐng)求;最后我們會(huì)聊聊前端開發(fā)過程中的編程風(fēng)格。

本實(shí)戰(zhàn)主要通過對(duì)前面 ES6 的學(xué)習(xí)應(yīng)用到實(shí)際開發(fā)中來,Vue3 的響應(yīng)式系統(tǒng)涵蓋了大部分 ES6 新增的核心 API,

如:Proxy、Reflect、Set/Map、WeakMap、Symbol 等 ES6 新特性的應(yīng)用。更加深入地學(xué)習(xí) ES6 新增 API 的應(yīng)用場(chǎng)景。

由于篇幅內(nèi)容有限,本實(shí)戰(zhàn)不會(huì)完全實(shí)現(xiàn) Vue3 響應(yīng)式系統(tǒng)的所有 API,主要實(shí)現(xiàn) reactive 、 effect 這四個(gè)核心 API,其他 API 可以參考 vue-next

源碼。本節(jié)的目錄結(jié)構(gòu)和命名和 Vue3 源碼基本一致,在閱讀源碼的時(shí)候我們能看到作者的思考,和功能細(xì)顆粒度的拆分,使得代碼更易于擴(kuò)展和復(fù)用。

2. 環(huán)境配置

2.1 rollup 配置

ES6 很多 API 不能在低版本瀏覽器自己運(yùn)行,另外我們?cè)陂_發(fā)源碼的時(shí)候需要大量地使用模塊化,以拆分源碼的結(jié)構(gòu)。在學(xué)習(xí)模塊化一節(jié)時(shí),我們使用了 Webpack 作用打包工具,由于 Vue3 使用的是 rollup,更加適合框架和庫(kù)的大包,這里我們也和 Vue3 看齊,rollup 最大的特點(diǎn)是按需打包,也就是我們?cè)谠创a中使用的才會(huì)引入,另外 rollup 打包的結(jié)果不會(huì)產(chǎn)生而外冗余的代碼,可以自己閱讀。下面我們來看下 rollup 簡(jiǎn)單的配置:

// rollup.config.js
import babel from "rollup-plugin-babel";
import serve from "rollup-plugin-serve";

export default {
  input: "./src/index.js",
  output: {
    format: "umd", // 模塊化類型
    file: "dist/umd/reactivity.js",
    name: "VueReactivity", // 打包后的全局變量的名字
    sourcemap: true,
  },
  plugins: [
    babel({
      exclude: "node_modules/**",
    }),
    process.env.ENV === "development"
      ? serve({
          open: true,
          openPage: "/public/index.html",
          port: 3000,
          contentBase: "",
        })
      : null,
  ],
};

上面的配置內(nèi)容和 webpack 很相似,是最基礎(chǔ)的編譯內(nèi)容,有興趣的小伙伴可以去了解一下。本節(jié)源碼 在 ES6-Wiki 倉(cāng)庫(kù)的 vue-next 目錄下,在這個(gè)項(xiàng)目中可以直接啟動(dòng),在啟動(dòng)前需要在項(xiàng)目根目錄中安裝依賴。本項(xiàng)目使用的是 yarn workspace 的工作環(huán)境,可以在根目錄中共享 npm 包。

2.2 調(diào)試源碼

在開發(fā)的過程中需要對(duì)我們編寫的代碼進(jìn)行調(diào)試,這里我們?cè)?public 目錄中創(chuàng)建了一個(gè) html 文件用于在瀏覽器中打開。并且引入了 reactivity 的源碼可以參考對(duì)比我們實(shí)現(xiàn)的 API 的功能,同學(xué)在使用時(shí)可以打開注釋進(jìn)行驗(yàn)證。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app"></div>

	<!-- 我們自己實(shí)現(xiàn)的 reactivity 模塊 -->
	<script src="/dist/umd/reactivity.js"></script>

  <!-- vue的 reactivity 模塊,測(cè)試時(shí)可以使用 -->
	<!-- <script src="./vue.reactivity.js"></script> -->

  <script>
    const { reactive, effect } = VueReactivity;
    const proxy = reactive({
      name: 'ES6 Wiki',
    })
    document.getElementById('app').innerHTML = proxy.name;
  </script>
</body>
</html>

3. reactive 實(shí)現(xiàn)

在實(shí)現(xiàn) Vue3 的響應(yīng)式原理前,我們先來回顧一下 Vue2 的響應(yīng)式存在什么缺陷,主要有以下三個(gè)缺陷:

  • 默認(rèn)會(huì)劫持的數(shù)據(jù)進(jìn)行遞歸;
  • 不支持?jǐn)?shù)組,數(shù)組長(zhǎng)度改變是無效的;
  • 不能劫持不存在的屬性。

Vue3 使用了 Proxy 去實(shí)現(xiàn)數(shù)據(jù)的代理,在實(shí)現(xiàn) Vue3 的響應(yīng)式原理的同時(shí),我們需要思考 Proxy 會(huì)不會(huì)存在上面的缺陷,它的缺點(diǎn)又是什么呢?

3.1 數(shù)據(jù)劫持

首先我在 reactive 文件中定義并導(dǎo)出一個(gè) reactive 函數(shù),在 reactive 中返回一個(gè)創(chuàng)建響應(yīng)式對(duì)象的方法。createReactiveObject 函數(shù)主要是為了創(chuàng)建響應(yīng)式對(duì)象使用,在 reactive 的相關(guān) API 中,很多都需要?jiǎng)?chuàng)建響應(yīng)式對(duì)象,這樣可以復(fù)用,而且更加直觀。

// vue-next/reactivity-1/reactive.js
import { isObject } from "../shared/index";
import { mutableHandlers } from './baseHandlers';

export function reactive(target: object) {
    // 1.創(chuàng)建響應(yīng)式對(duì)象
    return createReactiveObject(target, mutableHandlers)
}

function createReactiveObject(target, baseHandlers) {
    // 3.對(duì)數(shù)據(jù)進(jìn)行代理
    const proxy = new Proxy(target, baseHandlers);
    return proxy;
}

下面的代碼是 Proxy 處理對(duì)象的回調(diào),包括 get、set、deleteProperty 等回調(diào)方法,具體用法可以參考 Proxy 小節(jié)內(nèi)容。這樣我們就實(shí)現(xiàn)了攔截?cái)?shù)據(jù)的功能。

// vue-next/reactivity-1/baseHandlers.js
function createGetter() {
  return function get(target, key, receiver) {
    console.log('獲取值');
    return target[key];
  }
}

function createSetter() {
  return function get(target, key, value, receiver) {
    console.log('設(shè)置值');
    target[key] = value;
  }
}

function deleteProperty(target, key) {
  delete target[key];
}

const get = createGetter()
const set = createSetter()

export const mutableHandlers = {
  get,
  set,
  deleteProperty,
  // has,
  // ownKeys
}

在 Vue3 源碼中使用 Reflect 來操作對(duì)象的,Reflect 和 Proxy 方法一一對(duì)應(yīng),并且 Reflect 操作后的對(duì)象有返回值,這樣我們可以對(duì)返回值做異常處理等,修改上面的代碼如下:

// vue-next/reactivity-1/baseHandlers.js
function createGetter() {
  return function get(target, key, receiver) {
    console.log('獲取值');
    const res = Reflect.get(target, key, receiver);
    return res;
  }
}

function createSetter() {
  return function get(target, key, value, receiver) {
    console.log('設(shè)置值');
    const result = Reflect.set(target, key, value, receiver);
    return result;
  }
}

下面是測(cè)試用例,可以放在 public/index.html 下執(zhí)行。

const { reactive } = VueReactivity;
const proxy = reactive({
  name: 'ES6 Wiki',
})
proxy.name = 'imooc ES6 wiki';	// 設(shè)置值
console.log('proxy.name');		// 獲取值
// proxy.name

3.2 實(shí)現(xiàn)響應(yīng)式邏輯

首先我們需要對(duì)傳入的參數(shù)進(jìn)行判斷,如果不是對(duì)象則直接返回。

// shared/index.js
const isObject = val => val !== null && typeof val === 'object'

function createReactiveObject(target, baseHandlers) {
    if (!isObject(target)) {
        return target
    }
    ...
}

在使用時(shí),用戶可能多次代理對(duì)象或多次代理過的對(duì)象,如:

var obj = {a:1, b:2};
var proxy = reactive(obj);
var proxy = reactive(obj);
// 或者
var proxy = reactive(proxy);
var proxy = reactive(proxy);

像上面這樣的情況我們需要處理,不能多次代理。所以我們這里要將代理的對(duì)象和代理后的結(jié)果做一個(gè)映射表,這樣我們?cè)诖頃r(shí)判斷此對(duì)象是否被代理即可。這里的映射我們用到了 WeakMap 弱引用。

export const reactiveMap = new WeakMap();

function createReactiveObject(target, baseHandlers) {
  if (!isObject(target)) {
    return target
  }
  const proxyMap = reactiveMap;
  const existingProxy = proxyMap.get(target);
	// 這里判斷對(duì)象是否被代理,如果映射表上有,則說明對(duì)象已經(jīng)被代理,則直接返回。
  if (existingProxy) {
    return existingProxy;
  }
  const proxy = new Proxy(target, baseHandlers);
  // 這里在代理過后把對(duì)象存入映射表中,用于判斷。
  proxyMap.set(target, proxy);
  return proxy;
}

上面我們已經(jīng)基本實(shí)現(xiàn)了響應(yīng)式,但是有個(gè)問題,我們只實(shí)現(xiàn)了一層響應(yīng)式,如果是嵌套多層的對(duì)象這樣就不行了。Vue2 是使用的是深層遞歸的方式來做的,而我們使用了 Proxy 就不需要做遞歸操作了。Proxy 在獲取值的時(shí)候會(huì)調(diào) get 方法,這時(shí)我們只需要在獲取值時(shí)判斷這個(gè)值是不是對(duì)象,如果是對(duì)象則繼續(xù)代理。

import { isSymbol, isObject } from '../shared';
import { reactive } from './reactive';

function createGetter() {
  return function get(target, key, receiver) {
    const res = Reflect.get(target, key, receiver);
    if (isSymbol(key)) {
      return res;
    }
    console.log("獲取值,調(diào)用get方法"); // 攔截get方法
    if (isObject(res)) {
      return reactive(res);
    }
    return res;
  };
}

在獲取值的時(shí)候有很多邊界值需要特殊處理,這里列出了如果 key 是 symbol 類型的話直接返回結(jié)果,當(dāng)然還有其他場(chǎng)景,同學(xué)可以去看 Vue3 的源碼。

在我們?cè)O(shè)置值的時(shí)候,如果是新增屬性時(shí) Vue2 是不支持的,使用 Proxy 是可以的,但是我們需要知道當(dāng)前操作是新增還是修改?所以需要判斷有無這個(gè)屬性,如果是修改則肯定有值。一般判斷有兩種情況:

  • 數(shù)組新增和修改邏輯,需要先進(jìn)行數(shù)組判斷,當(dāng)我們修改的 key 小于數(shù)組的長(zhǎng)度時(shí)說明是修改,反之則是新增;
  • 對(duì)象新增和修改邏輯,對(duì)象判斷是否有屬性就比較簡(jiǎn)單了,直接取值驗(yàn)證即可。
// 判斷數(shù)組
export const isArray = Array.isArray;
export const isIntegerKey = key => '' + parseInt(key, 10) === key;	// 判斷key是不是整型
// 使用 Number(key) < target.length 判斷數(shù)組是不是新增,key 小于數(shù)組長(zhǎng)度說明有key

// 判斷對(duì)象是否有某屬性
const hasOwnProperty = Object.prototype.hasOwnProperty
export const hasOwn = (val, key) => hasOwnProperty.call(val, key)

// 判斷有無key
const hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key);

最終我們可以得到下面的 createSetter 函數(shù)。

function createSetter() {
  return function get(target, key, value, receiver) {
    const oldValue = target[key];	// 獲取舊值
    const hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key);

    const result = Reflect.set(target, key, value, receiver);

    if (!hadKey) {
      console.log('新增屬性');
    } else if (hasChanged(value, oldValue)) {
      console.log('修改屬性');
    }

    return result;
  };
}

4. 小結(jié)

以上內(nèi)容就是 Vue3 中實(shí)現(xiàn) reactive API 的核心源碼,文章的完整代碼放在了 reactivity-1 目錄下。源碼中的實(shí)現(xiàn)方式可能會(huì)有所改變,在對(duì)照學(xué)習(xí)時(shí)可以參考 Vue 3.0.0 版本。本節(jié)實(shí)現(xiàn)響應(yīng)式的核心是 Proxy 對(duì)數(shù)據(jù)的劫持,通過對(duì) set 和 get 方法的實(shí)現(xiàn)來處理各種邊界數(shù)據(jù)問題。在學(xué)習(xí)過程中需要注意多次代理、設(shè)置屬性時(shí)判斷是新增還是修改,這對(duì)后面實(shí)現(xiàn) effect 等 API 有很重要的作用。