Coder Social home page Coder Social logo

dtq-blog's People

Stargazers

 avatar

Watchers

 avatar

dtq-blog's Issues

前端模块化

一. CommonJS简介
CommonJS同步模块:
CommonJS的模块规范定义比较简单,意义在于将类聚的方法和变量等限定在私有的作用域中,同时支持引入和导出将上下游模块无缝衔接,每个模块具有独立的空间,它们互不干扰。
(1)模块引用

var add = require('./add.js');
var config = require('config.js');
var http = require('http');

(2)模块定义

module.exports.add = function () {
  ...
}
module.exports = function () {
  return ...
}

可以在一个文件中引入模块并导出另一个模块

var add = require('./add.js');
module.exports.increment = function () {
  return add(val, 1);
}

CommonJS都是针对服务端的实现:
服务端加载一个模块,直接就从硬盘或者内存中读取了,消耗时间可以忽略不计.
浏览器需要从服务端下载这个文件,所以说如果用CommonJS的require方式加载模块,需要等代码模块下载完毕,并运行之后才能得到所需要的API。
二. AMD(Asynchronous Module Definition) & RequireJS

AMD——异步模块加载规范 与CommonJS的主要区别就是异步模块加载,就是模块加载过程中即使require的模块还没有获取到,也不会影响后面代码的执行。
RequireJS——AMD规范的实现。其实也可以说AMD是RequireJS在推广过程中对模块定义的规范化产出。
模块定义:
(1)独立模块的定义——不依赖其它模块的模块定义

//独立模块定义
define({
  method1: function() {}
  method2: function() {}
});  
//或者
define(function(){
  return {
    method1: function() {},
    method2: function() {},
  }
});

(2)非独立模块——依赖其他模块的模块定义

define(['math', 'graph'], function(math, graph){
  ...
});

模块引用:

require(['a', 'b'], function(a, b){
  a.method();
  b.method();
})

CommonJS 和AMD的对比:
CommonJS一般用于服务端,AMD一般用于浏览器客户端
CommonJS和AMD都是运行时加载(只能在运行时确定模块之间的依赖关系)
三. CMD(Common Module Definition) & SeaJS
CMD——通用模块规范,由国内的玉伯提出。
SeaJS——CMD的实现,其实也可以说CMD是SeaJS在推广过程中对模块定义的规范化产出。
与AMD规范的主要区别在于定义模块和依赖引入的部分。AMD需要在声明模块的时候指定所有的依赖,通过形参传递依赖到模块内容中:

define(['dep1', 'dep2'], function(dep1, dep2){
  return function(){};
})

与AMD模块规范相比,CMD模块更接近于Node对CommonJS规范的定义:
define(factory);
在依赖示例部分,CMD支持动态引入,require、exports和module通过形参传递给模块,在需要依赖模块时,随时调用require( )引入即可,示例如下:

define(function(require, exports, module){
  //依赖模块a
  var a = require('./a');
  //调用模块a的方法
  a.method();
})

也就是说与AMD相比,CMD推崇依赖就近, AMD推崇依赖前置。
四. es6模块
如前面所述,CommonJS和AMD都是运行时加载。ES6在语言规格层面上实现了模块功能,是编译时加载,完全可以取代现有的CommonJS和AMD规范,可以成为浏览器和服务器通用的模块解决方案。
ES6模块使用——export default
其实export default,在项目里用的非常多,一般一个Vue组件或者React组件我们都是使用export default命令,需要注意的是使用export default命令时,import是不需要加{}的。而不使用export default时,import是必须加{},示例如下:

//person.js
export function getName() {
 ...
}
//my_module
import {getName} from './person.js';

-----------------对比---------------------

//person.js
export default function getName(){
 ...
}
//my_module
import getName from './person.js';
export default其实是导出一个叫做default的变量,所以其后面不能跟变量声明语句。

//错误
export default var a = 1;
值得注意的是我们可以同时使用export 和export default

//person.js
export name = 'dingman';
export default function getName(){
  ...
}

//my_module
import getName, { name } from './person.js';

ES6模块与commonjs模块的差异:
1.commonjs输出的是一个值的拷贝,es6输出的值的引用
2.commonjs是运行时加载,es6是编译时输出接口
3.因为 CommonJS 加载的是一个对象(即 module.exports 属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

五,总结
CommonJS 规范主要用于服务端编程,加载模块是同步的,这并不适合在浏览器环境,因为同步意味着阻塞加载,浏览器资源是异步加载的,因此有了 AMD CMD 解决方案。
AMD 规范在浏览器环境中异步加载模块,而且可以并行加载多个模块。不过,AMD 规范开发成本高,代码的阅读和书写比较困难,模块定义方式的语义不顺畅。
CMD 规范与 AMD 规范很相似,都用于浏览器编程,依赖就近,延迟执行,可以很容易在 Node.js 中运行。不过,依赖 SPM 打包,模块的加载逻辑偏重ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。

参考链接:https://zhuanlan.zhihu.com/p/41568986

js 类数组对象

devtools 判断类数组的方法:

/**
 * @param {?Object} obj
 * @return {boolean}
 */
function isArrayLike(obj) {
  if (!obj || typeof obj !== 'object')
    return false;
  try {
    if (typeof obj.splice === 'function') {
      const len = obj.length;
      return typeof len === 'number' && (len >>> 0 === len && (len > 0 || 1 / len > 0));
    }
  } catch (e) {
  }
  return false;
}

判断的过程:

存在且是对象
对象上的splice 属性是函数类型
对象上有 length 属性且为正整数

观察者模式和订阅发布模式的区别

观察者模式

比较概念的解释是,目标和观察者是基类,目标提供维护观察者的一系列方法,观察者提供更新接口。具体观察者和具体目标继承各自的基类,然后具体观察者把自己注册到具体目标里,在具体目标发生变化时候,调度观察者的更新方法。

比如有个“天气中心”的具体目标A,专门监听天气变化,而有个显示天气的界面的观察者B,B就把自己注册到A里,当A触发天气变化,就调度B的更新方法,并带上自己的上下文。

image

发布/订阅模式

比较概念的解释是,订阅者把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者发布该事件到调度中心(顺带上下文),由调度中心统一调度订阅者注册到调度中心的处理代码。

比如有个界面是实时显示天气,它就订阅天气事件(注册到调度中心,包括处理程序),当天气变化时(定时获取数据),就作为发布者发布天气信息到调度中心,调度中心就调度订阅者的天气处理程序。

image

总结

  1. 从两张图片可以看到,最大的区别是调度的地方。

虽然两种模式都存在订阅者和发布者(具体观察者可认为是订阅者、具体目标可认为是发布者),但是观察者模式是由具体目标调度的,而发布/订阅模式是统一由调度中心调的,所以观察者模式的订阅者与发布者之间是存在依赖的,而发布/订阅模式则不会。

  1. 两种模式都可以用于松散耦合,改进代码管理和潜在的复用。
    image

工作中踩坑列表

  1. https协议的url下,加载http的图片资源,在chrome中会有warn,移动端会被阻止block掉
    解决办法: logoUrl.replace(/http/g, 'https') 强制转化一下
  2. vue中,如果prop的值改变,只有prop的值会影响dom显示的时候才会触发updated()函数。
    这也是和react componentDidUpdate()的区别,后者是只要setState()就会触发。
    (还要继续看源码啊,不看源码的话,这些细节只能试来拭去的也不知道为啥,很low)

Vue源码学习(一)

一. 万事开头难, 先new 一个 Vue!

image

那么Vue的是怎么定义的?

image
image
(所以呢,beforeCreact 和 creacted 其实是指创建data props computed之类的属性之前 || 后)
图片接上
image

image

二 这些init都是在初始化什么?

  1.  initLifecycle(vm) /*初始化生命周期*/
    
  2. initEvents(vm) /*初始化事件*/
    
  3. initRender(vm)/初始化render/

  4. initInjections(vm) // resolve injections before data/props

  5. initState(vm)/初始化props、methods、data、computed与watch/

  6. initProvide(vm) // resolve provide after data/props

initstate里面的加载顺序:

image

event loop

event loop 的队列存放的是事件,而不是事件的回调函数,比如两个ajax请求事件,事件本身是有顺序的队列,有顺序的发送请求,但是谁先请求出结果,先执行谁的callback,由于单线程原因,另一个callback要等待这个callback执行完,写的简单了点,回头详细写

如何解决JavaScript中0.1+0.2不等于0.3

console.log(0.1+0.2===0.3);    //true or false?

在正常的数学逻辑思维中,0.1+0.2=0.3这个逻辑是正确的,但是在JavaScript中0.1+0.2!==0.3,这是为什么呢?这个问题也会偶尔被用来当做面试题来考查面试者对JavaScript的数值的理解程度

在JavaScript中的二进制的浮点数0.1和0.2并不是十分精确,在他们相加的结果并非正好等于0.3,而是一个比较接近的数字 0.30000000000000004 ,所以条件判断结果为 false

那么应该怎样来解决0.1+0.2等于0.3呢? 最好的方法是设置一个误差范围值,通常称为”机器精度“,而对于 JavaScript 来说,这个值通常是2^-52,而在 ES6 中,已经为我们提供了这样一个属性:Number.EPSILON,而这个值正等于2^-52。这个值非常非常小,在底层计算机已经帮我们运算好,并且无限接近0,但不等于0,。这个时候我们只要判断(0.1+0.2)-0.3小于Number.EPSILON,在这个误差的范围内就可以判定0.1+0.2===0.3为true

function numbersEqual(a,b){     return Math.abs(a-b)<Number.EPSILON; } var a=0.1+0.2, b=0.3; console.log(numbersEqual(a,b));    //true
( 注: Number.EPSILON
ES6 在 Number 对象上面,新增一个极小的常量 Number.EPSILON。根据规格,它表示1与大于1的最小浮点数之间的差。
对于64位浮点数来说,大于1的最小浮点数相当于二进制的1.00..001,小数点后面有连续51个零,这个值减去1之后,就等于2的-52次方。
Number.EPSILON 实际上是 JavaScript 能够表示的最小精度,误差如果小于这个值,就可以认为已经没有意义了,即不存在误差了。)

计算时解决思路:把小数转成整数计算后在转回来。

CSS伪元素及其用法

CSS中有两个很常见的概念,伪类和伪元素。

伪类用于在页面中的元素处于某个状态时,为其添加指定的样式。

:active | 向被激活的元素添加样式。 
:focus | 向拥有键盘输入焦点的元素添加样式。 
:hover | 当鼠标悬浮在元素上方时,向元素添加样式。 
:link | 向未被访问的链接添加样式。
:visited | 向已被访问的链接添加样式。 
:first-child | 向元素的第一个子元素添加样式。 
:lang | 向带有指定 lang 属性的元素添加样式。

伪元素会创建一个抽象的伪元素,这个元素不是DOM中的真实元素,但是会存在于最终的渲染树中,我们可以为其添加样式。

最常规的区分伪类和伪元素的方法是:实现伪类的效果可以通过添加类来实现,但是想要实现伪元素的等价效果只能创建实际的DOM节点。

此外 ,伪类是使用单冒号:,伪元素使用是双冒号::
伪元素可以分为排版伪元素、突出显示伪元素、树中伪元素三类。下面我们一起看看具体都有哪些吧。
第一类:排版伪元素

  1. ::first-line伪元素

它表示所属源元素的第一格式化行。可以定义这一行的样式。

效果如图所示。

p::first-line {
 text-transform: uppercase; //定义仅有大写字母。
 }

<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Necessitatibus quisquam ipsum sunt doloribus accusamus quae atque quaerat quam deleniti beatae, ipsam nobis dignissimos fugiat reiciendis error deserunt. Odio, eligendi placeat.</p>
image
1.1 如何确定第一格式化行
需要注意的是,::first-line伪元素只有应用在块级容器上才有效,且必须出现在相同流中的块级子孙元素中(即没有定位和浮动)。
1.2 可以用于::first-line伪元素的样式
还有一些其他限制。主要有以下样式可以应用于其上:

所有的字体属性
color和opacity属性
所有的背景属性
可以应用于行级元素的排版属性
文字装饰属性
可以用于行级元素的行布局属性
其他一些规范中特别指定可以应用的属性
此外,浏览器厂商有可能额外应用其他属性。
2. ::first-letter伪元素

::first-letter伪元素代表所属源元素的第一格式化行的第一个排版字符单元,且其前面不能有任何其他内容。

::first-letter常用于下沉首字母效果。

如下,我们可以创建一个下沉两行的段落。第一种方法,是CSS-INLINE-3中引入的,浏览器尚不支持。我们可以通过第二种方法实现同样的效果。

<p>“Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dignissimos hic vero reprehenderit sunt temporibus?

 Doloribus consequatur quo illo porro quae recusandae autem eos. Corrupti itaque alias nam eius animi illum.</p>
<!-- initial-letter(尚不支持) -->

 p::first-letter { initial-letter: 2; }

 <!-- 普通实现 -->

 h3 + p::first-letter {

 float: left;

 display: inline-block;

 font-size: 32px;

 padding: 10px 15px;

 }

image

2.1 如何确定首字母
首字母必须出现在第一格式化行内。

2.2 可以用于::first-letter伪元素的样式
::first-line生成的伪元素的行为类似于一个行级元素,还有一些其他限制。主要有以下样式可以应用于其上:

所有的字体属性
color和opacity属性
所有的背景属性
可以应用于行级元素的排版属性
文字装饰属性
可以用于行级元素的行布局属性
margin和padding属性
border和box-shadow
其他一些规范中特别指定可以应用的属性

同样,浏览器厂商有可能额外应用其他属性。

第二类:突出显示伪元素
突出显示伪元素表示文档中特定状态的部分,通常采用不同的样式展示该状态。如页面内容的选中。

突出显示伪元素不需要在元素树中有体现,并且可以任意跨越元素边界而不考虑其嵌套结构。

突出显示伪元素主要有以下几类:

1 ::selection与::inactive-selection
这两个伪元素表示用户在文档中选取的内容。::selection表示有效的选择,相反,::inactive-selection表示无效的选择(如当窗口无效,无法相应选中事件时)
如下图所示,我们可以定义页面中选中内容的样式,输入框中的内容也可以。
image

2 ::spelling-error
::spelling-error表示浏览器识别为拼写错误的文本部分。暂无实现。

3 ::grammar-error
::grammar-error表示浏览器识别为语法错误的文本部分。暂无实现。

::spelling-error和::grammar-error暂时均无实现。一方面,不同的语言的语法与拼写较为复杂。另一方面,::spelling-error和::grammar-error还可能会导致用户隐私的泄露,如用户名和地址等。所以浏览器实现必须避免读取这类突出显示内容的样式。

可以应用到突出显示类伪元素的样式

对于突出显示类伪元素,我们只可以应用不影响布局的属性。如下:

color
background-color
cursor
caret-color
caret-color
text-decoration及其相关属性
text-shadow
stroke-color/fill-color/stroke-width

第三类:树中伪元素
这类伪元素会一直存在于元素树中,它们汇集成源元素的任何属性。

  1. 内容生成伪元素:::before/::after

当::before/::after伪元素的content属性不为'none'时,这两类伪元素就会生成一个元素,作为源元素的子元素,可以和DOM树中的元素一样定义样式。

::before是在源元素的实际内容前添加伪元素。::after是在源元素的实际内容后添加伪元素。

正如上文提到的,与常规的元素一样,::before和::after两个伪元素可以包含::first-line和::first-letter。

  1. 列表项标记伪元素:::marker

::markder可以用于定义列表项标记的样式。

如下,我们可以分开定义列表项及其内容的颜色。

<ul>

 <li>Item 1</li>

 <li>Item 2</li>

 <li>Item 3</li>

 </ul>
li{

 color: red;

 }

 li::marker {

 color:green;

 }

image

  1. 输入框占位伪元素:::placeholder

::placeholder表示输入框内占位提示文字。可以定义其样式。

如:

::placeholder {

color: blue;

}
image

所有可以应用到::first-line伪元素的样式都可以用于::placeholder上。可以参考上面的内容。

注意还有一个:placeholder-shown伪类,它主要用于定义显示了占位文字的元元素本身的样式,而不是占位文字的样式。

总结

本文列举了CSS Pseudo-Elements Module Level 4中的所有伪元素类型。

首先,详细介绍了排版类伪元素,这一类大家的使用场景较多,支持度也较好。

其次,介绍了突出显示类伪元素,主要可以用于选中样式的修改,其他的尚未得到支持。

最后,介绍了树中伪元素,包括::before/::after/::marker/::placeholder

webpack4学习(二)代码分离

代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。
有三种常用的代码分离方法:
入口起点:使用 entry 配置手动地分离代码。
防止重复:使用 CommonsChunkPlugin 去重和分离 chunk。
动态导入:通过模块的内联函数调用来分离代码。
一. 这是迄今为止最简单、最直观的分离代码的方式。
二. plugins: [
new HTMLWebpackPlugin({
title: 'Code Splitting'
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'common' // 指定公共 bundle 的名称。
})
],
三. 当涉及到动态代码拆分时,webpack 提供了两个类似的技术。对于动态导入,第一种,也是优先选择的方式是,使用符合 ECMAScript 提案 的 import() 语法。第二种,则是使用 webpack 特定的 require.ensure。
懒加载或者按需加载:
vue,react之类的库是支持这种异步组件的,拿vue来说,

  1. 全局注册:
    Vue.component("AsyncCmp", () => import("./AsyncCmp"));

  2. 局部注册:
    new Vue({
    // ...
    components: {
    AsyncCmp: () => import("./AsyncCmp")
    }
    });

  3. vue-router内置也支持延迟加载:
    // Instead of: import Login from './login'
    const Login = () => import("./login");

    new VueRouter({
    routes: [{ path: "/login", component: Login }]
    });

  4. Vuex有一种registerModule方法可以让我们动态创建Vuex模块:
    const store = new Vuex.Store();
    ...
    // Assume there is a "login" module we wanna load
    import('./store/login').then(loginModule => {
    store.registerModule('login', loginModule)
    })

参考文档: https://alexjover.com/blog/lazy-load-in-vue-using-webpack-s-code-splitting/

自己实现 new 操作符

首先我们再来回顾下 new 操作符的几个作用

1.new 操作符会返回一个对象,所以我们需要在内部创建一个对象
2.这个对象,也就是构造函数中的 this,可以访问到挂载在 this 上的任意属性
3.这个对象可以访问到构造函数原型上的属性,所以需要将对象与构造函数链接起来
4.返回原始值需要忽略,返回对象需要正常处理

function create(Con, ...args) {
let obj = {}
Object.setPrototypeOf(obj, Con.prototype)
let result = Con.apply(obj, args)
return result instanceof Object ? result : obj
}

这就是一个完整的实现代码,我们通过以下几个步骤实现了它:

1.首先函数接受不定量的参数,第一个参数为构造函数,接下来的参数被构造函数使用
2.然后内部创建一个空对象 obj
3.因为 obj 对象需要访问到构造函数原型链上的属性,所以我们通过 setPrototypeOf 将两者联系起来。这段代码等同于 obj.proto = Con.prototype
4.将 obj 绑定到构造函数上,并且传入剩余的参数
5.判断构造函数返回值是否为对象,如果为对象就使用构造函数返回的值,否则使用 obj,这样就实现了忽略构造函数返回的原始值

参考链接:https://juejin.im/post/5c7b963ae51d453eb173896e

HTML5 history新特性pushState、replaceState

HTML5引入了histtory.pushState()和history.replaceState()这两个方法,他们允许添加和修改history实体。同时,这些方法会和window.onpostate事件一起工作。

使用history.pushState()方法来修改referrer,这种方法可以被用在经过修改状态后而为xmlhttpRequest对象创建的http header中。这个referrer会是创建XMLHttpRequest 时document的URL。

pushState 用于向 history 添加当前页面的记录,而 replaceState 和 pushState 的用法完全一样,唯一的区别就是它用于修改当前页面在 history 中的记录。
pushState方法
pushState()有三个参数:state对象,标题(现在是被忽略,未作处理),URL(可选)。具体细节:

·        state对象 –state对象是一个JavaScript对象,它关系到由pushState()方法创建出来的新的history实体。用以存储关于你所要插入到历史 记录的条目的相关信息。State对象可以是任何Json字符串。因为firefox会使用用户的硬盘来存取state对象,这个对象的最大存储空间为640k。如果大于这个数 值,则pushState()方法会抛出一个异常。如果确实需要更多的空间来存储,请使用本地存储。

·        title—firefox现在回忽略这个参数,虽然它可能将来会被使用上。而现在最安全的使用方式是传一个空字符串,以防止将来的修改。或者可以传一个简短的标题来表示state

·        URL—这个参数用来传递新的history实体的URL,注意浏览器将不会在调用pushState()方法后加载这个URL。但也许会过一会尝试加载这个URL。比如在用户重启了浏览器后,新的url可以不是绝对路径。如果是相对路径,那么它会相对于现有的url。新的url必须和现有的url同域,否则pushState()将抛出异常。这个参数是选填的,如果为空,则会被置为document当前的url。

某种意义上来说,调用pushState()方法很像设置了window.location = “#foo”,这两者都会创建和激活另一个关联到当前document的history实体,但pushState()另外有一些优点:

l 新的url可以是任何和当前url同域的url,相比之下,如果只设置hash,window.location会保持在同一个document。

l 如果不需要,你可以不修改url。对比而言,设置window.location = “#foo”;仅产生新的history实体,如果你当前的hash不是#foo

l 你可以将任意的数据与你的新history实体关联。使用基于hash的方法,需要将所有相关的数据编码为一个短字符串。

注意,pushState()方法不会使hashchange时间发生,即使是新旧url只是hash不同。

replaceState()方法
history.replaceState() 用起来很像pushState(),除了replaceState()是用来修改当前的history实体而不是创建一个新的。这个方法有时会很有用,当 你需要对某些用户行为作反应而更新一个state对象或者当前history实体时,可以使用它来更新state对象或者当前history实体的url。

popstate事件
当history实体被改变时,popstate事件将会发生。如果history实体是有pushState和replaceState方法产生的,popstate事件的state属性会包含一份来自history实体的state对象的拷贝

详见window.onpopstate

读取当前的state
读取现有state

当页面加载时,它可能会有一个非空的state对象。这可能发生在当页面设置一个state对象(使用pushState或者replaceState)之后用户重启了浏览器。当页面重新加载,页面将收到onload事件,但不会有popstate事件。然而,如果你读取history.state属性,将在popstate事件发生后得到这个state对象

参考:https://blog.csdn.net/tianyitianyi1/article/details/7426606

h5标签<video/>在iphone上无法播放的问题

<video controls="controls" autoplay="autoplay" id="video"> <source id="source" src="" type="video/mp4"> </video>
问题:这样一段和w3c实例一样的代码会有什么问题呢,为啥在iphone上播放不了呢
原因:视频格式MP4是正确的,但是你的后台没有对ios的视频播放器做适配。
如果想要在iOS上播放视频,那么必须在http协议中应用rang请求头。

对于有的朋友还对ios播放器http的range标记不是很懂。我再讲解下。

视频文件总长度是123456789
range是播放器要求的区间也就是客户端发送请求的时候http会带有这个标记,这个区间的值在http.headers.range中获取,一般是bytes=0-1这样的。

我们需要做的处理是返回文件的指定区间(如上面的例子,我们就应该返回0到1的字符),并添加Content-Range:btyes 0-1、Accept-Ranges:bytes、'Content-Length: 123456789','Content-Type: video/mp4'到http.headers中

上一段node代码就明白了:

`var http = require('http'),
url = require('url'),
path = require('path'),
fs = require('fs')
http.createServer(function (req, res) {
var pathname = __dirname+url.parse(req.url).pathname;
if (path.extname(pathname)=='') {

pathname+='/';
}
if (pathname.charAt(pathname.length-1)=='/'){

pathname+='index.html';
}

fs.exists(pathname,function(exists){
if(exists){

switch(path.extname(pathname)){
case '.html':
res.writeHead(200, {'Content-Type': 'text/html'});
break;
case '.js':
res.writeHead(200, {'Content-Type': 'text/javascript'});
break;
case '.css':
res.writeHead(200, {'Content-Type': 'text/css'});
break;
case '.gif':
res.writeHead(200, {'Content-Type': 'image/gif'});
break;
case '.jpg':
res.writeHead(200, {'Content-Type': 'image/jpeg'});
break;
case '.png':
res.writeHead(200, {'Content-Type': 'image/png'});
break;
case '.mp4':
res.writeHead(200, {
'Content-Type' : 'video/mp4',
'Accept-Ranges' : 'bytes',
//'Range' : 'bytes=0-1',
'Content-Range' : 'bytes 0-1',
'Content-Range' : 'bytes 1-11000/243104',
'Content-Length' : 11000,
'Content-Range' : 'bytes 11001-243103/243104',
'Content-Length' : 133103
});
default:
res.writeHead(200, {'Content-Type': 'application/octet-stream'});
}
fs.readFile(pathname,function (err,data){
res.end(data);
});
} else {

res.writeHead(404, {'Content-Type': 'text/html'});
res.end('404 Not Found');
}
});

}).listen(8181);`

webpack4学习(一)模块热替换

1.使用观察模式
你可以指示 webpack --watch 依赖图中的所有文件以进行更改。如果其中一个文件被更新,代码将被重新编译,所以你不必手动运行整个构建。唯一的缺点是,为了看到修改后的实际效果,你需要刷新浏览器。如果能够自动刷新浏览器就更好了,可以尝试使用 webpack-dev-server,恰好可以实现我们想要的功能。
2.搭建自动刷新的服务
第一种方式:webpack-dev-server 为你提供了一个简单的 web 服务器,并且能够实时重新加载(live reloading)。使用 webpack-dev-server --open 就会看到浏览器自动加载页面。如果现在修改和保存任意源文件,web 服务器就会自动重新加载编译后的代码。
第二种方式: webpack-dev-middleware + express 也可以实现 webpack-dev-server的效果(即webpack-dev-middleware+express = webpack-dev-server;vue-cli就是使用此方式)。
webpack-dev-middleware 是一个容器(wrapper),它可以把 webpack 处理后的文件传递给一个服务器(server)。 webpack-dev-server 在内部使用了它,同时,它也可以作为一个单独的包来使用,以便进行更多自定义设置来实现更多的需求。
3.模块热替换(Hot Module Replacement 或 HMR)是 webpack 提供的最有用的功能之一。它允许在运行时更新各种模块,而无需进行完全刷新。即:不用完全的重新编译,可以只编译修改的部分并替换(HMR 不适用于生产环境,这意味着它应当只在开发环境使用)
启用此功能实际上相当简单。而我们要做的,就是更新 webpack-dev-server 的配置:
devServer: {
contentBase: './dist',

  • hot: true
    

    },
    如果你在 使用 webpack-dev-middleware,可以通过 webpack-hot-middleware package 包,在自定义开发服务下启用 HMR,分三步:

    1. plugins: [
      new webpack.HotModuleReplacementPlugin()
      ]
    2. var webpack = require('webpack');
      var webpackConfig = require('./webpack.config');
      var compiler = webpack(webpackConfig);
     app.use(require("webpack-dev-middleware")(compiler, {
     noInfo: true, publicPath: webpackConfig.output.publicPath
    

    }));
    3. app.use(require("webpack-hot-middleware")(compiler));

参考文档:https://www.webpackjs.com/guides/hot-module-replacement/
https://github.com/webpack-contrib/webpack-hot-middleware

module.exports和exports和export和export default的区别,import和require的区别

一. module.exports和exports
在学习Node.js时,经常能看到两种导出模块的方式:module.exports和exports。module和exports是Node.js给每个js文件内置的两个对象:
console.log(exports); consoel.log(module)
Module { id: '.', exports: {}, parent: null, filename: '/Users/dongtianqi/workspace/my-ssr-demo/index.js', loaded: false, children: [], paths: [ '/Users/dongtianqi/workspace/my-ssr-demo/node_modules', '/Users/dongtianqi/workspace/node_modules', '/Users/dongtianqi/node_modules', '/Users/node_modules', '/node_modules' ] } {}
区别:
从打印我们可以看出,module.exports和exports一开始都是一个空对象{},实际上,这两个对象指向同一块内存。这也就是说module.exports和exports是等价的(有个前提:不去改变它们指向的内存地址)。
require引入的对象本质上是module.exports。
解释:
为了方便,Node为每个模块提供一个exports变量,指向module.exports。这等同在每个模块头部,有一行这样的命令。

var exports = module.exports;
于是我们可以直接在 exports 对象上添加方法,表示对外输出的接口,如同在module.exports上添加一样。注意,不能直接将exports变量指向一个值,因为这样等于切断了exports与module.exports的联系。

二. export和export default
在es5中,用module.exports和exports导出模块,用require引入模块。
es6新增export和export default导出模块,import导入模块。
使用:

  1. export default导出:
    const Programmer = {name: 'UncleFirefly',age:25} export default Programmer
    export default导出对应的导入:
    import Programmer from './a.js'
  2. export导出:
    const uncle = {name: 'UncleFirefly',age:25} const aunt = {name: 'AuntFirefly',age:25} export {uncle, aunt}
    export 导出对应的导入:
    import {uncle, aunt} from './a.js'
    区别:
    //a.js const Programmer = {name: 'UncleFirefly',age:25} export default Programmer console.log(module) /* //打印结果 {exports: {default:{age:25,name:'UncleFirefly'}, hot:{...}} */
    //a.js const uncle = {name: 'UncleFirefly',age:25} const aunt = {name: 'AuntFirefly',age:25} export {uncle, aunt} /* //打印结果 {exports: {aunt:{age:25,name:'AuntFirefly'},uncle:{age:25,name:'UncleFirefly'}, hot:{...}} */

从打印可以看出:
导出时
export相当于把对象添加到module的exports中。
export default相当于把对象添加到module的exports中,并且对象的key叫default。
导入时:
不带{}的导入
本质上就是导入exports中的default属性(注:如果default属性不存在,则导入exports对象)。
带{}的导入
本质上按照属性key值导入exports中对应的属性值。

三、小tips
一般来说,module.exports和exports与require对应。也就是用module.exports和exports导出的模块,则用require导入。(不是绝对,如果代码支持es6,也可以用import引入)。

参考链接:https://www.jianshu.com/p/f6c5a646c00b

前端基础面试题

知识点统计:
html , css :
js :
es6:
vue:
react :
router:
vuex, redux:
前端攻击:
webpack:
babel:
浏览器原理:
计算机网络:http, http2.0 , https, tcp...
linux命令:
数据结构:
nodejs:
(mysql + go + nginx + redis)

https:
http://www.ruanyifeng.com/blog/2014/02/ssl_tls.html

js :
https://juejin.im/post/5d0644976fb9a07ed064b0ca
let errorList = [11,12, 16, 17, 24,27,28,29,33,35,37]
https://juejin.im/post/5d51e16d6fb9a06ae17d6bbc
mqyqingfeng/Blog#12

vue:
https://juejin.im/post/5e649e3e5188252c06113021
https://segmentfault.com/a/1190000020637178

  1. 总结
    Set
    成员唯一、无序且不重复
    [value, value],键值与键名是一致的(或者说只有键值,没有键名)
    可以遍历,方法有:add、delete、has
    WeakSet
    成员都是对象
    成员都是弱引用,可以被垃圾回收机制回收,可以用来保存DOM节点,不容易造成内存泄漏
    不能遍历,方法有add、delete、has
    Map
    本质上是键值对的集合,类似集合
    可以遍历,方法很多可以跟各种数据格式转换
    WeakMap
    只接受对象作为键名(null除外),不接受其他类型的值作为键名
    键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的
    不能遍历,方法有get、set、has、delete

浏览器缓存

一. 强缓存:
不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cache或from memory cache。强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control。

Expires和Cache-Control两者对比
其实这两者差别不大,区别就在于 Expires 是http1.0的产物,Cache-Control是http1.1的产物,两者同时存在的话,Cache-Control优先级高于Expires;在某些不支持HTTP1.1的环境下,Expires就会发挥用处。所以Expires其实是过时的产物,现阶段它的存在只是一种兼容性的写法。
强缓存判断是否缓存的依据来自于是否超出某个时间或者某个时间段,而不关心服务器端文件是否已经更新,这可能会导致加载文件不是服务器端最新的内容,那我们如何获知服务器端内容是否已经发生了更新呢?此时我们需要用到协商缓存策略。

二. 协商缓存:
就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,协商缓存可以通过设置两种 HTTP Header 实现:Last-Modified 和 ETag 。
1.Last-Modified和If-Modified-Since
浏览器在第一次访问资源时,服务器返回资源的同时,在response header中添加 Last-Modified的header,值是这个资源在服务器上的最后修改时间,浏览器接收后缓存文件和header;
2.ETag和If-None-Match
Etag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,Etag就会重新生成。浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的Etag值放到request header里的If-None-Match里,服务器只需要比较客户端传来的If-None-Match跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。如果服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源(当然也包括了新的ETag)发给客户端;如果ETag是一致的,则直接返回304知会客户端直接使用本地缓存即可。
3.两者之间对比:
首先在精确度上,Etag要优于Last-Modified。
Last-Modified的时间单位是秒,如果某个文件在1秒内改变了多次,那么他们的Last-Modified其实并没有体现出来修改,但是Etag每次都会改变确保了精度;如果是负载均衡的服务器,各个服务器生成的Last-Modified也有可能不一致。
第二在性能上,Etag要逊于Last-Modified,毕竟Last-Modified只需要记录时间,而Etag需要服务器通过算法来计算出一个hash值。
第三在优先级上,服务器校验优先考虑Etag

三、缓存机制
强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回200,重新返回资源和缓存标识,再存入浏览器缓存中;生效则返回304,继续使用缓存

四、用户行为对浏览器缓存的影响
所谓用户行为对浏览器缓存的影响,指的就是用户在浏览器如何操作时,会触发怎样的缓存策略。
主要有 3 种:

打开网页,地址栏输入地址: 查找 disk cache 中是否有匹配。如有则使用;如没有则发送网络请求。
普通刷新 (F5):因为 TAB 并没有关闭,因此 memory cache 是可用的,会被优先使用(如果匹配的话)。其次才是 disk cache。
强制刷新 (Ctrl + F5):浏览器不使用缓存,因此发送的请求头部均带有 Cache-control: no-cache(为了兼容,还带了 Pragma: no-cache),服务器直接返回 200 和最新内容。

参考链接:ljianshu/Blog#23

深入浅出浏览器渲染原理

浏览器渲染页面的顺序

  1. 构建DOM
  2. 构建CSSOM
  3. 构建渲染树 ( 渲染树只会包括需要显示的节点和这些节点的样式信息,如果某个节点是 display:none 的,那么就不会在渲染树中显示 )

问题一:渲染过程中遇到JS文件怎么处理?

JavaScript的加载、解析与执行会阻塞DOM的构建,也就是说,在构建DOM时,HTML解析器若遇到了JavaScript,那么它会暂停构建DOM,将控制权移交给JavaScript引擎,等JavaScript引擎运行完毕,浏览器再从中断的地方恢复DOM构建。
也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都建议将 script 标签放在 body 标签底部的原因。当然在当下,并不是说 script 标签必须放在底部,因为你可以给 script 标签添加 defer 或者 async 属性。
JS文件不只是阻塞DOM的构建,它会导致CSSOM也阻塞DOM的构建。
原本DOM和CSSOM的构建是互不影响,井水不犯河水,但是一旦引入了JavaScript,CSSOM也开始阻塞DOM的构建,只有CSSOM构建完毕后,DOM再恢复DOM构建。
这是什么情况?
这是因为JavaScript不只是可以改DOM,它还可以更改样式,也就是它可以更改CSSOM。前面我们介绍,不完整的CSSOM是无法使用的,但JavaScript中想访问CSSOM并更改它,那么在执行JavaScript时,必须要能拿到完整的CSSOM。所以就导致了一个现象,如果浏览器尚未完成CSSOM的下载和构建,而我们却想在此时运行脚本,那么浏览器将延迟脚本执行和DOM构建,直至其完成CSSOM的下载和构建。也就是说,在这种情况下,浏览器会先下载和构建CSSOM,然后再执行JavaScript,最后在继续构建DOM。

问题二:async和defer的作用是什么?有什么区别?

async 属性表示异步执行引入的 JavaScript,与 defer 的区别在于,如果已经加载好,就会开始执行——无论此刻是 HTML 解析阶段还是 DOMContentLoaded 触发之后。需要注意的是,这种方式加载的 JavaScript 依然会阻塞 load 事件。换句话说,async-script 可能在 DOMContentLoaded 触发之前或之后执行,但一定在 load 触发之前执行。
defer 属性表示延迟执行引入的 JavaScript,即这段 JavaScript 加载时 HTML 并未停止解析,这两个过程是并行的。整个 document 解析完毕且 defer-script 也加载完成之后(这两件事情的顺序无关),会执行所有由 defer-script 加载的 JavaScript 代码,然后触发 DOMContentLoaded 事件。
defer 与相比普通 script,有两点区别:载入 JavaScript 文件时不阻塞 HTML 的解析,执行阶段被放到 HTML 标签解析完成之后。 在加载多个JS脚本的时候,async是无顺序的加载,而defer是有顺序的加载。

一些总结:

  1. css不阻止dom的解析
  2. js阻止dom的解析
  3. css js都会阻止dom的渲染

深入理解CSS外边距折叠(Margin Collapse)

浮动元素不会与任何元素发生叠加,也包括它的子元素
创建了 BFC 的元素不会和它的子元素发生外边距叠加
绝对定位元素和其他任何元素之间不发生外边距叠加,也包括它的子元素
inline-block 元素和其他任何元素之间不发生外边距叠加,也包括它的子元素
普通流中的块级元素的 margin-bottom 永远和它相邻的下一个块级元素的 margin-top 叠加,除非相邻的兄弟元素 clear
普通流中的块级元素(没有 border-top、没有 padding-top)的 margin-top 和它的第一个普通流中的子元素(没有clear)发生 margin-top 叠加
普通流中的块级元素(height为 auto、min-height为0、没有 border-bottom、没有 padding-bottom)和它的最后一个普通流中的子元素(没有自身发生margin叠加或clear)发生 margin-bottom叠加
如果一个元素的 min-height 为0、没有 border、没有padding、高度为0或者auto、不包含子元素,那么它自身的外边距会发生叠加

参考资料:https://segmentfault.com/a/1190000010346113

vue学习 -- data

类型:Object | Function

限制:组件的定义只接受 function。

详细:
Vue 实例的数据对象。Vue 将会递归将 data 的属性转换为 getter/setter,从而让 data 的属性能够响应数据变化。对象必须是纯粹的对象 (含有零个或多个的 key/value 对):浏览器 API 创建的原生对象,原型上的属性会被忽略。大概来说,data 应该只能是数据 - 不推荐观察拥有状态行为的对象。

一旦观察过,不需要再次在数据对象上添加响应式属性。因此推荐在创建实例之前,就声明所有的根级响应式属性。

实例创建之后,可以通过 vm.$data 访问原始数据对象。Vue 实例也代理了 data 对象上所有的属性,因此访问 vm.a 等价于访问 vm.$data.a。

以 _ 或 $ 开头的属性 不会 被 Vue 实例代理,因为它们可能和 Vue 内置的属性、API 方法冲突。你可以使用例如 vm.$data._property 的方式访问这些属性。

当一个组件被定义,data 必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。如果 data 仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象!通过提供 data 函数,每次创建一个新实例后,我们能够调用 data 函数,从而返回初始数据的一个全新副本数据对象。

如果需要,可以通过将 vm.$data 传入 JSON.parse(JSON.stringify(...)) 得到深拷贝的原始数据对象。
image

cookie里面有什么

image

secure的值改为了true,true意味着"指示浏览器仅通过 HTTPS 连接传回 cookie。这可以确保 cookie ID 是安全的,且仅用于使用 HTTPS 的网站。如果启用此功能,则 HTTP 上的会话 Cookie 将不再起作用。

观察者模式

直接上代码:

// 发布者
function Publisher(){
this.observers = [];
this.state = "";
//新增两个对于state的操作 获取/更新
this.getState=function(){
    return state;
}
this.setState=function(value){
    state = value;
    this.notice();
}
}
// 订阅
Publisher.prototype.subscribe = function(observer) {
this.observers.push(observer);
}
// 发布
Publisher.prototype.notice = function() {
var observers = this.observers;
for(let i=0;i<observers.length;i++){
    observers[i].update(this.getState());
}
}
// 退订
Publisher.prototype.unsubscribe = function() {
var observers = this.observers;
for (var i = 0; i < observers.length; i++) {
    if(observers[i]===observer){
        observers.splice(i,1);
    }
};
return this;
}

//订阅者
function Subscribe(){
this.update = function(data){
      console.log(data);
};
}

//实际应用
var oba = new Subscribe();
var pba = new Publisher();
pba.subscribe(oba); //发布者更新了内容`
pba.setState("open "); //发布者更新了内容`

prefetch和preload

  1. preload的script代码会被优先加载,并且会占用http并发数
  2. prefetch的script被安排正常js的后面加载,这样才达到了优化效果
    (webpack的懒加载其实就是把组件js变成一个prefetch的js)

image

image
image

webpack 懒加载:
这里的repaymentlist是懒加载的,其他是正常引入的
image

自己实现一个promise

function Promise(executor) {
let self = this;
self.value = undefined;
self.reason = undefined;
self.status = 'pending';

self.onFulFilledCallbacks = [];
self.onRejectedCallbacks = [];

function resolve(value) {
    if (self.status === 'pending') {
        self.value = value;
        self.status = 'resolved'

        self.onFulFilledCallbacks.forEach(onFulFilled => {
            onFulFilled(self.value)
        });
    }
}
function reject(reason) {
    if (self.status === 'pending') {
        self.reason = reason;
        self.status = 'rejected';
        self.onRejectedCallbacks.forEach(onRejected => {
            onRejected(self.reason)
        });
    }
}

try {
    executor(resolve, reject);
} catch (error) {
    reject(error)
}
}

//给这个函数加个返回值,返回值就是一个新new的promise对象
Promise.prototype.then = function (onFulFilled, onRejected) {
//p2的resolve在里面,外面拿不到,只有这样很贱的给在外面记下来了
let p2Resolve ;
let p2Reject;
let p2 = new Promise((resolve, reject) => {
  p2Resolve = resolve;
  p2Reject = reject;
});

if (this.status === 'pending') {
  this.onFulFilledCallbacks.push(() => {
    onFulFilled(this.value)
    p2Resolve()
  });
  this.onRejectedCallbacks.push(() => {
    onRejected(this.reason)
    p2Reject()
  })
} else if (this.status === 'resolved') {
  onFulFilled(this.value);
  p2Resolve()
} else if (this.status === 'rejected') {
  onRejected(this.reason);
  p2Reject()
}

return p2;
}

参考:https://juejin.im/post/5b55c28ee51d45195a7139d6
https://juejin.im/post/5b5c24ebe51d45195f0b4cfc

页面性能优化(二)

  1. 使用 html-webpack-plugin 自动插入 loading
  2. 使用 prerender-spa-plugin 渲染首屏
  3. 除掉外链 css
    截止到目前,我们的首屏体积 = html + css,依然有优化的空间,那就是把外链的 css 去掉,让浏览器在加载完 html 时,即可渲染首屏。

实际上,webpack 默认就是没有外链 css 的,你什么都不需要做就可以了。当然如果你的项目之前配置了 extract-text-webpack-plugin 或者 mini-css-extract-plugin 来生成独立的 css 文件,直接去掉即可。

有人可能要质疑,把 css 打入 js 包里,会丢失浏览器很多缓存的好处(比如你只改了 js 代码,导致构建出的 js 内容变化,但连带 css 都要一起重新加载一次),这样做真的值得吗?

确实这么做会让 css 无法缓存,但实际上对于现在成熟的前端应用来说,缓存不应该在 js/css 这个维度上区分,而是应该按照“组件”区分,即配合动态 import 缓存组件。

接下来你会看到,css in js 的模式带来的好处远大于这么一丁点缺点。

4 使用动态 polyfill
Polyfill 的特点是非必需和不变,因为对于一台手机来说,需要哪些 polyfill 是固定的,当然也可能完全不需要 polyfill。

现在为了浏览器的兼容性,我们常常引入各种 polyfill,但是在构建时静态地引入 polyfill 存在一些问题,比如对于机型和浏览器版本比较新的用户来说,他们完全不需要 polyfill,引入 polyfill 对于这部分用户来说是多余的,从而造成体积变大和性能损失。

比如 React 16 的代码中依赖了 ES6 的 Map/Set 对象,使用时需要你自己加入 polyfill,但目前几个完备的 Map/Set 的 polyfill 体积都比较大,打包进来会增大很多体积。

还比如 Promise 对象,实际上根据 caniuse.com 的数据,移动端上,**接近 94% 的用户浏览器,都是原生支持 Promise 的,并不需要 polyfill。但实际上我们打包时还是会打包 Promise 的 polyfill,也就是说,我们为了 6% 的用户兼容性,增大了 94% 用户的加载体积。

所以这里的解决方法就是,去掉构建中静态的 polyfill,换而使用 polyfill.io 这样的动态 polyfill 服务,保证只有在需要时,才会引入 polyfill。

具体的使用方法非常简单,只需要外链一个 js:

<script src="https://cdn.polyfill.io/v2/polyfill.min.js"></script>

当然这样是加载全部的 polyfill,实际上你可能并不需要这么多,比如你只需要 Map/Set 的话:

<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=Map,Set"></script>
动态 polyfill 的原理
如果你用最新的 Chrome 浏览器访问这个链接的话:https://cdn.polyfill.io/v2/polyfill.js,你会发现内容几乎是空的:

image

如果打开控制台,模拟 iOS 的 Safari,再访问一次,你会发现里面就出现了一些 polyfill(URL 对象的 polyfill):

image

这就是 polyfill.io 的原理,它会根据你的浏览器 UA 头,判断你是否支持某些特性,从而返回给你一个合适的 polyfill。对于最新的 Chrome 浏览器来说,不需要任何 polyfill,所以返回的内容为空。对于 iOS Safari 来说,需要 URL 对象的 polyfill,所以返回了对应的资源。

image
5 使用 SplitChunksPlugin 自动拆分业务基础库
Webpack 4 抛弃了原有的 CommonChunksPlugin,换成了更为先进的 SplitChunksPlugin,用于提取公用代码。

它们的区别就在于,CommonChunksPlugin 会找到多数模块中都共有的东西,并且把它提取出来(common.js),也就意味着如果你加载了 common.js,那么里面可能会存在一些当前模块不需要的东西。

而 SplitChunksPlugin 采用了完全不同的 heuristics 方法,它会根据模块之间的依赖关系,自动打包出很多很多(而不是单个)通用模块,可以保证加载进来的代码一定是会被依赖到的。

下面是一个简单的例子,假设我们有 4 个 chunk,分别依赖了以下模块:

image

如果是以前的 CommonChunksPlugin,那么默认配置会把它们打包成下面这样:
image
显然在这里,react、react-dom、angular 这些公用的模块没有被抽出成为独立的包,存在进一步优化的空间。

现在,新的 SplitChunksPlugin 会把它们打包成以下几个包:

这就保证了所有公用的模块,都会被抽出成为独立的包,几乎完全避免了多页应用中,重复加载相同模块的问题。

6 正确使用 Tree Shaking 减少业务代码体积
7 Code Splitting
8 LazyLoad
懒加载其实没什么好说的,目前也有一些比较成熟的组件了,自己实现一个也不是特别难:

react-lazyload

react-lazy-load

当然你也可以实现像 Medium 的那种加载体验(好像知乎已经是这样了),即先加载一张低像素的模糊图片,然后等真实图片加载完毕之后,再替换掉。

实际上目前几乎所有 lazyload 组件都不外乎以下两种原理:

监听 window 对象或者父级对象的 scroll 事件,触发 load;

使用 Intersection Observer API 来获取元素的可见性。
9 placeholder
我们在加载文本、图片的时候,经常出现“闪屏”的情况,比如图片或者文字还没有加载完毕,此时页面上对应的位置还是完全空着的,然后加载完毕,内容会突然撑开页面,导致“闪屏”的出现,造成不好的体验。

为了避免这种突然撑开的情况,我们要做的就是提前设置占位元素,也就是 placeholder:

已经有一些现成的第三方组件可以用了:

react-placeholder
react-hold

总结

这篇文章里,我们一共提到了下面这些优化加载的点:

在 HTML 内实现 Loading 态或者骨架屏;

去掉外联 css;

使用动态 polyfill;

使用 SplitChunksPlugin 拆分公共代码;

正确地使用 Webpack 4.0 的 Tree Shaking;

使用动态 import,切分页面代码,减小首屏 JS 体积;

编译到 ES2015+,提高代码运行效率,减小体积;

使用 lazyload 和 placeholder 提升加载体验。

推荐 : https://developers.google.com/web/fundamentals/performance/get-started/
参考资料:https://mp.weixin.qq.com/s?__biz=MzIzNjcwNzA2Mw==&mid=2247485902&idx=1&sn=952e0db3bc0f36b7cd4db71b17914daa&chksm=e8d28456dfa50d407e52c935cb7518cf76f1179a8bcbbac760f1685f88fd8d809ca84f5d9c3f#rd

发布订阅模式

代码:

var pubsub = {};
 (function(myObject) {
 // Storage for topics that can be broadcast
 // or listened to
 var topics = {};
 // An topic identifier
 var subUid = -1;
 // Publish or broadcast events of interest
 // with a specific topic name and arguments
 // such as the data to pass along
 myObject.publish = function( topic, args ) {
     if ( !topics[topic] ) {
         return false;
     }
     var subscribers = topics[topic],
         len = subscribers ? subscribers.length : 0;
     while (len--) {
         subscribers[len].func( topic, args );
     }
     return this;
 };
 // Subscribe to events of interest
 // with a specific topic name and a
 // callback function, to be executed
 // when the topic/event is observed
 myObject.subscribe = function( topic, func ) {
     if (!topics[topic]) {
         topics[topic] = [];
     }
     var token = ( ++subUid ).toString();
     topics[topic].push({
         token: token,
         func: func
     });
     return token;
 };
 // Unsubscribe from a specific
 // topic, based on a tokenized reference
 // to the subscription
 myObject.unsubscribe = function( token ) {
     for ( var m in topics ) {
         if ( topics[m] ) {
             for ( var i = 0, j = topics[m].length; i < j; i++ ) {
                 if ( topics[m][i].token === token ) {
                     topics[m].splice( i, 1 );
                     return token;
                 }
             }
         }
     }
     return this;
 };
}( pubsub ));

js 值传递和引用传递

  1. 当为函数传递参数的时候,是将此值复制一份传递给函数,所以在函数执行之后,num本身的值并没有被改变,函数中被改变的值仅仅是一个副本而已。
  2. 声明一个对象web,它是一个引用类型,当为函数传递参数的时候,是传递的web对象的引用,也就是此对象的内存地址,所以在函数中修改属性的对象就是函数外面创建的对象本身。

https://www.cnblogs.com/refe/p/5101744.html
https://www.cnblogs.com/lcngu/p/5876273.html
https://blog.csdn.net/weixin_39728230/article/details/80607294

页面性能优化总结(一)

页面性能优化办法有哪些?

互联网有一项著名的8秒原则。用户在访问Web网页时,如果时间超过8秒就会感到不耐烦,如果加载需要太长时间,他们就会放弃访问。

一、资源压缩与合并

主要包括这些方面:html压缩、css 压缩、js的压缩和混乱和文件合并。 资源压缩可以从文件中去掉多余的字符,比如回车、空格。你在编辑器中写代码的时候,会使用缩进和注释,这些方法无疑会让你的代码简洁而且易读,但它们也会在文档中添加多余的字节。

1.html压缩

html代码压缩就是压缩这些在文本文件中有意义,但是在HTML中不显示的字符,包括空格,制表符,换行符等,还有一些其他意义的字符,如HTML注释也可以被压缩。

如何进行html压缩:

使用在线网站进行压缩(开发过程中一般不用)
nodejs 提供了html-minifier工具
后端模板引擎渲染压缩
2.css代码压缩:

css代码压缩简单来说就是无效代码删除和css语义合并

如何进行css压缩:

使用在线网站进行压缩(开发过程中一般不用)
使用html-minifier工具
使用clean-css对css压缩

3.js的压缩和混乱

js的压缩和混乱主要包括以下这几部分:

无效字符的删除
剔除注释
代码语义的缩减和优化
代码保护(代码逻辑变得混乱,降低代码的可读性,这点很重要)
如何进行js的压缩和混乱

使用在线网站进行压缩(开发过程中一般不用)
使用html-minifier工具
使用uglifyjs2对js进行压缩
其实css压缩与js的压缩和混乱比html压缩收益要大得多,同时css代码和js代码比html代码多得多,通过css压缩和js压缩带来流量的减少,会非常明显。所以对大公司来说,html压缩可有可无,但css压缩与js的压缩和混乱必须要有!

4.文件合并

文件与文件之间有插入的上行请求,增加了N-1个网络延迟
受丢包问题影响更严重
keep-alive方式可能会出现状况,经过代理服务器时可能会被断开,也就是说不能一直保持keep-alive的状态
压缩合并css和js可以减少网站http请求的次数,但合并文件可能会带来问题:首屏渲染和缓存失效问题。那该如何处理这问题呢?----公共库合并和不同页面的合并

如何进行文件合并

使用在线网站进行文件合并
使用nodejs实现文件合并(gulp、fis3)
二、非核心代码异步加载异步加载的方式

1、异步加载的方式

异步加载的三种方式——async和defer、动态脚本创建

① async方式

async属性是HTML5新增属性,需要Chrome、FireFox、IE9+浏览器支持
async属性规定一旦脚本可用,则会异步执行
async属性仅适用于外部脚本
如果是多个脚本,该方法不能保证脚本按顺序执行
image

② defer方式

兼容所有浏览器
defer属性规定是否对脚本执行进行延迟,直到页面加载为止
如果是多个脚本,该方法可以确保所有设置了defer属性的脚本按顺序执行
如果脚本不会改变文档的内容,可将defer属性加入到script标签中,以便加快处理文档的速度

③动态创建script标签在还没定义defer和async前,异步加载的方式是动态创建script,通过window.onload方法确保页面加载完毕再将script标签插入到DOM中,具体代码如下:

image

2、异步加载的区别

1)defer是在HTML解析完之后才会执行,如果是多个,按照加载的顺序依次执行

2)async是在加载完之后立即执行,如果是多个,执行顺序和加载顺序无关
image

其中蓝色线代表网络读取,红色线代表执行时间,这俩都是针对脚本的;绿色线代表 HTML 解析。

三、利用浏览器缓存

对于web应用来说,缓存是提升页面性能同时减少服务器压力的利器。

浏览器缓存类型

1.强缓存:不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的network选项中可以看到该请求返回200的状态码,并且size显示from disk cache或from memory cache;

相关的header:

Expires :response header里的过期时间,浏览器再次加载资源时,如果在这个过期时间内,则命中强缓存。它的值为一个绝对时间的GMT格式的时间字符串, 比如Expires:Thu,21 Jan 2018 23:39:02 GMT

Cache-Control :这是一个相对时间,在配置缓存的时候,以秒为单位,用数值表示。当值设为max-age=300时,则代表在这个请求正确返回时间(浏览器也会记录下来)的5分钟内再次加载资源,就会命中强缓存。比如Cache-Control:max-age=300,

简单概括:其实这两者差别不大,区别就在于 Expires 是http1.0的产物,Cache-Control是http1.1的产物,两者同时存在的话,Cache-Control优先级高于Expires;在某些不支持HTTP1.1的环境下,Expires就会发挥用处。所以Expires其实是过时的产物,现阶段它的存在只是一种兼容性的写法。强缓存判断是否缓存的依据来自于是否超出某个时间或者某个时间段,而不关心服务器端文件是否已经更新,这可能会导致加载文件不是服务器端最新的内容,那我们如何获知服务器端内容较客户端是否已经发生了更新呢?此时我们需要协商缓存策略。

2.协商缓存:向服务器发送请求,服务器会根据这个请求的request header的一些参数来判断是否命中协商缓存,如果命中,则返回304状态码并带上新的response header通知浏览器从缓存中读取资源;另外协商缓存需要与cache-control共同使用。

相关的header:

①Last-Modified和If-Modified-Since:当第一次请求资源时,服务器将资源传递给客户端时,会将资源最后更改的时间以“Last-Modified: GMT”的形式加在实体首部上一起返回给客户端。
image

客户端会为资源标记上该信息,下次再次请求时,会把该信息附带在请求报文中一并带给服务器去做检查,若传递的时间值与服务器上该资源最终修改时间是一致的,则说明该资源没有被修改过,直接返回304状态码,内容为空,这样就节省了传输数据量 。如果两个时间不一致,则服务器会发回该资源并返回200状态码,和第一次请求时类似。这样保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。一个304响应比一个静态资源通常小得多,这样就节省了网络带宽。

但last-modified 存在一些缺点:

Ⅰ.某些服务端不能获取精确的修改时间

Ⅱ.文件修改时间改了,但文件内容却没有变

既然根据文件修改时间来决定是否缓存尚有不足,能否可以直接根据文件内容是否修改来决定缓存策略?----ETag和If-None-Match

②ETag和If-None-Match:Etag是上一次加载资源时,服务器返回的response header,是对该资源的一种唯一标识,只要资源有变化,Etag就会重新生成。浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的Etag值放到request header里的If-None-Match里,服务器只需要比较客户端传来的If-None-Match跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。如果服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源(当然也包括了新的ETag)发给客户端;如果ETag是一致的,则直接返回304知会客户端直接使用本地缓存即可。
image

两者之间对比:首先在精确度上,Etag要优于Last-Modified。Last-Modified的时间单位是秒,如果某个文件在1秒内改变了多次,那么他们的Last-Modified其实并没有体现出来修改,但是Etag每次都会改变确保了精度;如果是负载均衡的服务器,各个服务器生成的Last-Modified也有可能不一致。第二在性能上,Etag要逊于Last-Modified,毕竟Last-Modified只需要记录时间,而Etag需要服务器通过算法来计算出一个hash值。第三在优先级上,服务器校验优先考虑Etag

缓存的机制

强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,重新获取请求结果,再存入浏览器缓存中;生效则返回304,继续使用缓存。主要过程如下:

image

用户行为对浏览器缓存的影响

1.地址栏访问,链接跳转是正常用户行为,将会触发浏览器缓存机制;

2.F5刷新,浏览器会设置max-age=0,跳过强缓存判断,会进行协商缓存判断;

3.ctrl+F5刷新,跳过强缓存和协商缓存,直接从服务器拉取资源。

如果想了解更多缓存机制,请猛戳 深入理解浏览器的缓存机制

四、使用CDN

大型Web应用对速度的追求并没有止步于仅仅利用浏览器缓存,因为浏览器缓存始终只是为了提升二次访问的速度,对于首次访问的加速,我们需要从网络层面进行优化,最常见的手段就是CDN(Content Delivery Network,内容分发网络)加速。通过将静态资源(例如javascript,css,图片等等)缓存到离用户很近的相同网络运营商的CDN节点上,不但能提升用户的访问速度,还能节省服务器的带宽消耗,降低负载。

image

CDN是怎么做到加速的呢?

其实这是CDN服务商在全国各个省份部署计算节点,CDN加速将网站的内容缓存在网络边缘,不同地区的用户就会访问到离自己最近的相同网络线路上的CDN节点,当请求达到CDN节点后,节点会判断自己的内容缓存是否有效,如果有效,则立即响应缓存内容给用户,从而加快响应速度。如果CDN节点的缓存失效,它会根据服务配置去我们的内容源服务器获取最新的资源响应给用户,并将内容缓存下来以便响应给后续访问的用户。因此,一个地区内只要有一个用户先加载资源,在CDN中建立了缓存,该地区的其他后续用户都能因此而受益。

五、预解析DNS

资源预加载是另一个性能优化技术,我们可以使用该技术来预先告知浏览器某些资源可能在将来会被使用到。通过 DNS 预解析来告诉浏览器未来我们可能从某个特定的 URL 获取资源,当浏览器真正使用到该域中的某个资源时就可以尽快地完成 DNS 解析。例如,我们将来可从 example.com 获取图片或音频资源,那么可以在文档顶部的 标签中加入以下内容:
image

当我们从该 URL 请求一个资源时,就不再需要等待 DNS 的解析过程。该技术对使用第三方资源特别有用。通过简单的一行代码就可以告知那些兼容的浏览器进行 DNS 预解析,这意味着当浏览器真正请求该域中的某个资源时,DNS 的解析就已经完成了,从而节省了宝贵的时间。 另外需要注意的是,浏览器会对a标签的href自动启用DNS Prefetching,所以a标签里包含的域名不需要在head中手动设置link。但是在HTTPS下不起作用,需要meta来强制开启功能。这个限制的原因是防止窃听者根据DNS Prefetching推断显示在HTTPS页面中超链接的主机名。下面这句话作用是强制打开a标签域名解析
image

js 数组(一)

工作中遇到过滤数组,感觉有好多种方式,突然想把js数组相关的api重新梳理一下:
这是chrome中Array.prototype的打印输出:

image

vue源码学习(二)

image
image
可以看到 beforeCreate 和 created 的钩子调用是在 initState 的前后,initState 的作用是初始化 props、data、methods、watch、computed 等属性,之后我们会详细分析。那么显然 beforeCreate 的钩子函数中就不能获取到 props、data 中定义的值,也不能调用 methods 中定义的函数。

在这俩个钩子函数执行的时候,并没有渲染 DOM,所以我们也不能够访问 DOM,一般来说,如果组件在加载的时候需要和后端有交互,放在这俩个钩子函数执行都可以,如果是需要访问 props、data 等数据的话,就需要使用 created 钩子函数。之后我们会介绍 vue-router 和 vuex 的时候会发现它们都混合了 beforeCreatd 钩子函数。

js 数组方法总结 & 各方法是否改变原数组

不会改变原来数组的有:
concat()---连接两个或更多的数组,并返回结果。

every()---检测数组元素的每个元素是否都符合条件。

some()---检测数组元素中是否有元素符合指定条件。

filter()---检测数组元素,并返回符合条件所有元素的数组。

indexOf()---搜索数组中的元素,并返回它所在的位置。

join()---把数组的所有元素放入一个字符串。

toString()---把数组转换为字符串,并返回结果。
lastIndexOf()---返回一个指定的字符串值最后出现的位置,在一个字符串中的指定位置从后向前搜索。

map()---通过指定函数处理数组的每个元素,并返回处理后的数组。

slice()---选取数组的的一部分,并返回一个新数组。

valueOf()---返回数组对象的原始值。

-----------分割线-------------------

会改变原来数组的有:
pop()---删除数组的最后一个元素并返回删除的元素。

push()---向数组的末尾添加一个或更多元素,并返回新的长度。

shift()---删除并返回数组的第一个元素。

unshift()---向数组的开头添加一个或更多元素,并返回新的长度。

reverse()---反转数组的元素顺序。

sort()---对数组的元素进行排序。

splice()---用于插入、删除或替换数组的元素。

async/await 和 promise 的执行顺序

上代码:

async function async1() {
    console.log( 'async1 start' )
    await async2()
    console.log( 'async1 end' )
}

async function async2() {
    console.log( 'async2' )
}

console.log( 'script start' )

setTimeout( function () {
    console.log( 'setTimeout' )
}, 0 )

async1();

new Promise( function ( resolve ) {
    console.log( 'promise1' )
    resolve();
} ).then( function () {
    console.log( 'promise2' )
} )

console.log( 'script end' )

注:答案是以浏览器的eventloop机制为准的,在node平台上运行会有差异。

答案:
script start
async1 start
async2
promise1
script end
promise2
async1 end
setTimeout

思考:
1.async 做一件什么事情?
2.await 在等什么?
3.await 等到之后,做了一件什么事情?
4.补充: async/await 比 promise有哪些优势?(回头补充)

解析:
1.async 做一件什么事情?
一句话概括: 带 async 关键字的函数,它使得你的函数的返回值必定是 promise 对象
2.await 在等什么?
一句话概括: await等的是右侧「表达式」的结果
3.await 等到之后,做了一件什么事情?
右侧表达式的结果,就是await要等的东西。
等到之后,对于await来说,分2个情况:
不是promise对象
是promise对象
如果不是 promise , await会阻塞后面的代码,先执行async外面的同步代码,同步代码执行完,再回到async内部,把这个非promise的东西,作为 await表达式的结果
如果它等到的是一个 promise 对象,await 也会暂停async后面的代码,先执行async外面的同步代码,等着 Promise 对象 fulfilled,然后把 resolve 的参数作为 await 表达式的运算结果。

重点分析一下: promise2 和 async1 end

回到async内部,执行await Promise.resolve(undefined)
这部分可能不太好理解,我尽量表达我的想法。

对于 await Promise.resolve(undefined) 如何理解呢?

根据 MDN 原话我们知道 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/await

如果一个 Promise 被传递给一个 await 操作符,await 将等待 Promise 正常处理完成并返回其处理结果。

在我们这个例子中,就是Promise.resolve(undefined)正常处理完成,并返回其处理结果。那么await async2()就算是执行结束了。

目前这个promise的状态是fulfilled,等其处理结果返回就可以执行await下面的代码了。

那何时能拿到处理结果呢?

回忆平时我们用promise,调用resolve后,何时能拿到处理结果?是不是需要在then的第一个参数里,才能拿到结果。

(调用resolve时,会把then的参数推入微任务队列,等主线程空闲时,再调用它)

所以这里的 await Promise.resolve() 就类似于

Promise.resolve(undefined).then((undefined) => {

})

把then的第一个回调参数 (undefined) => {} 推入微任务队列。

then执行完,才是await async2()执行结束。

await async2()执行结束,才能继续执行后面的代码。
此时当前宏任务1都执行完了,要处理微任务队列里的代码。

微任务队列,先进选出的原则,

执行微任务1,打印promise2
执行微任务2,没什么内容..
但是微任务2执行后,await async2()语句结束,后面的代码不再被阻塞,所以打印
console.log( 'async1 end' )

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.