achen的个人博客

一个能力有限的前端


  • 首页

  • 归档49

  • 公益 404

  • 搜索

UDP

发表于 2020-03-11 | 更新于 2020-07-08

UDP

UDP协议是面向无连接的,也就是说不需要在正式传递数据之前先连接起双方。然后UDP协议只是数据报文的搬运工,不保证有序且不丢失的传递到对端,并且UDP协议也没有任何控制流量的算法,总的来说UDP相较于TCP更加的轻便。

我们经常使用“ping”命令来测试两台主机之间TCP/IP通信是否正常, 其实“ping”命令的原理就是向对方主机发送UDP数据包,然后对方主机确认收到数据包, 如果数据包是否到达的消息及时反馈回来,那么网络就是通的。

面向无连接

UDP是不需要和TCP一样在发送数据前进行3次握手建立连接的,想发数据就可以发送了。
并且也只是数据报文的搬运工,不会对数据报文进行任何拆分和拼接操作。

具体来说:

  • 在发送端,应用层将数据传递给传输层的UDP协议,UDP只会给数据增加一个UDP头标识下是UDP协议,然后就传递给网络层
  • 在接收端,网络层将数据传递给传输层,UDP只去除IP报文头就传递给应用层,不会任何拼接操作

不可靠性

首先不可靠性体现在无连接上,通信都不需要建立连接,想发就发,这样的情况肯定不可靠。
并且收到什么数据就传递什么数据,并且也不会备份数据,发送数据也不会关心对方是否已经正确接收到数据了。
再者网络环境时好时坏,但是 UDP 因为没有拥塞控制,一直会以恒定的速度发送数据。即使网络条件不好,也不会对发送速率进行调整。这样实现的弊端就是在网络条件不好的情况下可能会导致丢包,但是优点也很明显,在某些实时性要求高的场景(比如电话会议)就需要使用 UDP 而不是 TCP。

高效

虽然 UDP 协议不是那么的可靠,但是正是因为它不是那么的可靠,所以也就没有 TCP 那么复杂了,需要保证数据不丢失且有序到达。
因此 UDP 的头部开销小,只有八字节,相比 TCP 的至少二十字节要少得多,在传输数据报文时是很高效的。

传输方式

UDP 不止支持一对一的传输方式,同样支持一对多,多对多,多对一的方式,也就是说 UDP 提供了单播,多播,广播的功能。

适合使用的场景

  • 直播

想必大家都看过直播吧,大家可以考虑下如果直播使用了基于 TCP 的协议会发生什么事情?

TCP 会严格控制传输的正确性,一旦有某一个数据对端没有收到,就会停止下来直到对端收到这个数据。这种问题在网络条件不错的情况下可能并不会发生什么事情,但是在网络情况差的时候就会变成画面卡住,然后再继续播放下一帧的情况。

但是对于直播来说,用户肯定关注的是最新的画面,而不是因为网络条件差而丢失的老旧画面,所以 TCP 在这种情况下无用武之地,只会降低用户体验。

  • 王者荣耀

为什么这样说呢?首先对于王者荣耀来说,用户体量是相当大的,如果使用 TCP 连接的话,就可能会出现服务器不够用的情况,因为每台服务器可供支撑的 TCP 连接数量是有限制的。

再者,因为 TCP 会严格控制传输的正确性,如果因为用户网络条件不好就造成页面卡顿然后再传输旧的游戏画面是肯定不能接受的,毕竟对于这类实时性要求很高的游戏来说,最新的游戏画面才是最需要的,而不是老旧的画面,否则角色都不知道死多少次了。

总结

  • UDP 相比 TCP 简单的多,不需要建立连接,不需要验证数据报文,不需要流量控制,只会把想发的数据报文一股脑的丢给对端
  • 虽然 UDP 并没有 TCP 传输来的准确,但是也能在很多实时性要求高的地方有所作为

面经

发表于 2020-03-10 | 更新于 2020-07-27

说说你对JS模块化的理解

模块化就是将一个大的功能拆分为多个块,每一个块都是独立的,你不需要去担心污染全局变量,命名冲突什么的。

那么模块化的好处也就显然易见了

  • 解决命名冲突
  • 依赖管理
  • 代码更加可读
  • 提高复用性

js设计之初并没有模块化的概念,所以原始时代只能通过自执行函数来设计模块化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var myModule = (function(window) {
// private
var moduleName = 'module';

// public
function setModuleName(name) {
moduleName = name;
};
// public
function getModuleName() {
return moduleName;
};

return {setModuleName, getModuleName};
})(window);

它通过闭包的特性打开了一个新的作用域,缓解了全局作用域命名冲突和安全性的问题。但是,开发者并不能够用它来组织和拆分代码,于是乎便出现了以此为基石的模块化规范。

1. CommonJs规范(同步加载模块)
  • 允许模块通过require的方法来同步加载所要依赖的其他模块,然后通过exports或module.exports来导出需要暴露的接口
1
2
3
4
5
6
7
8
9
10
11
12
//  a.js
module.exports = {
a: 1,
};
// or
exports.a = 1;

// b.js
var module = require('./a.js');
module.a = 1;

// require函数同步加载了a.js,并且返回了module.exports输出字面量的拷贝值。
  • 优点:
  1. 简单容易使用
  2. 服务器端模块便于复用
  • 缺点:
  1. 同步加载的方式不适合在浏览器环境中使用,同步意味着阻塞加载,浏览器资源是异步加载
  2. 不能非阻塞的并行加载多个模块
2. AMD(异步加载模块)

区别于CommonJS,AMD规范的被依赖模块是异步加载的,而定义的模块是被当作回调函数来执行的,依赖于require.js模块管理工具库。

1
2
3
4
define(['./a', './b'], function(a, b) {
a.do();
b.do();
})
3. CMD(异步加载模块)

CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。AMD 推崇依赖前置,CMD 推崇依赖就近。

1
2
3
4
5
6
7
8
9
10
11
12
define(function(require, exports, module) {
var a = require('./a');
a.doSomething();
// 异步加载一个模块,在加载完成时,执行回调
require.async(['./b'], function(b) {
b.doSomething();
});
// 对外暴露成员
exports.doSomething = function() {};
})
// 使用模块
seajs.use('path');
4. ES6 module

ES6的模块化已经不是规范了,而是JS语言的特性。

1
2
3
4
5
// 引入的语法就这样 import,XXX 这里有很多语法变化
import XXX from './a.js'
// 导出也有很多语法变化的写法,基本的就这两个,反正语法没什么难得
export function a() {}
export default function() {}

随着ES6的推出,AMD和CMD也随之成为了历史。ES6模块与模块化规范相比,有两大特点:

  • 模块化规范输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  • 模块化规范是运行时加载,ES6 模块是编译时输出接口。

commonJs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var count = 1;

function add() {
count++;
}

module.exports = {
count,
add,
}

var v = require('./common');
console.log(v.count); // 1
v.add();
console.log(v.count); // 1

ES6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var count = 1;

function add() {
count++;
}

export default {
count,
add
}

import v from './common';
console.log(v.count); // 1
v.add();
console.log(v.count); // 2

如何理解原型?如何理解原型链?

  • prototype
    这是一个显式原型属性,任何一个对象都有原型,但是有一个例外
1
let fun = Function.prototype.bind();

如果你已上述方法创建一个函数,那么可以发现这个函数不具有prototype属性。

prototype 如何产生的
当我们声明一个函数时,这个属性就被自动创建了

1
function Foo() {}

并且这个属性的值是一个对象(也就是原型),只有一个属性constructor

  • constructor
    constructor 是一个公有且不可枚举的属性。一旦我们改变了函数的prototype,那么新对象就没有这个属性了(当然可以通过原型链取到constructor)

  • __proto__
    这是每个对象都有的隐式原型属性,指向了创建该对象的构造函数的原型。其实这个属性指向了 [[prototype]],但是 [[prototype]] 是内部属性,我们并不能访问到,所以使用 __proto__ 来访问。

**实例对象的 __proto__ 如何产生的

当我们使用 new 操作符时,生成的实例对象拥有了 __proto__ 属性。

1
function Foo() {} //  这个函数是 Function的实例对象
1
2
3
4
5
6
7
8
9
10
11
//  纯对象的原型
console.log({}.__proto__); // {}

function Student(name) {
this.name = name;
}

const stu = new Student('wang');
// Student 类型实例的原型,默认也是一个对象
console.log(stu.__proto__); // { constructor: f }
console.log(stu.__proto__.constructor); // Student(name, age) { this.name = name }

new做了什么,new的模拟实现

  1. 创建了一个全新的对象。
  2. 这个对象会被执行[[Prototype]](也就是proto)链接。
  3. 生成的新对象会绑定到函数调用的this。
  4. 通过new创建的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上。
  5. 如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用会自动返回这个新的对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 模拟实现 new 操作符
* @param {Function} ctor [构造函数]
* @return {Object|Function|Regex|Date|Error} [返回结果]
*/
function create() {
// 创建一个空的对象
let obj = new Object()
// 获得构造函数
let Con = [].shift.call(arguments)
// 链接到原型
obj.__proto__ = Con.prototype
// 绑定 this,执行构造函数
let result = Con.apply(obj, arguments)
// 确保 new 出来的是个对象
return typeof result === 'object' ? result : obj
}

谈谈小程序的登录授权

1.调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。

2.调用 code2Session 接口,换取 用户唯一标识 OpenID 和 会话密钥 session_key。

vue的几种传参方式

  • props

  • vuex

  • $parent

  • $children

  • provide-inject

…项目相关

如何实现ajax

发表于 2020-03-10

ajax

ajax是异步的JavaScript和XML。ajax是一种通过在后台与服务器进行少量数据交换,在无需重新加载整个网页的情况下,更新部分网页的技术

如何实现ajax

  1. 创建XMLHttpRequest实例。通过创建一个XMLHttpRequest对象得到一个实例,调用实例的open()方法为这次ajax请求设定相应的HTTP方法,相应的地址以及是否异步。

  2. 发送http请求。调用spen()方法发送请求,其可以接收一个参数,既要作为请求主体所发送的数据。

  3. 接收服务器相应数据。监听readystatechange事件,通过该实例的readystate属性来判断请求状态,其分为0,1,2,3,4五种状态

属性 描述
onreadystatechange 存储函数(或函数名),每当 readyState 属性改变时,就会调用该函数。
readyState 存有XMLHttpRequest的状态。从0到4发生变化。
0: 请求未初始化
1: 已建立连接
2: 请求已接收
3: 请求处理中
* 4: 请求已完成,且响应已就绪
status 200: “OK”
404: 未找到页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var xhr;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject('Microsoft.XMLHTTP');
}
xhr.onreadystatechange(function() {
if (xhr.redayState === 4 && xhr.status === 200) {
console.log(xhr.responseText);
console.log(xhr.responseXml);
}
})
xhr.open('GET', 'www.xxxx.com/xxx/xxx', true);
xhr.send(null);

JavaScript 观察者模式与发布订阅模式

发表于 2020-03-06 | 更新于 2020-03-09

观察者模式

观察者模式定义了对象间一种一对多的依赖关系,当目标对象Subject的状态发生改变时,所有依赖它的对象ObServe都会得到通知。

模式特征:

一个目标者对象Subject,拥有方法:添加/删除/通知ObServer;

多个观察者对象ObServe,拥有方法:接收Subject状态变更通知并处理;

目标对象Subject状态变更时,通知所有ObServer。

Subject添加一系列ObServer,Subject负责维护与这些ObServe之间的联系,

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class Subject {
constructor() {
this.observers = []; // 观察者列表
}
// 添加
add(observer) {
this.observers.push(observer);
}
// 删除
remove(observer) {
let idx = this.observers.findIndex(item => item === observer);
idx > -1 && this.observers.splice(idx, 1);
},
// 通知
notify() {
for (let observer of observers) {
observer.update();
}
}
}

class ObServer {
constructor(name) {
this.name = name;
}
update() {
console.log(`收到通知:${this.name}`);
}
}

// 实例化目标者
let subject = new Subject();
// 实例化两个观察者
let obs1 = new ObServer('前端开发者');
let obs2 = new ObServer('后端开发者');
// 向目标者添加观察者
subject.add(obs1);
subject.add(obs2);

// 目标者通知更新
subject.notify();

// 输出:
// 收到通知:前端开发者
// 收到通知:后端开发者

优势:目标者与观察者,功能耦合度降低,专注自身功能逻辑;观察者被动接收更新,时间上解耦,实时接收目标者更新状态。
缺点:观察者模式虽然实现了对象间依赖关系的低耦合,但却不能对事件通知进行细分管控,如”筛选通知“,”指定主题事件通知“。

发布订阅模式

发布订阅模式基于一个事件(主题)通道,希望接收通知的对象Subscriber通过自定义事件订阅主题,被激活事件的对象Publisher通过发布主题事件的方式通知各个订阅该主题的subscriber对象。

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//  事件中心
let pubSub = {
list: {},
// 订阅
subscribe: function(key, fn) {
if (!this.list[key]) {
this.list[key] = [];
}
this.list[key].push(fn);
},
// 发布
publish: function(key, ...arg) {
for (let fn of this.list[key]) {
fn.call(this, ...arg);
}
},
// 取消订阅
unSubscribe: function(key, fn) {
let fnList = this.list[key];
if (!fnList) return false;

if (!fn) {
// 不传入指定取消的订阅方法,则清空所有key下的订阅
fnList && (fnList.length === 0);
} else {
fnList.forEach((item, index) => {
if (item === fn) {
fnList.splice(index, 1);
}
})
}
}
}
// 订阅
pubSub.subscribe('onwork', time => {
console.log(`上班了:${time}`);
})
pubSub.subscribe('offwork', time => {
console.log(`下班了:${time}`);
})
// 发布
pubSub.publish('onwork', '9:00:00');
pubSub.publish('offwork', '18:00:00');
// 取消订阅
pubSub.unSubscribe('onwork');

发布订阅模式中,订阅者各自实现不同的逻辑,且只接受自己对应的事件通知。实现你想要的 “不一样”。

DOM 事件监听也是 “发布订阅模式” 的应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let loginBtn = document.getElementById('#loginBtn');

// 监听回调函数(指定事件)
function notifyClick() {
console.log('点击');
}

// 添加事件监听
loginBtn.addEventListener('click', notifyClick);
// 触发点击,事件中心派发指定事件
loginBtn.click();

// 取消事件监听
loginBtn.removeEventListener('click', notifyClick);

发布订阅的通知顺序:

先订阅后发布时才通知(常规)

订阅后可获取过往以后的发布通知 (QQ离线消息,上线后获取之前的信息)

流行库的应用

jQuery 的 on 和 trigger,$.callback();

Vue 的双向数据绑定;

Vue 的父子组件通信 $on/$emit

什么是babel?

发表于 2020-03-02 | 更新于 2020-03-05

什么是babel?

官方的解释 Babel 是一个 JavaScript 编译器,用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前版本和旧版本的浏览器或其他环境中。简单来说 Babel 的工作就是:

  • 语法转换
  • 通过Polyfill的方式在目标环境中添加缺失的特性
  • JS源码转换

babel的基本原理

原理很简单,核心就是 AST (抽象语法树)。首先将源码转成抽象语法树,然后对语法树进行处理生成新的语法树,最后将新语法树生成新的 JS 代码,整个编译过程可以分为 3 个阶段 parsing (解析)、transforming (转换)、generating (生成),都是在围绕着 AST 去做文章,话不多说上图:

Babel 只负责编译新标准引入的新语法,比如 Arrow function、Class、ES Module 等,它不会编译原生对象新引入的方法和 API,比如 Array.includes,Map,Set 等,这些需要通过 Polyfill 来解决

babel的使用

运行babel所需的基本环境
  1. babel/cli

  2. babel/core

配置babel
  1. babel.config.js

  2. .babelrc

  3. babelrc.js

  4. package.json

四种配置方式作用都一样,你就合着自己的口味来,那种看着顺眼,你就翻它。

插件(Plugins)

插件使用来定义如何转换你的代码的。在babel的配置项中填写需要使用的插件名称,babel在编译的时候就会去加载node_modules中对应的npm包,然后编译插件对应的语法。

.babelrc

1
2
3
4
5
6
{
"plugins": [
"transform-decorators-legacy",
"transform-class-properties"
]
}
插件执行顺序

插件在预设(persets)前运行。

插件的执行顺序是从左往右执行。也就是说在上面的示例中,babel在进行AST遍历的时候会先调用transform-decorators-legacy插件中定义的转换方法,然后再调用transform-class-properties中的方法。

插件传参

参数是由插件名称和参数对象组成的一个数组

1
2
3
4
5
6
7
8
9
10
11
12
{
"plugins": [
[
"import",
{
"libraryName": "antd",
"libraryDirectory": "es",
"style": "css"
}
],
]
}
插件名称

插件名称如果为 @babel/plugin-xxx,可以使用短名称@babel/xx,如果为babel-plugin-xx,可以直接使用xx。

自定义插件

预设(Presets)

预设就是一堆插件(Plugin)的组合,从而达到某种转译的能力,就比如react中使用到的@babel/perset-react,他就是下面几种插件的组合。

  • @babel/plugin-syntax-jsx
  • @babel/plugin-transform-react-jsx
  • @babel/plugin-transform-react-display-name

当然我们也可以手动的在plugins中配置一系列的plugin来达到目的,就像这样:

1
2
3
4
5
6
7
{
"plugins": [
"@babel/plugin-syntax-jsx",
"@babel/plugin-transform-react-jsx",
"@babel/plugin-transform-react-display-name"
]
}

但是这样一方面显得不那么优雅,另一方面增加了使用者的使用难度。如果直接使用预设就清新脱俗多了~

1
2
3
4
5
{
"persets": [
"@babel/preset-react"
]
}
预设(Presets)的执行顺序

前面提到插件的执行顺序是从左往右,而预设的执行顺序恰好反其道行之,它是从右往左。

1
2
3
4
5
6
7
{
"presets": [
"a",
"b",
"c"
]
}

它的执行顺序是 c、b、a,是不是有点奇怪,这主要是为了确保向后兼容,因为大多数用户将 “es2015” 放在 “stage-0” 之前。

自定义预设(Presets)
那些她认识你而你不认识她的预设(Presets)
  1. @babel/preset-stage-xxx

@babel/perset-stage-xxx 是ES在不同阶段语法提案的转码规则而产生的预设,随着被批准为ES新版本的组成部分而进行相应的改变(例如ES6/ES2015)。

提案分为以下几个阶段:

  • stage-0,设想:只是一个想法,可能有babel插件,stage-0的功能范围最广,包含stage-1,stage-2,stage-3的所有功能
  • stage-1,建议:这是值得跟进的
  • stage-2,草案:初始规范
  • stage-3,候选:完成规范并在浏览器上初步实现
  • stage-4,完成:将添加到下一个年度版本发布中
  1. @babel-preset-es2015

preset-es2015是仅包含ES6功能的Babel预设。

实际上在babel7出来后上面提到的这些预设stage-x,preset-es2015都可以废弃了,因为@bael/preset-env出来一统江湖了。

  1. @babel/preset-env

前面两个预设是从ES标准的维度来确定转码规则的,而@babel/preset-env是根据浏览器的不同版本中缺失的功能确定转换规则的,在配置的时候我们只需要配置需要支持的浏览器版本就好了,@babel/preset-env会
根据目标浏览器生成对应的插件列表然后进行编译:

1
2
3
4
5
6
7
8
9
10
{
"presets": [
["env", {
"targets": {
"browser": ["last 10 versions", "ie >= 9"]
}
}],
],
...
}

在默认情况下@babel/preset-env支持将JS目前最新的语法转成ES5,但需要注意的是,如果你代码中用到了还没有成为JS标准的语法,该语法暂时还处于stage阶段,这个时候还是需要安装对应的stage预设,不然编译会报错。

1
2
3
4
5
6
7
8
9
10
{
"presets": [
["env", {
"targets": {
"browsers": ["last 10 versions", "ie >= 9"]
}
}],
],
"stage-0"
}

虽然可以采用默认配置,但如果不需要照顾所有的浏览器,还是建议你配置目标浏览器和环境,这样可以保证编译后的代码体积足够小,因为在有的版本浏览器中,新语法本身就能执行,不需要编译。@babel/preset-env 在默认情况下和 preset-stage-x 一样只编译语法,不会对新方法和新的原生对象进行转译,例如:

1
2
3
const arrFun = ()=>{}
const arr = [1,2,3]
console.log(arr.includes(1))

转换后

1
2
3
4
5
6
"use strict";

var arrFun = function arrFun() {};

var arr = [1, 2, 3];
console.log(arr.includes(1));

箭头函数被转换了,但是 Array.includes 方法,并没有被处理,这个时候要是程序跑在低版本的浏览器上,就会出现 includes is not function 的错误。这个时候就需要 polyfill 闪亮登场了。

Polyfill

polyfill 的翻译过来就是垫片,垫片就是垫平不同浏览器环境的差异,让大家都一样。

@babel/polyfill 模块可以模拟完整的ES5环境。 babel7.4以上版本已经弃用,可以用core-js代替

注意 @babel/polyfill 不是在 Babel 配置文件中配置,而是在我们的代码中引入。

1
2
3
4
5
import '@babel/polyfill';
const arrFun = ()=>{}
const arr = [1,2,3]
console.log(arr.includes(1))
Promise.resolve(true)

这样在低版本的浏览器中也能正常运行了。

使用core-js代替,不需要在文件中引入@babel/polyfill

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"presets": [
"@babel/preset-flow",
[
"@babel/preset-env",
{
"targets": {
"node": "8.10"
},
"corejs": "3", // 声明 corejs 版本
"useBuiltIns": "usage"
}
]
]
}

ECMAScript 和 JavaScript 的关系

ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现。

判断数据类型的几种方式

发表于 2020-02-28 | 更新于 2020-07-17

判断数据类型

1. typeof

返回数据类型,包含这7种: number、boolean、symbol、string、object、undefined、function。

引用类型,除了function返回function类型外,其他均返回object。

其中,null 有属于自己的数据类型 Null , 引用类型中的 数组、日期、正则 也都有属于自己的具体类型,而 typeof 对于这些类型的处理,只返回了处于其原型链最顶端的 Object 类型,没有错,但不是我们想要的结果。

2. Object.prototype.toString.call()

toString() 是 Object 的原型方法,调用该方法,默认返回当前对象的 [[Class]] 。这是一个内部属性,其格式为 [object Xxx] ,其中 Xxx 就是对象的类型。

对于 Object 对象,直接调用 toString() 就能返回 [object Object] 。而对于其他对象,则需要通过 call / apply 来调用才能返回正确的类型信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Object.prototype.toString.call('') ;   // [object String]
Object.prototype.toString.call(1) ; // [object Number]
Object.prototype.toString.call(true) ; // [object Boolean]
Object.prototype.toString.call(Symbol()); //[object Symbol]
Object.prototype.toString.call(undefined) ; // [object Undefined]
Object.prototype.toString.call(null) ; // [object Null]
Object.prototype.toString.call(new Function()) ; // [object Function]
Object.prototype.toString.call(new Date()) ; // [object Date]
Object.prototype.toString.call([]) ; // [object Array]
Object.prototype.toString.call(new RegExp()) ; // [object RegExp]console.log(2 instanceof Number); // false
console.log(true instanceof Boolean); // false
console.log('str' instanceof String); // false
console.log([] instanceof Array); // true
console.log(function(){} instanceof Function); // true
console.log({} instanceof Object); // true
// console.log(undefined instanceof Undefined);
// console.log(null instanceof Null);

Object.prototype.toString.call(new Error()) ; // [object Error]
Object.prototype.toString.call(document) ; // [object HTMLDocument]
Object.prototype.toString.call(window) ; //[object global] window 是全局对象 global 的引用

3. constructor

constructor是原型prototype的一个属性,当函数被定义时候,js引擎会为函数添加原型prototype,并且这个prototype中constructor属性指向函数引用, 因此重写prototype会丢失原来的constructor。

不过这种方法有问题:

1:null 和 undefined 无constructor,这种方法判断不了。

2:还有,如果自定义对象,开发者重写prototype之后,原有的constructor会丢失,因此,为了规范开发,在重写对象原型时一般都需要重新给 constructor 赋值,以保证对象实例的类型不被篡改。

avatar

4. instanceof

instanceof 是用来判断 A 是否为 B 的实例,表达式为:A instanceof B,如果 A 是 B 的实例,则返回 true,否则返回 false。 在这里需要特别注意的是:instanceof 检测的是原型,

1
2
3
4
5
6
7
8
console.log(2 instanceof Number);       //  false
console.log('str' instanceof String); // false
console.log(true instanceof Boolean); // false
console.log([] instanceof Array); // true
console.log({} instanceof Object); // true
console.log(function() {} instanceof Function) // true
console.log(undefined instanceof Undefined); // true
console.log(null instanceof Null); // true

在这里字面量值,2,true,’str’不是实例,所以判断为false。

avatar

由上图可以看出[]的原型指向Array.prototype,间接指向Object.prototype, 因此 [] instanceof Array 返回true, [] instanceof Object 也返回true。

instanceof 只能用来判断两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪种类型。

前端有哪些页面优化方法?

发表于 2020-02-27 | 更新于 2020-03-05

前端有哪些页面优化方法?

  • 减少http请求数

  • 从设计实现层面简化页面,减少元素的使用

  • 合理设置http缓存

  • 资源合并与压缩

  • 合并css图片,减少请求数的又一个好办法

  • 将外部脚本置底(页面信息加载后在加载)

  • 多图页面使用图片懒加载

  • 在js中尽量减少闭包的使用

  • 尽量合并css和js文件

  • 尽量使用字体图标或者svg图标,来代替传统的png等格式图片

  • 减少对dom的操作

  • 尽可能使用事件委托(事件代理)来处理事件绑定的操作

移动端的兼容问题

  • 给移动端添加点击事件会有300S的延迟 如果用点击事件,需要引一个fastclick.js文件,解决300s的延迟 一般在移动端用ontouchstart、ontouchmove、ontouchend

  • 移动端点透问题,touchstart 早于 touchend 早于click,click的触发是有延迟的,这个时间大概在300ms左右,也就是说我们tap触发之后蒙层隐藏, 此时 click还没有触发,300ms之后由于蒙层隐藏,我们的click触发到了下面的a链接上
    尽量都使用touch事件来替换click事件。例如用touchend事件(推荐)。
    用fastclick,github.com/ftlabs/fast…
    用preventDefault阻止a标签的click
    消除 IE10 里面的那个叉号
    input:-ms-clear{display:none;}

  • 设置缓存 手机页面通常在第一次加载后会进行缓存,然后每次刷新会使用缓存而不是去重新向服务器发送请求。如果不希望使用缓存可以设置no-cache。

  • 圆角BUG 某些Android手机圆角失效 background-clip: padding-box; 防止手机中网页放大和缩小 这点是最基本的,做为手机网站开发者来说应该都知道的,就是设置meta中的viewport

  • 设置用户截止缩放,一般写视口的时候就已经写好了。

call bind apply的区别?js垃圾回收

发表于 2020-02-26 | 更新于 2020-05-21

call bind apply的区别?

call() 和 apply()的第一个参数相同,就是指定的对象。这个对象就是该函数的执行上下文。

call()和apply()的区别就在于,两者之间的参数。

call()在第一个参数之后的后续所有参数就是传入该函数的值

apply()只有两个参数,第一个是对象,第二个是数组,这个数组就是该函数的参数。
bind()方法和前两者不同在于:bind()方法会返回执行上下文被改变的函数而不会立即执行,而前两者是直接执行该函数。他的参数和call()相同.

js垃圾回收

JavaScript中的内存管理是自动执行的,而且是不可见的。我们创建基本类型、对象、函数….所有这些都需要内存.
当不再需要某样东西时会发生什么? JavaScript 引擎是如何发现并清理它?

1)问什么是垃圾

一般来说没有被引用的对象就是垃圾,就是要被清除, 有个例外如果几个对象引用形成一个环,互相引用,但根访问不到它们,这几个对象也是垃圾,也要被清除。

2)如何检垃圾

  • 引用计数,有缺陷无法解决循环引用问题
  • 标记清除,目前采用的算法

css问题

发表于 2020-02-25 | 更新于 2020-03-05

css问题

用纯css创建一个三角形
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<head>
<style>
div {
width: 0;
height: 0;
border-top: 40px solid transparent;
border-left: 40px solid transparent;
border-right: 40px solid transparent;
border-bottom: 40px solid #ff0000;
}
</style>
</head>
<body>
<div></div>
</body>
如何理解css的盒模型

标准盒子模型:宽度=内容的宽度(content)+ border + padding
低版本IE盒子模型:宽度=内容宽度(content+border+padding)

box-sizing属性

  • content-box:默认值。这是 CSS2.1 指定的宽度和高度的行为。指定元素的宽度和高度(最小/最大属性)适用于box的宽度和高度。元素的填充和边框布局和绘制指定宽度和高度除外。
  • border-box: 指定宽度和高度(最小/最大属性)确定元素边框。也就是说,对元素指定宽度和高度包括了 padding 和 border 。通过从已设定的宽度和高度分别减去边框和内边距才能得到内容的宽度和高度。
  • inherit: 指定 box-sizing 属性的值,应该从父元素继承

浏览器的兼容性问题
大多数浏览器都会按照上面的图示来呈现内容。然而 IE 5 和 6 的呈现却是不正确的。根据 W3C 的规范,元素内容占据的空间是由 width 属性设置的,而内容周围的 padding 和 border 值是另外计算的。不幸的是,IE5.X 和 6 在怪异模式中使用自己的非标准模型。这些浏览器的 width 属性不是内容的宽度,而是内容、内边距和边框的宽度的总和。

如何清除浮动?

clear清除浮动(添加空div法)在浮动元素下方添加空div,并给该元素写css样式 {clear:both;height:0;overflow:hidden;}

给浮动元素父级设置高度

父级同时浮动(需要给父级同级元素添加浮动)

父级设置成inline-block,其margin: 0 auto居中方式失效

给父级添加overflow:hidden 清除浮动方法

万能清除法 after伪类 清浮动(现在主流方法,推荐使用)

1
2
3
4
5
6
7
8
9
10
11
div {
zoom: 1;
&:after {
content: '';
clear: both;
display: block;
height: 0;
overflow: hidden;
visibility: hidden;
}
}

谈谈你对Redux的理解

发表于 2020-02-24 | 更新于 2020-03-20

谈谈你对Redux的理解

使用Redux应该遵循的原则:

  1. 整个应用共享的state应该存储在store的状态树中,store是唯一的
  2. state不能直接修改,只能通过action表达修改的意图,调用dispatch()修改state
  3. state的修改规则reducers必须是一个纯函数,不能有副作用
Redux提供的API
  1. createStore
    createStore的作用就是创建一个Redux,store用来存放应用中所有的state
    createStore(reducer, [perloadState], [enhancer])
    createStore方法接受3个参数,后面两个是可选参数
    reducer: 参数的类型必须是function
    perloadState: 这个参数代表初始化的state(initialState), 可以是任意类型的参数
    enhancer: 这个参数代表添加的各种中间件,参数的类型必须是function

  2. combineReducers
    combineReducers主要是把多个reducer合并成一个,并且返回一个新的reducer函数,该函数接收的参数也是两个state和action

  3. compose
    主要是在中间件时候使用,合成函数
    compose(applyMiddleware(thunk),
    window.devToolsExtension ?
    window.devToolsExtension() : undefined
    )

  4. applyMiddleware

  5. bindActionCreator
    bindActionCreator的主要作用就是将aciton与dispatch函数绑定,生成直接可以出发action的函数

从输入URL到页面加载的全过程

  1. 首先,在浏览器地址中输入url
  2. 浏览器先查看浏览器缓存-系统缓存-路由器缓存,如果缓存中有,会直接在屏幕中显示页面内容。若没有,则跳到第三步操作
  3. 浏览器向DNS(Domain Name System)服务器请求解析该URL中的域名对应的IP地址
  4. 解析出IP地址后,根据该IP地址和默认端口80,和服务器建立TCP连接
  5. 浏览器发出读取文件(URL中域名后面部分对应的文件)的HTTP请求,该请求报文作为TCP三次握手的第三个报文的数据发送给服务器
  6. 服务器对浏览器请求做出响应,并把对应的html文本发送给浏览器
  7. 释放TCP连接
  8. 浏览器将该html文本并显示内容

重绘(Repaint) & 回流(重排Reflow)

  1. 重绘(repaint):当我们对DOM的修改导致的样式变化,但未影响几何属性时,浏览器不需要重新计算元素的几何属性,直接可以为该元素绘制新的样式,跳过了回流环节,这个过程就叫重绘。

  2. 回流(重排 reflow):对DOM树进行渲染,只要修改DOM或修改元素的形状大小,就会触发reflow,reflow的时候,浏览器会使已渲染好受到影响的部分失效,并重新构造这部分,完成reflow后,浏览器会重新绘制受影响的部分到屏幕中

回流必定会发生重绘,重绘不一定发生回流

1
2
3
4
5
6
7
8
9
10
//  触发Reflow
增加、删除、修改DOM节点时,会导致Reflow或Repaint
移动DOM的位置,或是搞个动画的时候
修改CSS样式的时候(宽、高、内外边距、边框等)
Resize窗口的时候(移动端没有这个问题),或是滚动的时候
改变元素内容(文本或图片等)
修改网页的默认字体时
// 触发Repaint
DOM改动
CSS改动
如何减少回流、重绘?

减少回流、重绘就是减少对DOM的操作

1.直接改变className,如果动态改变样式,则使用cssText(减少设置多项内联样式)

2.让要操作的元素进行“离线处理”,处理完后一起更新

当使用DocumentFragment进行缓存操作,引发一次回流和重绘

使用display:none 技术,只引发两次回流和重绘

使用cloneNode(true or false)和replaceChild技术,引发一次回流和重绘

3.不要经常访问会引起浏览器flush队列的属性,如果你确实要访问,利用缓存

4.让元素脱离动画流,减少render 树的规模

5.牺牲平滑度换取速度

6.避免使用table布局

7.IE中避免使用javascript表达式

跨域通信的几种方式

  1. JSONP(只支持get请求)
  2. window + hash
  3. window + domain
  4. window + name
  5. postMessage
  6. WebSocket
  7. CORS(Cross-origin resource sharing)跨域资源共享(所有的HTTP请求)
  8. nginx反向代理
  9. http-proxy服务端代理请求

前端错误类

  1. 即时运行错误:代码错误;捕获方式:try…catch…、window.onerror

  2. 资源加载错误;object.onerror(不会冒泡 )、performance.getEntries、Error事件捕获

浅拷贝

  • 循环遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const arr1 = [1, 2, ['ming', 'abc'], 5];

const shallowClone = (arr) => {
const dst = [];
for (let prop in arr) {
if (arr.hasOwnProperty(prop)) {
dst[prop] = arr[prop];
}
}
return dst;
}

const arr2 = shallowClone(arr1);
arr2[2].push('wuhan');
arr2[3] = 5;

console.log(arr1); [1, 2, ['ming', 'abc', 'wuhan'], 5]
console.log(arr2); [1, 2, ['ming', 'abc', 'wuhan'], 5]
  • object.assign()
  • Array.prototype.concat()
  • Array.prototype.slice()
  • obj展开运算符

深拷贝

  • 手动递归
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function deepClone (sourceObj, targetObj) {
let cloneObj = targetObj || {}
if(!sourceObj || typeof sourceObj !== "object" || sourceObj.length === undefined){
return sourceObj
}
if(sourceObj instanceof Array){
cloneObj = sourceObj.concat()
} else {
for(let i in sourceObj){
if (typeof sourceObj[i] === 'object') {
cloneObj[i] = deepClone(sourceObj[i], {})
} else {
cloneObj[i] = sourceObj[i]
}
}
}
return cloneObj
}
let sourceObj = {
a: 1,
b: {
a: 1
},
c: {
a: 1,
b: {
a: 1
}
},
d: function() {
console.log('hello world')
},
e: [1, 2, 3]
}
let targetObj = deepClone(sourceObj, {})
targetObj.c.b.a = 9
console.log(sourceObj)
console.log(targetObj)
  • JSON.parse(JSON.stringify())
1
2
3
4
5
6
1.如果json里面有时间对象,则序列化结果:时间对象=>字符串的形式;
2.如果json里有RegExp、Error对象,则序列化的结果将只得到空对象 RegExp、Error => {};
3.如果json里有 function,undefined,则序列化的结果会把 function,undefined 丢失;
4.如果json里有NaN、Infinity和-Infinity,则序列化的结果会变成null;
5.如果json里有对象是由构造函数生成的,则序列化的结果会丢弃对象的 constructor;
6.如果对象中存在循环引用的情况也无法实现深拷贝
  • lodash函数库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var _= require('lodash');
const obj1 = [
1,
'Hello!',
{ name:'ming1' },
[
{
name:'meng1',
}
],
]
const obj2 = _.cloneDeep(obj1);
obj2[0] = 2;
obj2[1] = 'Hi!';
obj2[2].name = 'ming2'
obj2[3][0].name = 'meng2';
console.log(obj1);
console.log(obj2);
12345
achen

achen

日常复制粘贴,问啥啥不会

49 日志
12 标签
© 2021 achen
由 Hexo 强力驱动 v3.7.1
|
主题 – NexT.Pisces v6.4.0