匠人学院 JR Academy学AI来匠人
匠人学院 JR Academy学AI来匠人

Follow Us

linkedinfacebooktwitterinstagramweiboyoutubebilibilitiktokxigua

We Accept

/image/layout/pay-paypal.png/image/layout/pay-visa.png/image/layout/pay-master-card.png/image/layout/pay-airwallex.png/image/layout/pay-alipay.png
EN

关于公司

关于我们元宇宙课堂新闻资讯匠人工作成为导师匠人导师联系我们匠人商店J3.Club

匠人资源

工作内推匠人活动1对1私教行业白皮书线上学习平台面试中心分享面试经验Internship会员中心

AI 工具

AI 工具箱考证匠 Cert Master求职匠 Job Hunter牛小匠 UniMate AI

AI 学习方向

全部学习方向AI EngineerContext EngineeringVibe CodingPrompt MasterAI BuilderAI 产品经理Python 入门

AI 应用提效

AI 办公提效AI 数据分析AI 财务AI 内容创作AI 视觉创作前端开发Hermes AgentOpenClaw 本地智能体

大学资源

墨尔本大学昆士兰大学新南威尔士大学悉尼大学莫那什大学阿德莱德大学RMITQUTUTS

少儿 AI 教育

Airbotix 少儿 AI 编程澳洲家长实用资料库NAPLAN 成绩单怎么看My School 学校数据指南悉尼私校学费 2026少儿编程课程与训练营

移民服务

澳洲移民技术移民189/190/491雇主担保482/186/494投资移民188/888英国移民美国移民加拿大移民

企业合作

P3职业孵化器Enterprise (EN)企业培训实习合作招聘合作申请合作

求职代理

岗位代投职位监控LinkedIn代运营LinkedIn人脉代加了解P3项目

匠人支持

FAQsTerms & ConditionsPrivacy PolicyCancellation & Refund PolicySite map

Top Categories

Web全栈班DevOps项目班数据工程全栈班数据分析项目班编程入门班Business Analyst实习算法集训营

求职就业

BA和产品经理实习数据科学实习数据分析实习Marketing实习简历修改面试指导导师指导VIP

地址

Level 10b, 144 Edward Street, Brisbane CBD(Headquarter)
Level 2, 171 La Trobe St, Melbourne VIC 3000
四川省成都市武侯区桂溪街道天府大道中段500号D5东方希望天祥广场B座45A13号
Business Hub, 155 Waymouth St, Adelaide SA 5000

联系方式

hello@jiangren.com.au0421-672-555

Disclaimer

footer-disclaimerfooter-disclaimer

JR Academy acknowledges Traditional Owners of Country throughout Australia and recognises the continuing connection to lands, waters and communities. We pay our respect to Aboriginal and Torres Strait Islander cultures; and to Elders past and present. Aboriginal and Torres Strait Islander peoples should be aware that this website may contain images or names of people who have since passed away.

匠人学院网站上的所有内容,包括课程材料、徽标和匠人学院网站上提供的信息,均受澳大利亚政府知识产权法的保护。严禁未经授权使用、销售、分发、复制或修改。违规行为可能会导致法律诉讼。通过访问我们的网站,您同意尊重我们的知识产权。JR Academy Pty Ltd 保留所有权利,包括专利、商标和版权。任何侵权行为都将受到法律追究。查看用户协议

© 2017-2026 JR Academy Pty Ltd. All rights reserved.

ABN 26621887572

首页/资源中心/文章详情
JR Academy · Blog职业洞察

仿写'Vue生态'系列___"Proxy双向绑定与封装请求"

vue3.0选择了这个属性, 虽然也会提供兼容版本, 但基本也算是跟老版ie说再见了, Proxy会解决之前无法监听数组的修改这个痛点, 也算是我辈前端的福音了.

发布日期2019-09-04
阅读时长5 分钟
作者JiangRen Mr

快速导航

  • 一. Proxy
  • 二. Reflect
  • 三. 封装简易的"axios"
  • 四. 服务器
  • 五.跨域
  • end
  1. vue3.0使用了Proxy进行数据的劫持, 那当然就有必要研究并实战一下这方面知识了.
  2. 对Reflect进行解读, 并将Object的操作少部分改为Reflect的形式.
  3. 异步不能总用定时器模拟, 本次自己封装一个简易的'axios'.
  4. 有了请求当然需要服务器, 用koa启动一个简易的服务.

一. Proxy

vue3.0选择了这个属性, 虽然也会提供兼容版本, 但基本也算是跟老版ie说再见了, Proxy会解决之前无法监听数组的修改这个痛点, 也算是我辈前端的福音了.
使用方面会有很大不同, defineProperty是监控一个对象, 而Proxy是返回一个新对象, 这就需要我完全重写Observer模块了, 话不多说先把基本功能演示一下.
由下面的代码可知:

  1. Proxy可以代理数组.
  2. 代理并不会改变原数据的类型, Array还是Array.
  3. 修改length属性会触发set, 浏览器认为length当然是属性, 修改他当然要触发set.
  4. 像是push, pop这种操作也是会触发set的, 而且不止一次, 可以借此看出这些方法的实现原理.
  let ary = [1, 2, 3, 4];
  let proxy = new Proxy(ary, {
    get(target, key) {
      return target[key];
    },
    set(target, key, value) {
        console.log('我被触发了');
      return value;
    }
  });
  console.log(Array.isArray(proxy)); // true
  proxy.length = 1; // 我被触发了

我之前写的劫持模块就需要彻底改版了
cc_vue/src/Observer.js
改变$data指向我选择在这里做, 为了保持主函数的纯净.

// 数据劫持
import { Dep } from './Watch';
let toString = Object.prototype.toString;
class Observer {
  constructor(vm, data) {
    // 由于Proxy的机制是返回一个代理对象, 那我们就需要更改实例上的$data的指向了
    vm.$data = this.observer(data);
  }
}

export default Observer;

observer
对象与数组是两种循环的方式, 每次递归的解析里面的元素, 最后整个对象完全由Proxy组成.

 observer(data) {
    let type = toString.call(data),
        $data = this.defineReactive(data);
    if (type === '[object Object]') {
      for (let item in data) {
        data[item] = this.defineReactive(data[item]);
      }
    } else if (type === '[object Array]') {
      let len = data.length;
      for (let i; i < len; i++) {
        data[i] = this.defineReactive(data[i]);
      }
    }
    return $data;
  }

defineReactive
遇到基本类型我会直接return;
代理基本类型还会报错😯;

 defineReactive(data) {
    let type = toString.call(data);
    if (type !== '[object Object]' && type !== '[object Array]') return data;
    let dep = new Dep(),
        _this = this;
    return new Proxy(data, {
      get(target, key) {
        Dep.target && dep.addSub(Dep.target);
        return target[key];
      },
      set(target, key, value) {
        if (target[key] !== value) {
        // 万一用户付给了一个新的对象, 就需要重新生成监听元素了.
          target[key] = _this.observer(value);
          dep.notify();
        }
        return value;
      }
    });
  }

Observer模块改装完毕
现在vm上面的data已经是Proxy代理的data了, 也挺费性能的, 所以说用vue开发的时候, 尽量不要弄太多数据在data身上.

二. Reflect

这个属性也蛮有趣的, 它的出现很符合设计模式, 数据就是要有一套专用的处理方法, 而且函数式处理更符合js的设计理念.

  1. 静态方法 Reflect.defineProperty() 基本等同于 Object.defineProperty() 方法,唯一不同是返回 Boolean 值, 这样就不用担心defineProperty时的报错了.
  2. Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。

下面把常用的方法演示一下
操作成功或失败会返回布尔值

 let obj = {name:'lulu'};
  console.log(Reflect.get(obj,'name')) // name
  console.log(Reflect.has(obj,'name')) // true
  console.log(Reflect.has(obj,'name1')) // false
  console.log(Reflect.set(obj,'age',24)) // true
  console.log(Reflect.get(obj,'age')) // 24

把我的代码稍微改装一下
cc_vue/src/index.js

 proxyVm(data = {}, target = this) {
    for (let key in data) {
      Reflect.defineProperty(target, key, {
        enumerable: true, // 描述属性是否会出现在for in 或者 Object.keys()的遍历中
        configurable: true, // 描述属性是否配置,以及可否删除
        get() {
          return Reflect.get(data,key)
        },
        set(newVal) {
          if (newVal !== data[key]) {
            Reflect.set(data,key,newVal)
          }
        }
      });
    }
  }

三. 封装简易的"axios"

我见过很多人离开axios或者jq中的ajax就没法做项目了, 其实完全可以自己封装一个, 原理都差不多, 而且现在也可以用'feach'弄, 条件允许的情况下真的不一定非要依赖插件.
独立的文件夹负责网络相关的事宜;
cc_vue/use/http

class C_http {
  constructor() {
    // 请求可能很多, 并且需要互不干涉, 所以决定每个类生成一个独立的请求
    let request = new XMLHttpRequest();
    request.responseType = 'json';
    this.request = request;
  }
}

编写插件的时候, 先要考虑用户会怎么用它

  1. 用户指定请求的方法, 本次只做post与get.
  2. 可以配置请求地址.
  3. 可以传参, 当然post与get处理参数肯定不一样.
  4. 返回值我们用Promise的形式返回给用户.
  http.get('http:xxx.com', { name: 'lulu'}).then(data => {});
  http.post('http:xxx.com', { name: 'lulu'}).then(data => {});

get与post方法其实不用每次都初始化, 我们直接写在外面
处理好参数直接调用open方法, 进入open状态某些参数才能设置;
在有参数的情况下为链接添加'?';
参数品在链接后面, 我之前遇到一个bug, 拼接参数的时候如果结尾是'&'部分手机出现跳转错误, 所以为了防止特殊情况的发生, 我们要判断一下干掉结尾的'&';

function get(path, data) {
  let c_http = new C_http();
  let str = '?';
  for (let i in data) {
    str += `${i}=${data[i]}&`;
  }
  if (str.charAt(str.length - 1) === '&') {
    str = str.slice(0, -1);
  }
  path = str === '?' ? path : `${path}${str}`;
  c_http.request.open('GET', path);
  return c_http.handleReadyStateChange();
}

post
这个就很好说了, .data是请求自带的.

function post(path, data) {
  let c_http = new C_http();
  c_http.request.open('POST', path);
  c_http.data = data;
  return c_http.handleReadyStateChange();
}

handleReadyStateChange

handleReadyStateChange() { 
    // 这个需要在open之后写
    // 设置数据类型
    this.request.setRequestHeader(
      'content-type',
      'application/json;charset=utf-8'
    );
    // 现在前端所有返回都是Promise化;
    return new Promise((resolve) => {
      this.request.onreadystatechange = () => {
        // 0    UNSENT    代理被创建,但尚未调用 open() 方法。
        // 1    OPENED    open() 方法已经被调用。
        // 2    HEADERS_RECEIVED    send() 方法已经被调用,并且头部和状态已经可获得。
        // 3    LOADING    下载中; responseText 属性已经包含部分数据。
        // 4    DONE    下载操作已完成。
        if (this.request.readyState === 4) {
        // 这里因为是独立开发, 就直接写200了, 具体项目里面会比较复杂
          if (this.request.status === 200) {
           // 返回值都在response变量里面
            resolve(this.request.response);
          }
        }
      };
      // 真正的发送事件.
      this.send();
    });
  }

send

send() {
// 数据一定要JSON处理一下
    this.request.send(JSON.stringify(this.data));
  }

很多人提到 "拦截器" 会感觉很高大上, 其实真的没啥
简易的拦截器"interceptors"👇

// 1: 使用对象不使用[]是因为可以高效的删除拦截器
const interceptorsList = {};
// 2: 每次发送数据之前执行所有拦截器, 别忘了把请求源传进去.
  send() {
    for (let i in interceptorsList) {
      interceptorsList[i](this);
    }
    this.request.send(JSON.stringify(this.data));
  }
// 3: 添加与删除拦截器的方法, 没啥东西所以直接协议期了.
function interceptors(cb, type) {
  if (type === 'remove') {
    delete interceptorsList[cb];
  } else if (typeof cb === 'function') {
    interceptorsList[cb] = cb;
  }
}

边边角角的小功能

  1. 设置超出时间与超出时的回调.
  2. 请求的取消
class C_http {
  constructor() {
    let request = new XMLHttpRequest();
    request.timeout = 5000;
    request.responseType = 'json';
    request.ontimeout = this.ontimeout;
    this.request = request;
  }
 ontimeout() {
    throw new Error('超时了,快检查一下');
  }
abort() {
    this.request.abort();
  }
}

简易的'axios'就做好, 普通的请求都没问题的

四. 服务器

请求做好了, 当然要启动服务了, 本次就不连接数据库了, 要不然就跑题了.

koa2
不了解koa的同学跟着做也没问题

npm install koa-generator -g
Koa2 项目名

cc_vue/use/server 是本次工程的服务相关存放处.

cc_vue/use/server/bin/www
端口号可以随意更改, 当时9999被占了我就设了9998;

const pros = '9998';
var port = normalizePort(process.env.PORT || pros);

cc_vue/use/server/routes/index.js

这个页面就是专门处理路由相关, koa很贴心, router.get就是处理get请求.
每个函数必须写async也是为了著名的'洋葱圈'.
想了解更多相关知识可以去看koa教程, 我也是用到的时候才会去看一眼.
写代码的时候遇到需要测试延迟相关的时候, 不要总用定时器, 要多自己启动服务.

const router = require('koa-router')();

router.get('/', async (ctx, next) => {
  ctx.body = {
    data: '我是数据'
  };
});

router.post('/', async (ctx, next) => {
  ctx.body = ctx.request.body;
});

module.exports = router;

写到现在可以开始跑起来试试了

五.跨域

😺一个很传统的问题出现了'跨域'.
这里我们简单的选择插件来解决, 十分粗暴.
cc_vue/use/server/app.js

npm install --save koa2-cors
var cors = require('koa2-cors');
app.use(cors());

既然说到这里就, 那就总结一下吧
跨域的几种方式

  1. jsonp 这个太传统了, 制作一个script标签发送请求.
  2. cors 也就是服务端设置允许什么来源的请求, 什么方法的请求等等,才可以跨域.
  3. postMessage 两个页面之间传值, 经常出现在一个页面负责登录, 另一个页面获取用户的登录token.
  4. document.domain 相同的domain可以互相拿数据.
  5. window.name 这个没人用, 但是挺好玩, 有三个页面 a,b,c, a与b 同源, c单独一个源, a用iframe打开c页面, c把要传的值放在 window.name上,监听加载成功事件, 瞬间改变 iframe 的地址, 为b, 此时 b 同源, window 会被继承过来, 偷梁换柱, 利用了换地址 window不变的特点;
  6. location.hash 这个也好玩, 是很聪明的人想出来的, 有三个页面 a,b,c, a与b 同源, c单独一个源,a给c传一个 hash 值(因为一个网址而已,不会跨域), c把 hash解析好, 把结果 用iframe 传递给 b,b 使用 window.parent.parent 找到父级的父级, window.parent.parent.location.hash = 'xxxxx', 操控父级;
  7. http-proxy 就比如说vue的代理请求, 毕竟服务器之间不存在跨域.
  8. nginx 配置一下就好了, 比前端做好多了
  9. websocket 人家天生就不跨域.

本次测试的dom结构

<div id="app">
      <p>n: {{ n.length }}</p>
      <p>m: {{ m }}</p>
      <p>n+m: {{ n.length + m }}</p>
      <p>{{ http }}</p>
    </div>
 let vm = new C({
    el: '#app',
    data: {
      n: [1, 2, 3],
      m: 2,
      http: '等待中'
    }
  });
  http.get('http://localhost:9998/', { name: 'lulu', age: '23' }).then(data => {
    vm.http = data.data;
    vm.n.length = 1
    vm.n.push('22')
  });

具体效果请在工程里面查看

end

做这个工程能让自己对vue对框架以及数据的操作有更深的理解, 受益匪浅.
下一集:

  1. 对指令的解析.
  2. 具体指令的处理方式.
  3. 篇幅够的话聊聊事件与生命周期

 

本文章转载自SegmentFault.

资源
作者JiangRen Mr
一键分享或复制链接
作者: JiangRen Mr查看该作者的更多文章 →
← 上一篇如何写出一个惊艳面试官的深拷贝?下一篇 →“英语太渣!”主流媒体诟病中国留学生,澳学者声援:“简直是歧视”

相关文章推荐

澳洲留学生必看!二手闲置平台大盘点!

2024-08-27

Monash萌新不可不知!WES个人信息须每六个月更新一次!附具体保姆级更新手册

2024-08-07

真心建议在莫纳什的学生都来看看,官方独享AI工具!

2024-08-02

UNSW2024新生在哪里?来进组织啦!

2024-07-22

2024UNSW留学生有求必应群来袭!

2024-07-12

阿德莱德大学 | 阿德莱德大学学子必备-- Online Library 线上图书馆 查找文献 的使用指南! 写 Essay 不用愁啦~

2022-01-18
查看全部文章 →
JR Academy
全球华人学习 AI 第一站
✓15000+ 学员
✓50+ 课程
✓AI 驱动学习平台
训练营免费资源AI学习职业辅导
精选推荐
AI 职业影响地图
测测你的职业风险等级,查看转型路径与学习方向
热门工具
AI & 数据训练营
系统化课程 + 真实项目实战,快速提升竞争力
热门课程
1v1 就业辅导
资深导师一对一指导,简历优化 + 面试准备
就业保障
企业内训定制
AI 技能培训方案,助力团队升级
企业服务
热门标签
Vibe CodingAI 编程CursorClaude求职攻略Prompt前端开发后端开发
订阅更新

获取最新 AI 学习资源、技术教程和求职攻略,直接送达邮箱。

我们尊重您的隐私,不会发送垃圾邮件