Minimal and Clean blog theme for Hugo

授课项目首屏加载慢


现象

epp项目在pad端加载需要10-30s时间。

为什么会这样?

pad端是RN项目,前面2个界面的操作之后开启webview访问学生端网址,加载epp项目。

观察epp学生端项目的加载:

![优化前][优化-前.jpg]

这个是渲染第一帧前加载资源的截图,可以看到有17个js文件,而很明显的,fabric、ckeditor、aliplayer这些体积很大的库在学生端是不需要的(这3个文件都是编辑器用的,epp项目分学生端、教师端、编辑器),而且资源数量较多。

两个原因导致了pad端需要加载10s的问题:资源数量多、很多不需要的代码被加载。

30s的情况出现在网络差的时候,比如上课时,有30个学生同时使用pad,瓜分宽带导致资源下载速度更慢。这个后面也有方案尝试解决。

问题处在哪?

从代码的入口文件看起,发现了这段代码:

![懒加载][优化-代码.png]

这段代码的作用是根据url来加载不同的场景(学生端、教师端、编辑器),重点是下面这段代码。

const importScene = () =>
  import(`@/scene/${sceneName}Scene/${sceneName}Scene`)
    .then(module => module.default);

使用webpack的代码分割、懒加载机制。webpack会将import("./module")作为分割点,将其放在单独的chunk文件中,实现懒加载。
import()也可以接受动态表达式,比如这样import(./routes/${path}/route),此时import() 为每一个可能的模块创建独立的chunk,这句话的重点是可能的模块,也就是上面代码的问题所在。

对于前面那段代码,import的动态的部分就是sceneName,对于webpack来说它是一个未知变量,所以webpack认为它是可能的模块

在我们的业务中,可能的模块包含来学生端、教室端、编辑器,那么webpack会将3个大模块全部分别打包,而且将其全部下载。从而导致来前面提到的2个问题。

解决问题

尝试着把上面提到的那段动态表达式改成确定的语句,比如:

const importStudentScene = () =>
    import('@/scene/studentScene/studentScene')
        .then(module => module.default);
const app = edApplication.getInstance('edinnova');
importStudentScene().then(studentScene => {
    app.addScene(new studentScene());
    app.startScene('studentScene', '#app');
});

再次开启项目:

![优化后][优化-后.jpg]

可以看到数量和体积都小了很多。

![优化后][优化-后2.jpg]

这是在电脑上的截图,优化后首屏从3.4s变为2.1s,效果明显。这只是一个几乎没有改动代码的一步优化。

可以看到优化后还是有1个600k,1个200k两个chunk文件,后面还需要分析这两个chunk文件,看是否有优化空间。

30个学生同时使用pad

有一个猜想,因为30个学生是在同一个教室的局域网内。考虑使用webRTC的种子下载技术,实现下载速度的优化。

Read more ⟶

electron


基于 electronforge 开发

npm init electron-app@latest my-app -- --template=vite

不能用 pnpm 因为软连接导致 make 报错了

Read more ⟶

怎么写简历


《程序员面试金典》摘录

  • 篇幅较短的简历通常会令人印象更为深刻
  • 聘人员浏览一份简历一般只会用10秒钟左右
  • 建议工作经验不足10年的求职者将简历压缩成1页

工作经历

简历不是也不应该是工作经历的编年史。应该只列举那些相关的工作经验——那些会给别人留下深刻印象的工作经验。

在描述工作经历时,请尽量采用这样的格式:“使用Y实现了X,从而达到了Z效果。”比如下面这个例子:

❑ “通过实施分布式缓存功能减少了75% 的对象渲染时间,从而使得用户登录速度加快了10%。” 下面还有一个例子,描述略有不同:

❑ “实现了一种新的基于windiff的比较算法,系统平均匹配精度由1.2提升至1.5。”

尽管不是所有经历都能套用此句型,但原则无非是描述做过什么,如何完成,结果如何。理想的做法是尽可能地量化结果。

项目经历

简历上应该只列举2到4个最重要的项目。描述项目要简明扼要,比如使用哪些语言或技术。你也可以加上一些细节,比如该项目是个人独立开发还是团队合作的成果,是某一门课程的一部分还是独立开发的。当然,除非能让简历更出彩,否则这些细节不一定放到简历上。独立项目一般说来比课程设计会更加出彩,因为这些项目会展现出你的主动性。

项目也不要列太多。很多求职者都犯过这样的错误,在简历上一股脑儿列出先前做过的13个项目,鱼龙混杂,效果反而不佳。

那么,应该列出哪些项目呢?说实在的,其实这并没有那么重要。有一些公司非常喜欢开源项目(参与这些项目说明具备了大型代码库开发的经验),另一些公司则更喜欢独立项目(了解你在这些项目中的贡献会更加容易)。你的项目可以是一款移动应用、网络应用或者任何东西。最重要的是,你确实参与了开发。

编程语言

一种策略是列出你用过的主要语言,后面加上熟练程度,比如像下面这样的。

❑ 编程语言:Java(非常熟练),C++(熟练),JavaScript(有过使用经验)。

面试准备清单

逐字逐句检查简历,确保回答每个部分或项目时都能对答如流。填写下面的表格,它会助你一臂之力。

可以在表头中列出在简历中提到的主要事项,比如项目、职位或活动。然后在每一行写清楚常见问题。

在面试前温习这个表格。为了方便掌握和记忆,可以把每个故事提炼为几个关键词。这样,就可以在面试时胸有成竹、从容不迫了。

另外,确保你有1至3个项目可以拿得出手,并能就其细节侃侃而谈。你应该是这些项目的主力,并且有能力同面试官深入探讨相关的技术细节。

你有哪些缺点

你应该提到真实、合乎情理的缺点,然后话锋一转,强调自己是如何克服这个缺点的。

举例如下。

“有时候,我对细节不够重视。好的一面是我反应迅速,执行力强,但不免会因为粗心大意而犯错。有鉴于此,我总是会找其他同事帮忙检查自己的工作,确保不出问题。”


掘金

工作经历&项目经历

加分写法:

  • 工作经历项目经历可参照万能的STAR法则来写,STAR不清楚的童鞋点这里
  • 效力过哪些公司,我们匹配的公司? BAT? 知名大型互联网公司?
  • 做过什么行业领域,和我们目前的行业是否匹配
  • 擅长的技术语言,应用了哪些技术栈,(Java, Scala,Ruby, React, Vue, Microservice…)
  • 经历的项目复杂度,及在项目中承担什么样的角色(人的变化/技术的变化/环境的变化/不同工作经历相同角色的不同点)
  • 时间节点(空档期)

栗子2正确打开方式:

西安XXX公司 Java工程师 — 2016.2月-2017.2月

1、MOGU推荐架构数据与缓存层设计开发

  • MOGU是一款时尚资讯app,负责推荐页面资讯feed流的展示及用户历史的展示
  • 负责数据层,处理前端逻辑整个开发工作,分布式rpc服务搭建
  • 负责进行压测监测、缓存处理,对接又进行改进优化,主用redis缓存

2、基于JAVA的电商爬虫开发

  • 使用java搭建爬虫server平台,进行配置和开发,进行网页改版监测功能开发
  • 爬取淘宝时尚品牌与其他电商网站商品品牌与详情等
  • 通过频率、ip池、匿名代理等应对一些网站的反爬

3、同图搜索Solr服务开发

基于算法组的同图策略,使用solr做java接又实现rpc服务搭建,进行索引构建和solr实现

北京XXX

java大数据工程师— 2013.4月-2015.12月

1、负责实时流消息处理应用系统构建和实现

  • 在调研了kafka的优势和我们的具体需求之后,用kafka作为消费者,保证高吞吐处理消息,并持久化消息的同时供其它服务使用,进行了系统的设计和搭建使用。 本地日志保证消息不丢失,并通过记录游标滑动重复读取数据。
  • 使用storm 负责搭建消息处理架构,并完成基于业务的消息落地,提供后续的数据 统计分析实时和离线任务,诸如pv、uv等数据,为运营做决策
  • 网站用户行为埋点和基于js的日志收集器开发,定义接又和前端部门配合。主用go 2、hadoop集群搭建和数据分析处理

2、基于CDH的集群搭建工作,后期进行维护

Read more ⟶

gitlab-ci


需求

一个前端项目需要分别发布到线上的测试环境和生产环境。在打包步骤,利用CI的环境变量功能,分别配置不同的API地址、项目地址等。完成打包后,将其上传到阿里oss中,完成发布。

使用Gitlab CI/CD的准备工作

需要准备三个东西:

  1. .gitlab-ci.yml 文件定义CI的工作流程、
  2. Runner执行流程、
  3. 阿里云OSS的SDK及上传程序用于部署。

.gitlab-ci.yml文件

项目根目录下需要有一个 .gitlab-ci.yml 文件。

声明流程

stages:
  - install
  - build

stages 声明流程,这里就是包括 installbuild 两个流程。这两个流程都做哪些工作呢?这里的声明相当于一个类型或者接口,需要在后面去实现它。

实现流程

job_name:
  stage: install
  script:
    - npm install

这个 job 就是实现 stages 中声明的 install 流程的,其中 job_name 随便写就是个名字,stage 字段是说这个 job 是实现的哪个接口。一个接口也可以被实现多次,但是要有条件区分开。

build_develop:
  stage: build
  variables:
    REACT_APP_BASE_UR: https://develop.edinnovaedu.com:35040/
    REACT_APP_LEARNBOT_URL: https://ng-api--dev.edinnovaedu.com/
  only:
    - develop
  script:
    - npm run build:dev
    - npm run deploy

build_production:
  stage: build
  variables:
    REACT_APP_BASE_URL: https://api-gray.v.edinnovaedu.com/
    REACT_APP_LEARNBOT_URL: https://api-gray.v.edinnovaedu.com
  only:
    - master
  script:
    - npm run build:gray
    - npm run deploy

上面的两个 job,都是 build 的实现,它们根据 only 字段定义的条件(分支名)做不同的工作。

Read more ⟶

CSS3


常用的css3属性

  • 圆角 border-radius
  • 阴影 box-shadow
  • 透明度 opacity
  • 渐变色 gradient
  • 旋转 rotate
  • transform
  • scale
  • transitions
  • animation
  • keyframe
Read more ⟶

跨域


什么是跨域问题

跨域问题源于浏览器的同源策略

在网页中使用AJAX时,AJAX请求的URL与网页的URL相比较,如果是属于同一个域名才可以正常访问,如果不属于一个域名就会出现跨域问题。所以,怎么判断两个URL是不是属于同一个域名呢?
比如一个网页的URL是"https://segmentfault.com/a/1190000011145364?aaa=666",可以把它拆分开:

只有当两个URL的origin完全相同时,才不会出现跨域问题。所以以下origin都会出现跨域问题:

  • 子域名:https://abc.segmentfault.com
  • 不同协议:http://segmentfault.com
  • 不同端口:https://segmentfault.com:8080
  • IP地址:https://192.168.4.12

当出现跨域问题时,AJAX请求返回后浏览器控制台会报错,AJAX的回调拿不到返回值。

CORS

cross-origin-resouces-shared

这个方案好像不需要在JS代码中的额外操作。另外,默认情况下,Cookie不包括在CORS请求之中,如果需要请求中带着cookie,设置withCredentials参数即可。

使用 fetch 进行请求时,可在参数中加 mode : 'no-cors',使浏览器不发送 option 请求,直接发起网络请求。但是请求的 header 只能是 CORS-safelisted 列表中的。其他 header 浏览器不会发送。比如 Authorization

no-cors 对 header 有影响,

需要在服务器为response设置header

  • origin允许的域名
  • methods允许的http方法
  • credentials允许携带cookies
  • headers循序的请求头

用 fetch 发起 cors 请求

fetch('https://english-workers.407590300.workers.dev/corsproxy/login', {
	credentials: 'include',
	// mode: 'cors',
	// method:"GET",
	headers: new Headers({
	  'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
	  'X-Custom-PSK': 'mypresharedkey1',
	})
})

JSPNP

前端发起请求,并带上回调函数

<script src="https://anotherweb.com/api/tourism-data.json?myCallback=tourismJSONP"></script>

服务器返回的

tourismJSONP({"city":"Barcelona"})

处理返回结果

function tourismJSONP(data){
  alert(data.city); // "Barcelona"
}

利用script标签不受跨域限制

Read more ⟶

==类型转换


==类型转换

  • 两个类型不同时会发生转换
  • 一个是布尔,则将布尔转为数字,false-0,true-1
  • 一个是字符串,另一个是数字,则将字符创转为数值
  • 一个是对象,另一个不是,则调用对象的valueOf方法转为基本类型
  • null与undefined相同
  • 一个是NaN,则返回false(不等比较返回true)(两个NaN不相等)
  • 两个都是对象,则比较他们是不是同一地址
Read more ⟶

JavaScript基础


原型

  1. 是什么 它是所有对象的一个非标准属性,但是所有浏览器都实现了它, 它用于获取原型链的属性和方法, 在es6中标准化为Object.getPrototypeOf(obj)方法

它和prototype的区别是,只有函数对象才有prototype属性,用于记录原型属性和方法。 他们的关系是 对象.ptoto === 对象构造器.protptype 它主要是用来实现js中的继承

词法作用域

也叫静态作用域,是在写代码的地方确定的作用域。

为什么Map的性能比对象好?

Read more ⟶

new操作符


当使用 new 关键字调用函数时,该函数将被用作构造函数。new 的执行过程是:

  1. 创建一个空的简单 JavaScript 对象。为方便起见,我们称之为 newInstance
  2. 如果构造函数的 prototype 属性是一个对象,则将 newInstance 的 [[Prototype]] 指向构造函数的 prototype 属性,否则 newInstance 将保持为一个普通对象,其 [[Prototype]] 为 Object.prototype
  3. 使用给定参数执行构造函数,并将 newInstance 绑定为 this 的上下文(换句话说,在构造函数中的所有 this 引用都指向 newInstance)。
  4. 如果构造函数返回非原始值,则该返回值成为整个 new 表达式的结果。否则,如果构造函数未返回任何值或返回了一个原始值,则返回 newInstance。(通常构造函数不返回值,但可以选择返回值,以覆盖正常的对象创建过程。)

根据 MDN 中对 new 操作符的描述,我们可以实现一个名为 myNew 的函数来模拟 new 操作符的行为。下面是一个简单的实现:

function myNew(constructor, ...args) {
  // 步骤 1:创建一个空的简单 JavaScript 对象
  const newInstance = {};

  // 步骤 2:将 newInstance 的 [[Prototype]] 指向构造函数的 prototype 属性
  if (constructor.prototype !== null && typeof constructor.prototype === 'object') {
    Object.setPrototypeOf(newInstance, constructor.prototype);
  }

  // 步骤 3:使用给定参数执行构造函数,并将 newInstance 绑定为 this 的上下文
  const result = constructor.apply(newInstance, args);

  // 步骤 4:如果构造函数返回非原始值,则返回该值;否则返回 newInstance
  return typeof result === 'object' && result !== null ? result : newInstance;
}

使用这个 myNew 函数可以模拟 new 操作符的行为。例如:

Read more ⟶

Webpack


Loader 和 Plugin

Webpack 中的 Loader 和 Plugin 是两个不同的概念,它们分别用于处理不同的任务:

  1. Loader

    • Loader 用于对模块的源代码进行转换和处理,通常用于加载和转换各种类型的文件,比如将 ES6/ES7 代码转换为 ES5、将 SCSS 文件转换为 CSS 等。
    • Loader 是一个函数或者一个模块,它接受源文件作为输入,并返回转换后的文件内容。
  2. Plugin

    • Plugin 用于扩展 Webpack 的功能,在构建过程中执行各种任务,比如打包优化、资源管理、环境变量注入等。
    • Plugin 是一个具有 apply 方法的 JavaScript 对象,它会在 Webpack 的不同生命周期中被调用,并可以访问到整个编译过程中的各种信息和资源。

下面分别展示如何实现一个自定义的 Loader 和 Plugin:

自定义 Loader 示例

假设我们要实现一个简单的 Loader,将源代码中的每个单词首字母大写。首先创建一个 capitalize-loader.js 文件:

module.exports = function(source) {
  // 将源代码中每个单词的首字母大写
  return source.replace(/\b\w/g, function(match) {
    return match.toUpperCase();
  });
};

然后在 Webpack 配置文件中配置该 Loader:

module: {
  rules: [
    {
      test: /\.txt$/, // 匹配 .txt 文件
      use: 'capitalize-loader' // 使用 capitalize-loader 处理 .txt 文件
    }
  ]
}

现在,当 Webpack 在处理 .txt 文件时,会自动使用我们定义的 capitalize-loader 来处理文件内容。

Read more ⟶