开发技巧
需求分析
- 输入查询条件得出结果的需求:留意是数据筛选还是查询
JavaScript
JS基本原理
- javascript运行在单线程环境
数组操作
this指向
- 首先this指向的对象只有三个:windows、函数实例、所处对象
- 如果函数有this,但是没有所处对象或函数实例调用,那么this指向windows
function a(){
var user = "追梦子";
console.log(this.user); //undefined
console.log(this); //Window
}
a();
复制代码
var o = {
a:10,
b:{
a:12,
fn:function(){
console.log(this.a); //undefined
console.log(this); //window
}
}
}
var j = o.b.fn;
j();
复制代码
- 如果this被所处对象或函数实例调用,this指向该对象
var o = {
a:10,
b:{
//a:12,
fn:function(){
console.log(this.a); //undefind 有两个对象b和o,所以此this.a指向它的上一级
}
},
fn1:function(){
console.log(this.a); //10
}
}
o.fn1();
o.b.fn();
复制代码
- 当函数中有this,且最后使用了return,如果return的是对象,那么this指向该对象,否则this指向函数实例
function fn()
{
this.user = '追梦子';
return {}; // 或function(){}
}
var a = new fn;
console.log(a.user); //undefined
复制代码
function fn()
{
this.user = '追梦子';
return 1;
}
var a = new fn;
console.log(a.user); //追梦子
复制代码
- 虽然null也是对象,但是null比较特殊,return null,this依然指向函数实例,不指向null
bind、apply、call的区别
- apply和call不同点在于,call需要一个个传入参数,apply可以数组形式传入参数;使用方法分别为funA.apply(funB, args)和funA.call(funB, arg1, arg2, arg3...)。
- bind的传参方式与call相同,但永久改变this的指向,并返回新的函数;使用方法为var newFun = funA.bind(funB, arg1, arg2...);如果在使用bind时传入了参数,那么之后调用newFun时参数也是默认被传入,如var newFun = funA.bind(null, a, b); newFun(c, d); 这里相当于传入了a、b、c、d四个参数。
柯里化
- 柯里化是创建一个已经设置好一个或多个参数的函数,是用于动态创建函数的强大功能
- 柯里化函数的通用方式:
function curry(fn) {
var args = Array.prototype.slice.call(arguments, 1);
return function () {
var inArgs = Array.prototype.slice.call(null, arguments);
var finalArgs = args.concat(inArgs);
return fn.apply(null, finalArgs);
};
}
复制代码
不可扩展对象
- 使用Object.preventExtensions()可以防止对象添加新成员
var ob = {name: 'tom'};
Object.preventExtensions(ob);
ob.age = 22;
alert(ob.age); // undefined;
复制代码
- 使用Object.preventExtensions()后的对象,非严格模式下,添加新成员静默失败;严格模式下,添加新成员抛错。
- 使用Object.preventExtensions()后的对象,依旧可以删除修改成员
- 可以使用Object.isExtensible()可判断是否可以扩展;
密封对象
- 使用Object.seal(ob)密封对象ob,对象不可添加和删除成员。非严格模式默认失败,严格模式下抛错。但是可以修改成员。
冻结对象
- 使用Object.freeze(ob)冻结对象ob,对象不可修改、添加、删除成员。非严格模式默认失败,严格模式下抛错。
定时器原理
- 指定的时间间隔表示何时将定时器的代码放入队列,而不是何时立即执行代码,如果队列里面还有其它任务,那么这些排在前面的任务将会被优先执行,定时器代码会被滞后执行。
- 异步队列中的任务会在进程空闲期执行
重复的定时器原理
- 重复定时器主要是setInterval,但使用setInterval有很多问题
- 如果再添加定时器代码到队列时,上一次添加到队列的代码还没有执行完,可能会导致定时器代码连续执行多次,没有停顿。为了解决这个问题,浏览器会判断队列里有无定时器代码实例,如果有则不添加代码到队列。
- 上述浏览器的解决方案也存在另一问题:某些定时任务被跳过
数据属性
访问器属性
获取通用唯一识别码
https://blog.youkuaiyun.com/mr_raptor/article/details/52280753
复制代码
纯函数
- 函数返回结果只依赖于参数
- 函数没有副作用,不会更改参数
运算符
- NaN === NaN // false
- +0 === -0 // true (但这里本应该是不等的,是JS的缺陷)
字符串
- replace(arg1, arg2):用于字符串内容替换,第一参数为正则表达式或字符串,第二参数为需替换的字符串或函数,函数为function(matchStr, group1[...groupN], index, sourceStr),matchStr为正则匹配到的字符串,group为捕获组,index为匹配项在字符串中的开始下标,sourceStr为原字符串
CSS
布局
- clearfix
.clearfix {
display: block;
zoom: 1;
&:after {
content: " ";
display: block;
font-size: 0;
height: 0;
clear: both;
visibility: hidden;
}
}
复制代码
ES6
对象的新增方法
- Object.is()方法可以准确比较NaN与其自身,+0和-0的比较,如:
- Object.assign()参数为非对象则转换为对象并返回
- Object.assign()参数为null或undefined则报错
- 利用Object.assign()克隆对象并拷贝对象的原型链
function clone(origin) {
let originProto = Object.getPrototypeOf(origin);
return Object.assign(Object.create(originProto), origin);
}
复制代码
- 以下克隆方法可以解决get、set不能克隆的问题
function shallowMerge(target, source) {
return Object.defineProperties(target, Object.getOwnPropertyDescriptors(source))
}
复制代码
- 利用Object.create()克隆对象,对象属性可以为访问器属性,并拷贝对象的原型链
function merge(ob) {
return Object.create(Object.getPrototypeOf(ob), Object.getOwnPropertyDescriptors(ob));
};
复制代码
- Obeject.entries()不能输出Symbol键值对
- Symbol类型的实例具有唯一性,一般用作属性,可用于替换魔术字符串
- 使用Symbol.for()可创建全局的唯一Symbol类型实例
- 数据结构Set的成员唯一,可用于数组去重,如:
Array.from(new Set(arr))
[...new Set(arr)]
复制代码
也可以用于字符串去重,如:
[...new Set(str)].join('')
复制代码
Map和Set
- Set的原型链属性有contructor、size,操作属性有add、delete、has、clear,遍历方法有keys、values、entries、forEach
- WeakSet的成员只是对象,且对象都为弱引用,若对象引用次数为0,对象则被gc
- WeakSet的实例操作方法有add、has、delete
- Map数据结构的键可以为任意类型
- Map数据类型+0和-0等同于一个键、NaN虽然等同于自身但是会把NaN视为一个键
- Map数据类型具有继承属性size,操作方法set、get、delete、clear、has,遍历方法有keys、values、entries、forEach
- WeakMap数据结构只接受对象作为键名
Proxy
- 元编程即为对编程语言进行编程
- proxy是一种元编程方法,机制是在操作对象前会作一层拦截
- proxy中的get、set可以被继承
- proxy可用于属性的链式操作
var ob = {
n: 0,
a: function(n) {
return n + 1;
},
b: function(n) {
return n + 1;
}
}
var pipe = function (ob, value) {
var funcStack = [];
var oproxy = new Proxy(ob , {
get : function (pipeObject, fnName) {
if (fnName === 'get') {
return funcStack.reduce(function (val, fn) {
return fn(val);
}, value);
}
console.log('fnName', fnName)
funcStack.push(ob[fnName]);
console.log('funcStack', funcStack)
return oproxy;
}
});
return oproxy;
};
var proxy = pipe(ob, 1);
console.log(proxy.a.b.get) // 3
复制代码
- Reflect是使用内部语言方法操作对象
Promise
- 概念:Promise是一个容器,保存着未来发生的事情。Promise是一个对象,可以获取异步操作的消息。
- 优点:
(1)状态不受外界影响:三种状态pending、fullfilled、reject (2)状态改变后不会再改变,什么时候都可以得到这个结果:从pending到fullfilled或从pending到reject后,状态resolved(定型),任意时刻可以得 到结果 - 缺点:
(1)Promise一旦创建,不可以取消
(2)如果在pending状态,不可以知道是处于刚开始还是即将完成状态
Interator
- Interator是一种接口,为不同的数据结构提供统一的访问机制,具备Interator的数据结构完成遍历操作
- 具备Interator的数据结构:Array、Map、Set、String、TypedArray、函数的 arguments 对象、NodeList对象
- 对象不具备Iterator接口,是因为属性的顺序是不确定的
- 只有类数组对象可以使用[Symbol.iterator]方法,如:
let iterable = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // 'a', 'b', 'c'
}
复制代码
- 普通对象使用[Symbol.iterator]方法无效
let iterable = {
a: 'a',
b: 'b',
c: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // undefined, undefined, undefined
}
复制代码
- [Symbol.iterator]方法的使用场合,在解构赋值、扩展运算符、yield*(yield*后面为可遍历结构时,会调用后面的可遍历结构的[Symbol.iterator]方法),其它场合:(for...of Array.from() Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]])) Promise.all() Promise.race())
- 除了next方法外,还有return(),throw()方法
- for...of循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments对象、DOM NodeList 对象)、后文的 Generator 对象,以及字符串
Generator
- Generator是一个状态机,同时也是遍历器对象生成器
- 特征:function和函数名之间有一个*号,函数内部使用field表达式,表示不同的状态
- 返回:指向内部状态的指针对象
- 可以把Generator赋值给对象的[Symbol.iterator]实现对象的iterator操作
- Generator执行后返回的遍历器也具有[Symbol.iterator],并且Symbol.iterator指向其自身
- 如果next带有参数,那么这个参数就是上一个field的返回值
function* generator() {
yield 1;
yield 2;
return 3;
}
var g = generator();
console.log(g.next())
console.log(g.next())
console.log(g.next())
/**
* Object {value: 1, done: false}
Object {value: 2, done: false}
Object {value: 3, done: true}
Object {value: undefined, done: true}
*/
复制代码
如果调用next时遇到return,返回值中的done就会提前为true;如果没有return,当把generator的值遍历完后调用next,返回的done才为true
- 实现斐波那契数列:
function* fibonacci() {
let [prev, cur] = [0, 1];
for (;;) {
yield cur;
[prev, cur] = [cur, prev + cur]
}
}
for (let i of fibonacci()) {
if (i > 10000) break;
console.log(i)
}
复制代码
- Generator可以作为对象属性:
const ob = {
* generator () { //... }
}
等同于
const ob = {
generator: function* () { //... }
}
复制代码
- 协程是指并行执行,可以交互执行权的线程
- 求值策略,即是函数的参数何时取值的问题,有两种类型:传值调用和传名调用
- 编译器的“传名调用”实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体。这个临时函数就叫做 Thunk 函数。
- 基于Thunk的流程自动化管理
var fs = require('fs');
var thunkify = require('thunkify');
var readFileThunk = thunkify(fs.readFile);
function run(fn) {
var gen = fn();
function next(err, data) {
var result = gen.next(data);
console.log(result)
if (result.done) return;
result.value(next);
}
next();
}
var g = function* (){
var f1 = yield readFileThunk('fileA');
var f2 = yield readFileThunk('fileB');
var fn = yield readFileThunk('fileN');
};
run(g);
复制代码
async
- async是generator的语法糖,优点:(1)内置执行器
(2)更好的语义
(3)更广的适用性,await后面可以是Promise或原始类型值(若原始类型值为n,相当于Promise.resolve(n))
(4)返回的值是Promise
class
- 子类没有定义constructor方法,这个方法会被默认添加
- 在子类的constructor方法中,需要调用super才可以使用this
- super在静态方法中指向父类,在普通方法中指向父类的原型
- 在子类普通方法中使用super调用父类的方法,方法中的this指向子类实例
- 在子类的静态方法中使用super调用父类方法,方法中的this指向子类而不是子类的实例
- 通过super为属性赋值,此时super就是this,导致赋值的是子类的属性
class A {
constructor() {
this.x = 1;
}
}
class B extends A {
constructor() {
super();
this.x = 2;
super.x = 3;
console.log(super.x); // undefined
console.log(this.x); // 3
}
}
let b = new B();
复制代码
- Object.getPrototypeOf方法可以用来从子类上获取父类
- 子类的构造函数必须执行一次super()
- 若A为B的父类,super()相当于A.prototype.constructor.call(this)
- new.target只能在constructor中使用,并且指向当前执行的函数
- 子类的__proto__指向父类,子类的prototype的__proto__指向父类的prototype
class A {
}
class B extends A {
}
B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true
复制代码
- 如果只是单纯的类,没有继承,类的__proto__指向Function.prototype,类的prototype.__proto__指向Object.prototype
class A {
}
A.__proto__ === Function.prototype // true
A.prototype.__proto__ === Object.prototype // true
复制代码
- 子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性
class Point {}
class ColorPoint extends Point {}
var p1 = new Point(2, 3);
var p2 = new ColorPoint(2, 3, 'red');
console.log(p2.__proto__.__proto__ === p1.__proto__ ) // true
console.log(p2.__proto__ === p1 ) // false
复制代码
- ES的原生构造函数有如下:
Boolean()
Number()
String()
Array()
Date()
Function()
RegExp()
Error()
Object()
在ES5中不能通过继承内部属性和内部方法,在ES6中可以实现。注意在继承Object时会有异常:
class NewObj extends Object{
constructor(){
super(...arguments);
}
}
var o = new NewObj({attr: true});
o.attr === true // false
复制代码
因为如果不是使用new Object()新建对象,ES6会忽略传入的参数
- Mixin混入模式是指多个对象合成新对象,新对象具有这些原对象成员的接口
Module
- export用于定义模块对外的接口
- export可以输出变量、函数、类,不能输出常量
- 需要注意使用export输出函数的写法
function fn() { ... }
export fn; // 错
export { fn }; // 对
复制代码
- export 只能放在模块的顶层,可以放在顶层的任意位置,但不可以放在函数内部
- export default A等同于将A赋值给default然后输出,所以可以
import { default as B } from 'xx.js';
复制代码
也可以
export { A as default };
复制代码
- export和import的复合写法:
export { A } from 'xxx.js'
复制代码
- import用于引入其他模块的功能
- import引入的变量是动态绑定,如import a,如果输出的a是异步变化的,那么引入a的也是变化的
- import命令具有提升效果,因为import再编译期间(代码运行之前)就会执行
ArrayBuffer
- ArrayBuffer是内存中一段二进制数据
- TypeArray包括9类视图:
(1)Init8Array: 8位整型 (2)Uinit8Array: 8位无符号整型 (3)Uinit8CArray: 8位无符号整型(自动过滤溢出) (4)Init16Array: 16位整型 (5)Uinit16Array: 无符号16位整型 (6)Init32Array: 32位整型 (7)Uinit32Array: 无符号32位整型 (8)Float64Array: 64位浮点型 (9)Ufloat64Array: 无符号64位浮点型 - DataArray: 自定义的复合格式的视图
- ArrayBuffer.prototype.byteLength:用户获取ArrayBuffer的实例
- ArrayBuffer.prototype.slice():用于将ArrayBuffer内存的一部分,拷贝为一段新的内存;过程为创建一段新的内存,然后拷贝
- ArrayBuffer.prototype.slice():判断是否为ArrayBuffer的视图实例(如 DataArray、TypeArray的实例)
- TypeArray与Array的区别:
(1)成员连续
(2)成员默认为0 (3)成员类型一致 (4)本身只是视图,实际数据存储在底层的ArrayBuffer
计算机基础
编码
- 编码是信息从一种形式转换到另一种信息的过程,解码即是逆编码。
- JavaScript有全局方法encodeURI()和encodeURIComponent();
- encodeURI不对以下字符编码
-
- 保留字符:; , / ? : @ & = + $
-
- 非转义字符:字母 数字 - _ . ! ~ * ' ( )
-
- 数字字符 #
- encodeURIComponent不会对以下字符编码
-
- 非转义字符:字母 数字 - _ . ! ~ * ' ( )
- 两者的区别是encodeURI不能对保留字符和#转义
解码
- encodeURI()和encodeURIComponent()对应的解码方法有decodeURI()和decodeURIComponent()
进程和线程
- 进程是资源分配的最小单位,线程是程序执行的最小单位
- 进程有独立的内存地址空间
- 一个进程下可以有多个线程
- 同一进程下的多个线程可共享进程的资源
- 多进程程序更加健壮,因为一个线程死掉,进程即死掉,该进程下的所有所有线程都会死掉;而一个进程死掉不会影响另一进程,因为进程占用独立的内存地址空间
计算机网络
链路层
- 每一个网卡都用一个mac地址
- 广播:假设子网略内有若干台主机,一台主机发出一个数据包,接收到包的主机会读取数据包头部的MAC地址,并与自身的MAC地址比较,如果相同则做进一步处理,否则丢弃这个包
网络层
- 网络层的存在是因为:链路层广播的效率低,通信双方需要在一个子网略,局限性太大
- 网络层引进了“网络地址”,引入网络地址可以区分哪一些MAC地址处于同一个子网络
- 规定网络地址的协议成为IP地址,由32位二进制数组成 11111111.11111111.11111111.00000000,可分为网络部分和主机部分,如果网络部分为前24位,主机部分位后8位,那么前24位相同的两个ip地址肯定在同一个子网络。
- 子网掩码用于确定IP地址的网络部分,比如子网掩码位255.255.255.0,那么肯定是前24位为网络部分
- ARP协议:用于同一子网络的通信,协议内容是:发出的包含有所要查询主机的ip地址和mac地址,mac地址为FF:FF:FF:FF:FF:FF(12位16进制),表示广播到所有主机。所在子网络的每一台主机会收到这个包,取出其中的IP地址并与自身比对,如果相同双方都做出回复,告诉对方自己的mac地址,否则丢弃这个包
- 实现数据包的成功发送,需要获取接收方的MAC地址、IP地址、网关地址、DNP服务器IP地址
动态IP地址
- 静态IP地址:每次开机都用一个IP地址上网,即是“静态IP地址上网”。为了更方便地为新接入的主机添加IP地址,一般会使用动态IP地址。DHCP协议(动态主机设置协议)可实现动态IP地址
获取动态IP地址过程
(1)新加入的主机会向DHCP服务器发送一个DHCP请求数据包,数据包的结构为 |以太网标头|IP标头|UDP标头|data(数据内容)|
- 以太网标头含有发送方和接收方的MAC地址,前者为新加入主机自身的MAC地址,后者为FF.FF.FF.FF.FF.FF,即是广播地址
- IP标头含有发送方和接收方的IP地址,因为两者都不知道,于是发送发IP地址设为0.0.0.0,接收方IP地址设为255.255.255.255
- UDP标头含有发送方和接收方的端口,前者设为68,后者设为67
- 请求数据包在子网络内广播,接收到数据包的主机首先解析数据包MAC地址,因为接收方MAC地址为FF.FF.FF.FF.FF.FF广播地址,所以会解析数据包的IP地址,接收方IP为255.255.255.255,将之与自身IP地址比对,发现不同则丢包
(2)当DHCP服务器接收到请求数据包并解析接收方IP地址,发现为255.255.255.255,服务器知道需要去分配IP,发出响应数据包
(3)响应数据包的数据内容中含有为新主机分配的IP地址、网关IP地址、DNS的IP地址 - 响应数据包的以太网标头含有发送方和接收方的MAC地址,前者为DHCP服务器看的MAC地址,后者为新主机的MAC地址
- IP标头含有发送方IP地址为DHCP服务器自身的IP地址,接收方IP地址依然为255.255.255.255
- UDP标头的发送方端口为67,接收方端口为68 (4)新加入的数据包接收到响应数据包,拿到IP地址、子网掩码、网关IP、DNS服务器的IP。
传输层
- 端口:表示数据包供哪个程序使用,端口号可以为0到65535,0到1023供系统使用
- 传输层是"端口到端口"的通信
- UDP的优点是简单,但是可靠性差,无法知道对方是否接收到。
- TCP可靠性比UDP强,缺点是过程复杂、消耗较多资源
应用层
- 主要用于规定好客户端和服务端的通信格式
一次网络通信的过程
比如说用我们的计算机访问一次百度(baidu.com):
- 设置好本机IP、子网掩码、DNS_IP、网关IP
- 本机向DNS_IP发送一个DNS数据包,通过DNS协议获得将baidu.com解析为IP地址
- 接下来创建我们的数据包,首先将HTTP数据包嵌入TCP数据包,新数据包再嵌入IP数据包,这个由HTTP数据包、TCP数据包、IP数据包组成的新数据包最后会嵌入以太网数据包(它的结构如下图)
- 通过子网掩码判断本机与百度服务器是否在同一网关(我们和百度服务器的必然是不同的)
- 以太网标头的目标MAC地址改为本机网关的MAC地址,我们的数据包转由网关发送
- 通过ARP协议将百度服务器的IP地址解析为MAC地址,得到了最终的数据包。假设这一个数据包大小为4600字节,因为以太网数据包的大小限制为1500字节,所以需要切割为大小分别为1500、1500、1500、100字节的四个以太网数据包,最后再发送到百度的服务。
JS替换语言
TypeScript
基础类型
- 基础类型有:number、boolean、string、null、undefined、symbol、object
- null和undefined是所有类型的子类型,因此可以把它们赋给任何已经定义类型的变量
- never是那些不存在值的类型,如只会抛错和没有return值的函数,是所有类型的子类型,但它没有子类型(包括any)
- 类型断言:类似于类型转换、不进行类型检查,可有两个方式:和as string
- 如果想变量只读使用const,想对象的属性只读,可定义类型为只读类型,比如
interface Point {
readonly x: number;
readonly y: number;
}
复制代码
- 处理额外属性可以使用索引签名,索引签名可以为数字也可以是字符串,但是前者值的类型必须是后者的子类型,如(propName为其余属性):
interface Ob {
name: string;
[rest: string]: any; // string定义属性名类型、any定义值的类型
}
复制代码
模块化
AMD(异步模块规范)
define(['jquery', 'underscore'], function ($, _) {
// 方法
function a(){}; // 私有方法,因为没有被返回(见下面)
function b(){}; // 公共方法,因为被返回了
function c(){}; // 公共方法,因为被返回了
// 暴露公共方法
return {
b: b,
c: c
}
});
复制代码
CommonJS
var $ = require('jquery');
复制代码
UMD
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node, CommonJS之类的
module.exports = factory(require('jquery'));
} else {
// 浏览器全局变量(root 即 window)
root.returnExports = factory(root.jQuery);
}
}(this, function ($) {
// 方法
function myFunc(){};
// 暴露公共方法
return myFunc;
}));
复制代码