cosmic估算方法学习笔记

Cosmic 估算方法简介

COSMIC 软件度量方法简介

简单来说该方法是以数功能点的方式来预估项目工作量。把一个最小颗粒度的 story 拆分成 EXRW(输入、输出、读取、写入)4种功能点,最后再汇总统计来计算总工程量。

关于优缺点的思考

优点

快速,简便,通用

门槛低不需要专业人员参与,有助于和客户沟通确认实际需求,解释工作量。

缺点

改造、升级类项目的评估没有明显优势,可能难以适用。

内部逻辑复杂的项目恐怕难以适用,比如一个项目某个重点技术难点就要占据整个项目很大一部分时间的情况。

关于工程量估算的思考

敝人本专业是建筑环境与设备工程,我一直都在思考一个问题,为什么软件行业至今没有发展出像建筑工程行业一样成熟的项目估算,进度控制方法。

同时这个问题也可以转化成为什么建筑行业的成熟方法没有被套用在软件行业上。“人机料法环”六字真言多么顺耳。

大胆猜测之一,行业壁垒可能让两个行业缺乏顶层交流,软件行业可能从来不知道有这些方法可以借用。

但我认为这种可能性非常小,行业先贤在拓荒阶段肯定考虑过借鉴学习。大概率还是因为行业情况不同。建筑行业对质量的要求跟 IT 行业大部分情况下根本不是一个级别的。你不可能把一栋楼盖完住户入住之后再每个月升级打补丁。

还有就是 IT 行业的产品往往要求快速的研发发布抢占市场。

但我个人觉得以上的两种差别情况实际正在减少。IT 行业的渗透率越来越高,蓝海市场越来越少。红海市场的厮杀应该是质量、成本取胜而非速度。随着 IT 行业继续深入传统行业比如互联网+概念,汽车,飞机这些场景都需要堪比建筑行业的质量要求。理论上将来我们可能真有可能见到把建筑行业的工程管理方法引入到 IT 行业的一天。

回到 IT 行业的估算工作上来,就过往的经验来说估算不准确的情况 50% 因为需求不明确 40% 你估你的Boss就要下月交 9% 估算人员太过乐观 1% 估算人员过于悲观。就 90% 的情况来说你用什么方法根本不重要。就Cosmic 方法来说,能够增加和用户沟通便捷性,方便挖掘用户需求这方面来说,确实有其价值。也就是说对于交付甲方性质的项目有用,对于自研项目个人认为没有特别优势或劣势。

前端笔试选择题

单选题

1、控制台输出的结果是?
var arraynew = new Array(5)
arraynew[1]=1
arraynew[5]=2
console.log(arraynew.length)

A、0
B、1
C、5
D、6

2、在 css 选择器当中,优先级排序正确的是()

A、id选择器>标签选择器>类选择器
B、标签选择器>类选择器>id选择器
C、类选择器>标签选择器>id选择器
D、id选择器>类选择器>标签选择器

3、CSS 样式,下面哪一个元素能够达到最大宽度,且前后各有一个换行?( )

A、Block Element
B、Square Element
C、Side Element
D、Box Elemen

4、JavaScript中window对象的子对象不包含以下哪个对象?( )

A. document B. self C. history D. message

5、下边代码输出的结果是( )
var val = 'smtg';
console.log('Value is ' + (val === 'smtg') ? 'Something' : 'Nothing');

A: Value is Something
B: Value is Nothing
C: NaN
D: other

6、下边代码输出的结果是( )
var name = 'World!';
(function () {
    if (typeof name === 'undefined') {
        var name = 'Jack';
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
    }
})(); 

A: Goodbye Jack
B: Hello Jack
C: Hello undefined
D: Hello World

7、下列事件哪个不是由鼠标触发的事件()

A、click
B、contextmenu
C、mouseout
D、keydown

8、下列不属于javascript内置对象的是( )

A、Math
B、Date
C、RegExp
D、Window
E、Error

9、以下运行结果( )
for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}

A、0–9
B、10个10
C、10个9
D、无限循环

多选题

10、input元素的type属性的取值可以是( )

A、image
B、checkbox
C、button
D、select

11、下列关于web页面级优化描述最正确的是( )

A、减少HTTP请求的次数
B、进行资源合拼和压缩
C、Inline images
D、将外部脚本置于低端
E、减少不必要的HTTP跳转
F、以上描述都对

12、函数的调用方式有哪些:( )

A、直接调用
B、作为对象方法调用
C、作为构造函数调用
D、通过call和apply方法调用

参考文档

80道前端面试选择题

前端面试提纲

样式CSS

  1. 实现垂直居中
  2. 自适应布局 flex Grid
  3. 动画
  4. canvas

语言

  1. 原型链,作用域,闭包
  2. ES6,Pormise,async
  3. 设计模式,工厂、适配器、桥接
  4. 动态数据绑定的实现原理
  5. 全局状态管理 redux vuex
  6. TypeScript
  7. js 事件机制

框架

  1. 组件化理解
  2. Virtual Dom
  3. shadow dom

工程化

  1. 工程化的理解
  2. webpack loader
  3. 异步加载的实现原理
  4. CSS-module less sass
  5. 代码质量保障

性能优化

  1. 服务端渲染

调试

自动化测试

兼容性问题

编程思想

算法

其他

  1. 加班接受情况
  2. 职业规划
  3. 前端现状及未来走向
  4. 学习能力

N1小钢炮固件使用docker-ui安装openwrt

Docker-ui 安装及设置

进入N1小钢炮管理后台,点击 System 选项,之后再点击 Startup,在右侧往下拖,找到 Docker 的对应进程选项 /etc/init.d/S60dockerd,把 NO 点为 YES 启用 Docker,再点击左边的 start,之后点击下面的 SAVE 保存

点击Apps-Other-Docker Setting-install Docker UI (没安装时默认为黄色,下图为已经安装完成后的显示)

特别注意:这步操作没有在没有科学上网的情况下可能无法顺利完成

之后重启 N1

Openwrt 镜像容器安装步骤

  1. 通过 ssh 连接 N1
  2. 输入命令拉取OpenWrt镜像 docker pull kanshudj/n1-openwrtgateway:r9 此步同样建议使用科学上网环境
  3. 运行:ip link set eth0 promisc on
  4. 运行:docker network create -d macvlan --subnet=192.168.1.0/24 --gateway=192.168.1.1 -o parent=eth0 macnet
    (将第三个单独的1改为你主路由的网关地址,即你登录你主路由后台的IP地址第三位数字)

    这步是在 docker 环境中创建一个名为 macnet 的网卡

  5. 运行:docker run --restart always -d --network macnet --privileged kanshudj/n1-openwrtgateway:r9 /sbin/init
  6. 进入 Docker 管理界面,点击 Local,再点击 Containers
  1. 找到自己刚拉取完成的 OpenWrt 镜像,选择第四个命令行工具
  1. 点击Connect
  1. 输入 vi /etc/config/network
  1. 找到3处包含192.168.X.X的地方,输入i进入编辑,同样将第三个数字的位置改为你主路由的网段,在此处我的主路由为123,所以我将三处都改为123。当编辑完成后,按一次键盘左上角Esc键,之后输入:wq并且回车。(英文冒号+wq)

  2. 设置 OpenWrt 的初始密码命令为 passwd 还有可能为 passwordmount_root。输入正确之后会提示你输入正确明码。

  3. 重启N1

之后就可以通过你设置的 Openwrt 地址进入管理界面了(管理地址为上如3红框中的第一个),到这里我们旁路由OpenWrt就算全部设置完成了

参考文章

斐讯N1小钢炮Docker安装OpenWrt/LEDE做旁路由稳定去广告+科学功能

PS

我自己进行相关操作时遇到了两个坑,故把前人的操作步骤再完善一下,希望能让后来者少走弯路。

原生面经从初级到高级

原文链接

  1. 函数
    1.1函数的3种定义方法
    1.1.1 函数声明
    //ES5
    function getSum(){}
    function (){}//匿名函数
    //ES6
    ()=>{}//如果{}内容只有一行{}和return关键字可省,
    1.1.2 函数表达式(函数字面量)
    //ES5
    var sum=function(){}
    //ES6
    let sum=()=>{}//如果{}内容只有一行{}和return关键字可省,
    
    1.1.3 构造函数
    const sum = new Function('a', 'b' , 'return a + b')
    
    1.1.4 三种方法的对比
    1.函数声明有预解析,而且函数声明的优先级高于变量;
    2.使用Function构造函数定义函数的方式是一个函数表达式,这种方式会导致解析两次代码,影响性能。第一次解析常规的JavaScript代码,第二次解析传入构造函数的字符串

1.2.ES5中函数的4种调用
在ES5中函数内容的this指向和调用方法有关

1.2.1 函数调用模式
包括函数名()和匿名函数调用,this指向window

 function getSum() {
    console.log(this) //window
 }
 getSum()

 (function() {
    console.log(this) //window
 })()

 var getSum=function() {
    console.log(this) //window
 }
 getSum()

1.2.2 方法调用
对象.方法名(),this指向对象

var objList = {
   name: 'methods',
   getSum: function() {
     console.log(this) //objList对象
   }
}
objList.getSum()

1.2.3 构造器调用
new 构造函数名(),this指向构造函数

function Person() {
  console.log(this); //指向实例
}
var personOne = new Person();

1.2.4 间接调用
利用call和apply来实现,this就是call和apply对应的第一个参数,如果不传值或者第一个值为null,undefined时this指向window

function foo() {
   console.log(this);
}
foo.apply('我是apply改变的this值');//我是apply改变的this值
foo.call('我是call改变的this值');//我是call改变的this值

1.3 ES6中函数的调用
箭头函数不可以当作构造函数使用,也就是不能用new命令实例化一个对象,否则会抛出一个错误
箭头函数的this是和定义时有关和调用无关
调用就是函数调用模式

(() => {
   console.log(this)//window
})()

let arrowFun = () => {
  console.log(this)//window
}
arrowFun()

let arrowObj = {
  arrFun: function() {
   (() => {
     console.log(this)//指向函数arrFun
   })()
   }
 }
 arrowObj.arrFun();

1.4.call,apply和bind
1.IE5之前不支持call和apply,bind是ES5出来的;
2.call和apply可以调用函数,改变this,实现继承和借用别的对象的方法;

1.4.1 call和apply定义
调用方法,用一个对象替换掉另一个对象(this)
对象.call(新this对象,实参1,实参2,实参3…..)
对象.apply(新this对象,[实参1,实参2,实参3…..])

1.4.2 call和apply用法
1.间接调用函数,改变作用域的this值
2.劫持其他对象的方法

var foo = {
  name:"张三",
  logName:function(){
    console.log(this.name);
  }
}
var bar={
  name:"李四"
};
foo.logName.call(bar);//李四

实质是call改变了foo的this指向为bar,并调用该函数
3.两个函数实现继承

function Animal(name){
this.name = name;
this.showName = function(){
console.log(this.name);
}
}
function Cat(name){
Animal.call(this, name);
}
var cat = new Cat(“Black Cat”);
cat.showName(); //Black Cat
4.为类数组(arguments和nodeList)添加数组方法push,pop

(function(){
Array.prototype.push.call(arguments,’王五’);
console.log(arguments);//[‘张三’,’李四’,’王五’]
})(‘张三’,’李四’)
5.合并数组

let arr1=[1,2,3];
let arr2=[4,5,6];
Array.prototype.push.apply(arr1,arr2); //将arr2合并到了arr1中
6.求数组最大值

Math.max.apply(null,arr)
7.判断字符类型

Object.prototype.toString.call({})
1.4.3 bind
bind是function的一个函数扩展方法,bind以后代码重新绑定了func内部的this指向,不会调用方法,不兼容IE8

var name = ‘李四’
var foo = {
name: “张三”,
logName: function(age) {
console.log(this.name, age);
}
}
var fooNew = foo.logName;
var fooNewBind = foo.logName.bind(foo);
fooNew(10)//李四,10
fooNewBind(11)//张三,11 因为bind改变了fooNewBind里面的this指向
1.4.4 call,apply和bind原生实现
call实现:

Function.prototype.newCall = function(context, …parameter) {
if(context.instanceof Object) context={}
context.fn = this;
context.fn(…parameter);
delete context.fn;
}
let person = {
name: ‘Abiel’
}
function sayHi(age,sex) {
console.log(this.name, age, sex);
}
sayHi.newCall (person, 25, ‘男’); // Abiel 25 男
apply实现:

Function.prototype.newApply = function(context, parameter) {
if (typeof context === ‘object’) {
context = context || window
} else {
context = Object.create(null)
}
let fn = Symbol()
context[fn] = this
contextfn;
delete context[fn]
}
bind实现:

Function.prototype.bind = function (context,…innerArgs) {
var me = this
return function (…finnalyArgs) {
return me.call(context,…innerArgs,…finnalyArgs)
}
}
let person = {
name: ‘Abiel’
}
function sayHi(age,sex) {
console.log(this.name, age, sex);
}
let personSayHi = sayHi.bind(person, 25)
personSayHi(‘男’)
1.4.5 三者异同
同:都是改变this指向,都可接收参数
异:bind和call是接收单个参数,apply是接收数组

1.5.函数的节流和防抖
类型 概念 应用
节流 某个时间段内,只执行一次 scroll,resize事件一段时间触发一次
防抖 处理函数截止后一段时间依次执行 scroll,resize事件触发完后一段时间触发
节流:

1.5.1 节流
let throttle = function(func, delay) {
let timer = null;
return function() {
if (!timer) {
timer = setTimeout(()=> {
func.apply(this, arguments);
timer = null;
}, delay);
}
};
};
function handle() {
console.log(Math.random());
}
window.addEventListener(“scroll”, throttle(handle, 1000)); //事件处理函数
1.5.2 防抖
function debounce(fn, wait) {
let timeout = null;
return function() {
if (timeout !== null) clearTimeout(timeout);//如果多次触发将上次记录延迟清除掉
timeout = setTimeout(()=> {
fn.apply(this, arguments);
timeout = null;
}, wait);
};
}
// 处理函数
function handle() {
console.log(Math.random());
}
// 滚动事件
window.addEventListener(“scroll”, debounce(handle, 1000));
1.6.原型链
1.6.1 定义
对象继承属性的一个链条

1.6.2构造函数,实例与原型对象的关系
图片描述

var Person = function (name) { this.name = name; }//person是构造函数
var o3personTwo = new Person(‘personTwo’)//personTwo是实例
图片描述

原型对象都有一个默认的constructor属性指向构造函数

1.6.3 创建实例的方法
1.字面量

let obj={‘name’:’张三’}
2.Object构造函数创建

let Obj=new Object()
Obj.name=’张三’
3.使用工厂模式创建对象

function createPerson(name){
var o = new Object();
o.name = name;
};
return o;
}
var person1 = createPerson(‘张三’);
4.使用构造函数创建对象

function Person(name){
this.name = name;
}
var person1 = new Person(‘张三’);
1.6.4 new运算符
1.创了一个新对象;
2.this指向构造函数;
3.构造函数有返回,会替换new出来的对象,如果没有就是new出来的对象
4.手动封装一个new运算符

var new2 = function (func) {
var o = Object.create(func.prototype);    //创建对象
var k = func.call(o);             //改变this指向,把结果付给k
if (typeof k === ‘object’) {         //判断k的类型是不是对象
return k;                  //是,返回k
} else {
return o;                  //不是返回返回构造函数的执行结果
}
}
1.6.5 对象的原型链
图片描述

1.7 继承的方式
JS是一门弱类型动态语言,封装和继承是他的两大特性

1.7.1 原型链继承
将父类的实例作为子类的原型
1.代码实现
定义父类:

// 定义一个动物类
function Animal (name) {
// 属性
this.name = name || ‘Animal’;
// 实例方法
this.sleep = function(){
console.log(this.name + ‘正在睡觉!’);
}
}
// 原型方法
Animal.prototype.eat = function(food) {
console.log(this.name + ‘正在吃:’ + food);
};
子类:

function Cat(){
}
Cat.prototype = new Animal();
Cat.prototype.name = ‘cat’;

// Test Code
var cat = new Cat();
console.log(cat.name);//cat
console.log(cat.eat(‘fish’));//cat正在吃:fish undefined
console.log(cat.sleep());//cat正在睡觉! undefined
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true
2.优缺点
简单易于实现,但是要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,无法实现多继承

1.7.2 构造继承
实质是利用call来改变Cat中的this指向
1.代码实现
子类:

function Cat(name){
Animal.call(this);
this.name = name || ‘Tom’;
}
2.优缺点
可以实现多继承,不能继承原型属性/方法

1.7.3 实例继承
为父类实例添加新特性,作为子类实例返回
1.代码实现
子类

function Cat(name){
var instance = new Animal();
instance.name = name || ‘Tom’;
return instance;
}
2.优缺点
不限制调用方式,但不能实现多继承

1.7.4 拷贝继承
将父类的属性和方法拷贝一份到子类中
1.子类:

function Cat(name){
var animal = new Animal();
for(var p in animal){
Cat.prototype[p] = animal[p];
}
Cat.prototype.name = name || ‘Tom’;
}
2.优缺点
支持多继承,但是效率低占用内存

1.7.5 组合继承
通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
1.子类:

function Cat(name){
Animal.call(this);
this.name = name || ‘Tom’;
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
1.7.6 寄生组合继承
function Cat(name){
Animal.call(this);
this.name = name || ‘Tom’;
}
(function(){
// 创建一个没有实例方法的类
var Super = function(){};
Super.prototype = Animal.prototype;
//将实例作为子类的原型
Cat.prototype = new Super();
})();
1.7.7 ES6的extends继承
ES6 的继承机制是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this,链接描述

//父类
class Person {
//constructor是构造方法
constructor(skin, language) {
this.skin = skin;
this.language = language;
}
say() {
console.log(‘我是父类’)
}
}

//子类
class Chinese extends Person {
constructor(skin, language, positon) {
//console.log(this);//报错
super(skin, language);
//super();相当于父类的构造函数
//console.log(this);调用super后得到了this,不报错,this指向子类,相当于调用了父类.prototype.constructor.call(this)
this.positon = positon;
}
aboutMe() {
console.log(${this.skin} ${this.language} ${this.positon});
}
}

//调用只能通过new的方法得到实例,再调用里面的方法
let obj = new Chinese(‘红色’, ‘中文’, ‘香港’);
obj.aboutMe();
obj.say();
1.8.高阶函数
1.8.1定义
函数的参数是函数或返回函数

1.8.2 常见的高阶函数
map,reduce,filter,sort

1.8.3 柯里化
1.定义:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数

fn(a,b,c,d)=>fn(a)(b)(c)(d)
2.代码实现:

const currying = fn => {
const len = fn.length
return function curr (…args1) {
if (args1.length >= len) {
return fn(…args1)
}
return (…args2) => curr(…args1, …args2)
}
}

1.8.4 反柯里化
1.定义:

obj.func(arg1, arg2)=>func(obj, arg1, arg2)
2.代码实现:

Function.prototype.uncurrying = function() {
var that = this;
return function() {
return Function.prototype.call.apply(that, arguments);
}
};

function sayHi () {
return “Hello “ + this.value +” “+[].slice.call(arguments);
}
let sayHiuncurrying=sayHi.uncurrying();
console.log(sayHiuncurrying({value:’world’},”hahaha”));
1.8.5偏函数
1.定义:指定部分参数来返回一个新的定制函数的形式
2.例子:

function foo(a, b, c) {
return a + b + c;
}
function func(a, b) {
return foo(a,b,8);
}
2.对象
2.1.对象的声明方法
2.1.1 字面量
var test2 = {x:123,y:345};
console.log(test2);//{x:123,y:345};
console.log(test2.x);//123
console.log(test2.proto.x);//undefined
console.log(test2.proto.x === test2.x);//false
2.1.2 构造函数
var test1 = new Object({x:123,y:345});
console.log(test1);//{x:123,y:345}
console.log(test1.x);//123
console.log(test1.proto.x);//undefined
console.log(test1.proto.x === test1.x);//false
new的作用:
1.创了一个新对象;
2.this指向构造函数;
3.构造函数有返回,会替换new出来的对象,如果没有就是new出来的对象

2.1.3 内置方法
Obejct.create(obj,descriptor),obj是对象,describe描述符属性(可选)

let test = Object.create({x:123,y:345});
console.log(test);//{}
console.log(test.x);//123
console.log(test.proto.x);//3
console.log(test.proto.x === test.x);//true
2.1.4 三种方法的优缺点
1.功能:都能实现对象的声明,并能够赋值和取值
2.继承性:内置方法创建的对象继承到proto属性上
3.隐藏属性:三种声明方法会默认为内部的每个成员(属性或方法)生成一些隐藏属性,这些隐藏属性是可以读取和可配置的,属性分类见下面
4.属性读取:Object.getOwnPropertyDescriptor()或getOwnPropertyDescriptor()
5.属性设置:Object.definePropertype或Object.defineProperties

2.2.对象的属性
2.2.1 属性分类
1.数据属性4个特性:
configurable(可配置),enumerable(可枚举),writable(可修改),value(属性值)

2.访问器属性2个特性:
get(获取),set(设置)

3.内部属性
由JavaScript引擎内部使用的属性;
不能直接访问,但是可以通过对象内置方法间接访问,如:[[Prototype]]可以通过 Object.getPrototypeOf()访问;
内部属性用[[]]包围表示,是一个抽象操作,没有对应字符串类型的属性名,如[[Prototype]].

2.2.2 属性描述符
1.定义:将一个属性的所有特性编码成一个对象返回
2.描述符的属性有:数据属性和访问器属性
3.使用范围:
作为方法Object.defineProperty, Object.getOwnPropertyDescriptor, Object.create的第二个参数,

2.2.3 属性描述符的默认值
1.访问对象存在的属性

特性名 默认值
value 对应属性值
get 对应属性值
set undefined
writable true
enumerable true
configurable true
所以通过上面三种声明方法已存在的属性都是有这些默认描述符
2.访问对象不存在的属性

特性名 默认值
value undefined
get undefined
set undefined
writable false
enumerable false
configurable false
2.2.3 描述符属性的使用规则
get,set与wriable,value是互斥的,如果有交集设置会报错

2.2.4 属性定义
1.定义属性的函数有两个:Object.defineProperty和Object.defineProperties.例如:
Object.defineProperty(obj, propName, desc)

2.在引擎内部,会转换成这样的方法调用:
obj.[DefineOwnProperty]

2.2.5 属性赋值
1.赋值运算符(=)就是在调用[[Put]].比如:
obj.prop = v;

2.在引擎内部,会转换成这样的方法调用:
obj.[Put]

2.2.6 判断对象的属性
名称 含义 用法
in 如果指定的属性在指定的对象或其原型链中,则in 运算符返回true ‘name’ in test //true
hasOwnProperty() 只判断自身属性 test.hasOwnProperty(‘name’) //true
.或[] 对象或原型链上不存在该属性,则会返回undefined test.name //“lei” test[“name”] //“lei”
2.3.Symbol
2.3.1概念
是一种数据类型;
不能new,因为Symbol是一个原始类型的值,不是对象。

2.3.2 定义方法
Symbol(),可以传参

var s1 = Symbol();
var s2 = Symbol();
s1 === s2 // false

// 有参数的情况
var s1 = Symbol(“foo”);
var s2 = Symbol(“foo”);
s1 === s2 // false
2.3.3 用法
1.不能与其他类型的值进行运算;
2.作为属性名

let mySymbol = Symbol();

// 第一种写法
var a = {};
a[mySymbol] = ‘Hello!’;

// 第二种写法
var a = {
[mySymbol]: ‘Hello!’
};

// 第三种写法
var a = {};
Object.defineProperty(a, mySymbol, { value: ‘Hello!’ });

// 以上写法都得到同样结果
a[mySymbol] // “Hello!”
3.作为对象属性名时,不能用点运算符,可以用[]

let a = {};
let name = Symbol();
a.name = ‘lili’;
a[name] = ‘lucy’;
console.log(a.name,a[name]);
4.遍历不会被for…in、for…of和Object.keys()、Object.getOwnPropertyNames()取到该属性

2.3.4 Symbol.for
1.定义:在全局中搜索有没有以该参数作为名称的Symbol值,如果有,就返回这个Symbol值,否则就新建并返回一个以该字符串为名称的Symbol值
2.举例:

var s1 = Symbol.for(‘foo’);
var s2 = Symbol.for(‘foo’);
s1 === s2 // true
2.3.5 Symbol.keyFor
1.定义:返回一个已登记的Symbol类型值的key
2.举例:

var s1 = Symbol.for(“foo”);
Symbol.keyFor(s1) // “foo”

var s2 = Symbol(“foo”);
Symbol.keyFor(s2) // undefined
2.4.遍历
2.4.1 一级对象遍历方法
方法 特性
for … in 遍历对象自身的和继承的可枚举属性(不含Symbol属性)
Object.keys(obj) 返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)
Object.getOwnPropertyNames(obj) 返回一个数组,包括对象自身的所有可枚举属性(不含Symbol属性)
Object.getOwnPropertySymbols(obj) 返回一个数组,包含对象自身的所有Symbol属性
Reflect.ownKeys(obj) 返回一个数组,包含对象自身的所有(不枚举、可枚举和Symbol)属性
Reflect.enumerate(obj) 返回一个Iterator对象,遍历对象自身的和继承的所有可枚举属性(不含Symbol属性)
总结:1.只有Object.getOwnPropertySymbols(obj)和Reflect.ownKeys(obj)可以拿到Symbol属性
2.只有Reflect.ownKeys(obj)可以拿到不可枚举属性

2.4.2 多级对象遍历
数据模型:

var treeNodes = [
{
id: 1,
name: ‘1’,
children: [
{
id: 11,
name: ‘11’,
children: [
{
id: 111,
name: ‘111’,
children:[]
},
{
id: 112,
name: ‘112’
}
]
},
{
id: 12,
name: ‘12’,
children: []
}
],
users: []
},
];
递归:

var parseTreeJson = function(treeNodes){
if (!treeNodes || !treeNodes.length) return;

   for (var i = 0, len = treeNodes.length; i < len; i++) {

        var childs = treeNodes[i].children;

        console.log(treeNodes[i].id);

        if(childs && childs.length > 0){
             parseTreeJson(childs);
        }
   }
};

console.log('------------- 递归实现 ------------------');
parseTreeJson(treeNodes);

2.5.深度拷贝
2.5.1 Object.assign
1.定义:将源对象(source)的所有可枚举属性,复制到目标对象(target)
2.用法:

合并多个对象
var target = { a: 1, b: 1 };
var source1 = { b: 2, c: 2 };
var source2 = { c: 3 };
Object.assign(target, source1, source2);
3.注意:
这个是伪深度拷贝,只能拷贝第一层

2.5.2 JSON.stringify
1.原理:是将对象转化为字符串,而字符串是简单数据类型

2.5.3 递归拷贝
function deepClone(source){
const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象
for(let keys in source){ // 遍历目标
if(source.hasOwnProperty(keys)){
if(source[keys] && typeof source[keys] === ‘object’){ // 如果值是对象,就递归一下
targetObj[keys] = source[keys].constructor === Array ? [] : {};
targetObj[keys] = deepClone(source[keys]);
}else{ // 如果不是,就直接赋值
targetObj[keys] = source[keys];
}
}
}
return targetObj;
}

2.6.数据拦截
定义:利用对象内置方法,设置属性,进而改变对象的属性值

2.6.1 Object.defineProterty
1.ES5出来的方法;
2.三个参数:对象(必填),属性值(必填),描述符(可选);
3.defineProterty的描述符属性

数据属性:value,writable,configurable,enumerable
访问器属性:get,set
注:不能同时设置value和writable,这两对属性是互斥的
4.拦截对象的两种情况:

let obj = {name:’’,age:’’,sex:’’ },
defaultName = [“这是姓名默认值1”,”这是年龄默认值1”,”这是性别默认值1”];
Object.keys(obj).forEach(key => {
Object.defineProperty(obj, key, {
get() {
return defaultName;
},
set(value) {
defaultName = value;
}
});
});

console.log(obj.name);
console.log(obj.age);
console.log(obj.sex);
obj.name = “这是改变值1”;
console.log(obj.name);
console.log(obj.age);
console.log(obj.sex);

let objOne={},defaultNameOne=”这是默认值2”;
Object.defineProperty(obj, ‘name’, {
get() {
return defaultNameOne;
},
set(value) {
defaultNameOne = value;
}
});
console.log(objOne.name);
objOne.name = “这是改变值2”;
console.log(objOne.name);
5.拦截数组变化的情况

let a={};
bValue=1;
Object.defineProperty(a,”b”,{
set:function(value){
bValue=value;
console.log(“setted”);
},
get:function(){
return bValue;
}
});
a.b;//1
a.b=[];//setted
a.b=[1,2,3];//setted
a.b[1]=10;//无输出
a.b.push(4);//无输出
a.b.length=5;//无输出
a.b;//[1,10,3,4,undefined];

结论:defineProperty无法检测数组索引赋值,改变数组长度的变化;
但是通过数组方法来操作可以检测到

6.存在的问题

不能监听数组索引赋值和改变长度的变化
必须深层遍历嵌套的对象,因为defineProterty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历,如果属性值也是对象那么需要深度遍历,显然能劫持一个完整的对象是更好的选择

2.6.2 proxy
1.ES6出来的方法,实质是对对象做了一个拦截,并提供了13个处理方法
13个方法详情请戳,阮一峰的proxy介绍

2.两个参数:对象和行为函数

let handler = {
get(target, key, receiver) {
console.log(“get”, key);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
console.log(“set”, key, value);
return Reflect.set(target, key, value, receiver);
}
};
let proxy = new Proxy(obj, handler);
proxy.name = “李四”;
proxy.age = 24;
3.问题和优点
reflect对象没有构造函数
可以监听数组索引赋值,改变数组长度的变化,
是直接监听对象的变化,不用深层遍历

2.6.3 defineProterty和proxy的对比
1.defineProterty是es5的标准,proxy是es6的标准;

2.proxy可以监听到数组索引赋值,改变数组长度的变化;

3.proxy是监听对象,不用深层遍历,defineProterty是监听属性;

3.利用defineProterty实现双向数据绑定(vue2.x采用的核心)
请戳,剖析Vue原理&实现双向绑定MVVM
4.利用proxy实现双向数据绑定(vue3.x会采用)

3.数组
数组基本上考察数组方法多一点,所以这里就单纯介绍常见的场景数组的方法,还有很多场景后续补充;
本文主要从应用来讲数组api的一些骚操作;
如一行代码扁平化n维数组、数组去重、求数组最大值、数组求和、排序、对象和数组的转化等;
上面这些应用场景你可以用一行代码实现?

3.1 扁平化n维数组
1.终极篇

[1,[2,3]].flat(2) //[1,2,3]
[1,[2,3,[4,5]].flat(3) //[1,2,3,4,5]
[1,[2,3,[4,5]]].toString() //‘1,2,3,4,5’
[1[2,3,[4,5[…]].flat(Infinity) //[1,2,3,4…n]
Array.flat(n)是ES10扁平数组的api,n表示维度,n值为Infinity时维度为无限大

2.开始篇

function flatten(arr) {
while(arr.some(item=>Array.isArray(item))) {
arr = [].concat(…arr);
}
return arr;
}
flatten([1,[2,3]]) //[1,2,3]
flatten([1,[2,3,[4,5]]) //[1,2,3,4,5]
实质是利用递归和数组合并方法concat实现扁平

3.2 去重
1.终极篇

Array.from(new Set([1,2,3,3,4,4])) //[1,2,3,4]
[…new Set([1,2,3,3,4,4])] //[1,2,3,4]
set是ES6新出来的一种一种定义不重复数组的数据类型
Array.from是将类数组转化为数组
…是扩展运算符,将set里面的值转化为字符串
2.开始篇

Array.prototype.distinct = nums => {
const map = {}
const result = []
for (const n of nums) {
if (!(n in map)) {
map[n] = 1
result.push(n)
}
}
return result
}
[1,2,3,3,4,4].distinct(); //[1,2,3,4]
取新数组存值,循环两个数组值相比较

3.3排序
1.终极篇

[1,2,3,4].sort((a, b) => a - b); // [1, 2,3,4],默认是升序
[1,2,3,4].sort((a, b) => b - a); // [4,3,2,1] 降序
sort是js内置的排序方法,参数为一个函数
2.开始篇
冒泡排序:

Array.prototype.bubleSort=function () {
let arr=this,
len = arr.length;
for (let outer = len; outer >= 2; outer–) {
for (let inner = 0; inner <= outer - 1; inner++) {
if (arr[inner] > arr[inner + 1]) {
//升序
[arr[inner], arr[inner + 1]] = [arr[inner + 1], arr[inner]];
console.log([arr[inner], arr[inner + 1]]);
}
}
}
return arr;
}
[1,2,3,4].bubleSort() //[1,2,3,4]
选择排序

Array.prototype.selectSort=function () {
    let arr=this,
        len = arr.length;
    for (let i = 0, len = arr.length; i < len; i++) {
for (let j = i, len = arr.length; j < len; j++) {
  if (arr[i] > arr[j]) {
    [arr[i], arr[j]] = [arr[j], arr[i]];
  }
}

}
return arr;
}
[1,2,3,4].selectSort() //[1,2,3,4]
3.4最大值
1.终极篇

Math.max(…[1,2,3,4]) //4
Math.max.apply(this,[1,2,3,4]) //4
[1,2,3,4].reduce( (prev, cur,curIndex,arr)=> {
return Math.max(prev,cur);
},0) //4
Math.max()是Math对象内置的方法,参数是字符串;
reduce是ES5的数组api,参数有函数和默认初始值;
函数有四个参数,pre(上一次的返回值),cur(当前值),curIndex(当前值索引),arr(当前数组)

2.开始篇
先排序再取值

3.5求和
1.终极篇

[1,2,3,4].arr.reduce(function (prev, cur) {
return prev + cur;
},0) //10
2.开始篇

function sum(arr) {
var len = arr.length;
if(len == 0){
return 0;
} else if (len == 1){
return arr[0];
} else {
return arr[0] + sum(arr.slice(1));
}
}
sum([1,2,3,4]) //10
利用slice截取改变数组,再利用递归求和

3.6合并
1.终极篇

[1,2,3,4].concat([5,6]) //[1,2,3,4,5,6]
[…[1,2,3,4],…[4,5]] //[1,2,3,4,5,6]
let arrA = [1, 2], arrB = [3, 4]
Array.prototype.push.apply(arrA, arrB))//arrA值为[1,2,3,4]
2.开始篇

let arr=[1,2,3,4];
[5,6].map(item=>{
arr.push(item)
})
//arr值为[1,2,3,4,5,6],注意不能直接return出来,return后只会返回[5,6]
3.7判断是否包含值
1.终极篇

[1,2,3].includes(4) //false
[1,2,3].indexOf(4) //-1 如果存在换回索引
[1, 2, 3].find((item)=>item===3)) //3 如果数组中无值返回undefined
[1, 2, 3].findIndex((item)=>item===3)) //2 如果数组中无值返回-1
includes(),find(),findIndex()是ES6的api

2.开始篇

[1,2,3].some(item=>{
return item===3
}) //true 如果不包含返回false
3.8类数组转化
1.终极篇

Array.prototype.slice.call(arguments) //arguments是类数组(伪数组)
Array.prototype.slice.apply(arguments)
Array.from(arguments)
[…arguments]
类数组:表示有length属性,但是不具备数组的方法
call,apply:是改变slice里面的this指向arguments,所以arguments也可调用数组的方法
Array.from是将类似数组或可迭代对象创建为数组
…是将类数组扩展为字符串,再定义为数组

2.开始篇

Array.prototype.slice = function(start,end){
var result = new Array();
start = start || 0;
end = end || this.length; //this指向调用的对象,当用了call后,能够改变this的指向,也就是指向传进来的对象,这是关键
for(var i = start; i < end; i++){
result.push(this[i]);
}
return result;
}
3.9每一项设置值
1.终极篇

[1,2,3].fill(false) //[false,false,false]
fill是ES6的方法
2.开始篇

[1,2,3].map(() => 0)
3.10每一项是否满足
[1,2,3].every(item=>{return item>2}) //false
every是ES5的api,每一项满足返回 true

3.11有一项满足
[1,2,3].some(item=>{return item>2}) //true
some是ES5的api,有一项满足返回 true

3.12.过滤数组
[1,2,3].filter(item=>{return item>2}) //[3]
filter是ES5的api,返回满足添加的项的数组

3.13对象和数组转化
Object.keys({name:’张三’,age:14}) //[‘name’,’age’]
Object.values({name:’张三’,age:14}) //[‘张三’,14]
Object.entries({name:’张三’,age:14}) //[[name,’张三’],[age,14]]
Object.fromEntries([name,’张三’],[age,14]) //ES10的api,Chrome不支持 , firebox输出{name:’张三’,age:14}
3.14 对象数组
[{count:1},{count:2},{count:3}].reduce((p, e)=>p+(e.count), 0)
4.数据结构篇
数据结构是计算机存储、组织数据的方式,算法是系统描述解决问题的策略。了解基本的数据结构和算法可以提高代码的性能和质量。
也是程序猿进阶的一个重要技能。
手撸代码实现栈,队列,链表,字典,二叉树,动态规划和贪心算法
4.1 栈
栈的特点:先进后出

class Stack {
constructor() {
this.items = [];
}

// 入栈
push(element) {
  this.items.push(element);
}

// 出栈
pop() {
  return this.items.pop();
}

// 末位
get peek() {
  return this.items[this.items.length - 1];
}

// 是否为空栈
get isEmpty() {
  return !this.items.length;
}

// 长度
get size() {
  return this.items.length;
}

// 清空栈
clear() {
  this.items = [];
}

}

// 实例化一个栈
const stack = new Stack();
console.log(stack.isEmpty); // true

// 添加元素
stack.push(5);
stack.push(8);

// 读取属性再添加
console.log(stack.peek); // 8
stack.push(11);
console.log(stack.size); // 3
console.log(stack.isEmpty); // false
4.2 队列
队列:先进先出

class Queue {
constructor(items) {
this.items = items || [];
}

enqueue(element) {
  this.items.push(element);
}

dequeue() {
  return this.items.shift();
}

front() {
  return this.items[0];
}

clear() {
  this.items = [];
}

get size() {
  return this.items.length;
}

get isEmpty() {
  return !this.items.length;
}

print() {
  console.log(this.items.toString());
}

}

const queue = new Queue();
console.log(queue.isEmpty); // true

queue.enqueue(“John”);
queue.enqueue(“Jack”);
queue.enqueue(“Camila”);
console.log(queue.size); // 3
console.log(queue.isEmpty); // false
queue.dequeue();
queue.dequeue();

4.3 链表
链表:存贮有序元素的集合,
但是不同于数组,每个元素是一个存贮元素本身的节点和指向下一个元素引用组成
要想访问链表中间的元素,需要从起点开始遍历找到所需元素

class Node {
constructor(element) {
this.element = element;
this.next = null;
}
}

// 链表
class LinkedList {
constructor() {
this.head = null;
this.length = 0;
}

// 追加元素
append(element) {
  const node = new Node(element);
  let current = null;
  if (this.head === null) {
    this.head = node;
  } else {
    current = this.head;
    while (current.next) {
      current = current.next;
    }
    current.next = node;
  }
  this.length++;
}

// 任意位置插入元素
insert(position, element) {
  if (position >= 0 && position <= this.length) {
    const node = new Node(element);
    let current = this.head;
    let previous = null;
    let index = 0;
    if (position === 0) {
      this.head = node;
    } else {
      while (index++ < position) {
        previous = current;
        current = current.next;
      }
      node.next = current;
      previous.next = node;
    }
    this.length++;
    return true;
  }
  return false;
}

// 移除指定位置元素
removeAt(position) {
  // 检查越界值
  if (position > -1 && position < length) {
    let current = this.head;
    let previous = null;
    let index = 0;
    if (position === 0) {
      this.head = current.next;
    } else {
      while (index++ < position) {
        previous = current;
        current = current.next;
      }
      previous.next = current.next;
    }
    this.length--;
    return current.element;
  }
  return null;
}

// 寻找元素下标
findIndex(element) {
  let current = this.head;
  let index = -1;
  while (current) {
    if (element === current.element) {
      return index + 1;
    }
    index++;
    current = current.next;
  }
  return -1;
}

// 删除指定文档
remove(element) {
  const index = this.findIndex(element);
  return this.removeAt(index);
}

isEmpty() {
  return !this.length;
}

size() {
  return this.length;
}

// 转为字符串
toString() {
  let current = this.head;
  let string = "";
  while (current) {
    string += ` ${current.element}`;
    current = current.next;
  }
  return string;
}

}
const linkedList = new LinkedList();

console.log(linkedList);
linkedList.append(2);
linkedList.append(6);
linkedList.append(24);
linkedList.append(152);

linkedList.insert(3, 18);
console.log(linkedList);
console.log(linkedList.findIndex(24));

4.4 字典
字典:类似对象,以key,value存贮值

class Dictionary {
constructor() {
this.items = {};
}

set(key, value) {
  this.items[key] = value;
}

get(key) {
  return this.items[key];
}

remove(key) {
  delete this.items[key];
}

get keys() {
  return Object.keys(this.items);
}

get values() {
  /*
也可以使用ES7中的values方法
return Object.values(this.items)
*/

  // 在这里我们通过循环生成一个数组并输出
  return Object.keys(this.items).reduce((r, c, i) => {
    r.push(this.items[c]);
    return r;
  }, []);
}

}
const dictionary = new Dictionary();
dictionary.set(“Gandalf”, “gandalf@email.com“);
dictionary.set(“John”, “johnsnow@email.com“);
dictionary.set(“Tyrion”, “tyrion@email.com“);

console.log(dictionary);
console.log(dictionary.keys);
console.log(dictionary.values);
console.log(dictionary.items);

4.5 二叉树
特点:每个节点最多有两个子树的树结构

class NodeTree {
constructor(key) {
this.key = key;
this.left = null;
this.right = null;
}
}

class BinarySearchTree {
constructor() {
this.root = null;
}

insert(key) {
  const newNode = new NodeTree(key);
  const insertNode = (node, newNode) => {
    if (newNode.key < node.key) {
      if (node.left === null) {
        node.left = newNode;
      } else {
        insertNode(node.left, newNode);
      }
    } else {
      if (node.right === null) {
        node.right = newNode;
      } else {
        insertNode(node.right, newNode);
      }
    }
  };
  if (!this.root) {
    this.root = newNode;
  } else {
    insertNode(this.root, newNode);
  }
}

//访问树节点的三种方式:中序,先序,后序
inOrderTraverse(callback) {
  const inOrderTraverseNode = (node, callback) => {
    if (node !== null) {
      inOrderTraverseNode(node.left, callback);
      callback(node.key);
      inOrderTraverseNode(node.right, callback);
    }
  };
  inOrderTraverseNode(this.root, callback);
}

min(node) {
  const minNode = node => {
    return node ? (node.left ? minNode(node.left) : node) : null;
  };
  return minNode(node || this.root);
}

max(node) {
  const maxNode = node => {
    return node ? (node.right ? maxNode(node.right) : node) : null;
  };
  return maxNode(node || this.root);
}

}
const tree = new BinarySearchTree();
tree.insert(11);
tree.insert(7);
tree.insert(5);
tree.insert(3);
tree.insert(9);
tree.insert(8);
tree.insert(10);
tree.insert(13);
tree.insert(12);
tree.insert(14);
tree.inOrderTraverse(value => {
console.log(value);
});

console.log(tree.min());
console.log(tree.max());

5.算法篇
5.1 冒泡算法
冒泡排序,选择排序,插入排序,此处不做赘述,请戳 排序

5.2 斐波那契
特点:第三项等于前面两项之和

function fibonacci(num) {
if (num === 1 || num === 2) {
return 1
}
return fibonacci(num - 1) + fibonacci(num - 2)
}
5.3 动态规划
特点:通过全局规划,将大问题分割成小问题来取最优解
案例:最少硬币找零
美国有以下面额(硬币):d1=1, d2=5, d3=10, d4=25
如果要找36美分的零钱,我们可以用1个25美分、1个10美分和1个便士( 1美分)

class MinCoinChange {

constructor(coins) {
this.coins = coins
this.cache = {}
}

makeChange(amount) {
if (!amount) return []
if (this.cache[amount]) return this.cache[amount]
let min = [], newMin, newAmount
this.coins.forEach(coin => {
newAmount = amount - coin
if (newAmount >= 0) {
newMin = this.makeChange(newAmount)
}
if (newAmount >= 0 &&
(newMin.length < min.length - 1 || !min.length) &&
(newMin.length || !newAmount)) {
min = [coin].concat(newMin)
}
})
return (this.cache[amount] = min)
}
}

const rninCoinChange = new MinCoinChange([1, 5, 10, 25])
console.log(rninCoinChange.makeChange(36))
// [1, 10, 25]
const minCoinChange2 = new MinCoinChange([1, 3, 4])
console.log(minCoinChange2.makeChange(6))
// [3, 3]
5.4 贪心算法
特点:通过最优解来解决问题
用贪心算法来解决2.3中的案例

class MinCoinChange2 {

constructor(coins) {
this.coins = coins
}

makeChange(amount) {
const change = []
let total = 0
this.coins.sort((a, b) => a < b).forEach(coin => {
if ((total + coin) <= amount) {
change.push(coin)
total += coin
}
})
return change
}
}
const rninCoinChange2 = new MinCoinChange2 ( [ 1, 5, 10, 25])
console.log (rninCoinChange2. makeChange (36))
6 设计模式
设计模式如果应用到项目中,可以实现代码的复用和解耦,提高代码质量。 本文主要介绍14种设计模式
写UI组件,封装框架必备
6.1 简单工厂模式
1.定义:又叫静态工厂方法,就是创建对象,并赋予属性和方法
2.应用:抽取类相同的属性和方法封装到对象上
3.代码:

let UserFactory = function (role) {

function User(opt) {
this.name = opt.name;
this.viewPage = opt.viewPage;
}
switch (role) {
case ‘superAdmin’:
return new User(superAdmin);
break;
case ‘admin’:
return new User(admin);
break;
case ‘user’:
return new User(user);
break;
default:
throw new Error(‘参数错误, 可选参数:superAdmin、admin、user’)
}
}

//调用
let superAdmin = UserFactory(‘superAdmin’);
let admin = UserFactory(‘admin’)
let normalUser = UserFactory(‘user’)
//最后得到角色,可以调用
6.2工厂方法模式
1.定义:对产品类的抽象使其创建业务主要负责用于创建多类产品的实例
2.应用:创建实例
3.代码:

var Factory=function(type,content){
if(this instanceof Factory){
var s=new thistype;
return s;
}else{
return new Factory(type,content);
}
}

//工厂原型中设置创建类型数据对象的属性
Factory.prototype={
Java:function(content){
console.log(‘Java值为’,content);
},
PHP:function(content){
console.log(‘PHP值为’,content);
},
Python:function(content){
console.log(‘Python值为’,content);
},
}

//测试用例
Factory(‘Python’,’我是Python’);
6.3原型模式
1.定义:设置函数的原型属性
2.应用:实现继承
3.代码:

function Animal (name) {
// 属性
this.name = name || ‘Animal’;
// 实例方法
this.sleep = function(){
console.log(this.name + ‘正在睡觉!’);
}
}
// 原型方法
Animal.prototype.eat = function(food) {
console.log(this.name + ‘正在吃:’ + food);
};

function Cat(){
}
Cat.prototype = new Animal();
Cat.prototype.name = ‘cat’;

// Test Code
var cat = new Cat();
console.log(cat.name);//cat
console.log(cat.eat(‘fish’));//cat正在吃:fish undefined
console.log(cat.sleep());//cat正在睡觉! undefined
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true
6.4单例模式
1.定义:只允许被实例化依次的类
2.应用:提供一个命名空间
3.代码:

let singleCase = function(name){
this.name = name;
};
singleCase.prototype.getName = function(){
return this.name;
}
// 获取实例对象
let getInstance = (function() {
var instance = null;
return function(name) {
if(!instance) {//相当于一个一次性阀门,只能实例化一次
instance = new singleCase(name);
}
return instance;
}
})();
// 测试单体模式的实例,所以one===two
let one = getInstance(“one”);
let two = getInstance(“two”);
6.5外观模式
1.定义:为子系统中的一组接口提供一个一致的界面
2.应用:简化复杂接口
3.代码:
外观模式

6.6适配器模式
1.定义:将一个接口转换成客户端需要的接口而不需要去修改客户端代码,使得不兼容的代码可以一起工作
2.应用:适配函数参数
3.代码:
适配器模式

6.7装饰者模式
1.定义:不改变原对象的基础上,给对象添加属性或方法
2.代码

let decorator=function(input,fn){
//获取事件源
let input=document.getElementById(input);
//若事件源已经绑定事件
if(typeof input.onclick==’function’){
//缓存事件源原有的回调函数
let oldClickFn=input.onclick;
//为事件源定义新事件
input.onclick=function(){
//事件源原有回调函数
oldClickFn();
//执行事件源新增回调函数
fn();
}
}else{
//未绑定绑定
input.onclick=fn;
}
}

//测试用例
decorator(‘textInp’,function(){
console.log(‘文本框执行啦’);
})
decorator(‘btn’,function(){
console.log(‘按钮执行啦’);
})
6.8桥接模式
1.定义:将抽象部分与它的实现部分分离,使它们都可以独立地变化
2.代码
桥接模式

6.9模块方法模式
1.定义:定义一个模板,供以后传不同参数调用
2.代码:
模块方法模式

6.10.观察者模式
1.作用:解决类与对象,对象与对象之间的耦合
2.代码:

let Observer=
(function(){
let _message={};
return {
//注册接口,
//1.作用:将订阅者注册的消息推入到消息队列
//2.参数:所以要传两个参数,消息类型和处理动作,
//3.消息不存在重新创建,存在将消息推入到执行方法

  regist:function(type,fn){
    //如果消息不存在,创建
    if(typeof _message[type]==='undefined'){
      _message[type]=[fn];
    }else{
      //将消息推入到消息的执行动作
      _message[type].push(fn);
    }
  },

  //发布信息接口
    //1.作用:观察这发布消息将所有订阅的消息一次执行
    //2.参数:消息类型和动作执行传递参数
    //3.消息类型参数必须校验
  fire:function(type,args){
    //如果消息没有注册,则返回
    if(!_message[type]) return;
      //定义消息信息
      var events={
        type:type, //消息类型
        args:args||{} //消息携带数据
      },
      i=0,
      len=_message[type].length;
      //遍历消息
      for(;i<len;i++){
        //依次执行注册消息
        _message[type][i].call(this,events);
      }
  },

  //移除信息接口
    //1.作用:将订阅者注销消息从消息队列清除
    //2.参数:消息类型和执行的动作
    //3.消息参数校验
  remove:function(type,fn){
    //如果消息动作队列存在
    if(_message[type] instanceof Array){
      //从最后一个消息动作序遍历
      var i=_message[type].length-1;
      for(;i>=0;i--){
        //如果存在该动作在消息队列中移除
        _message[type][i]===fn&&_message[type].splice(i,1);
      }
    }
  }
}

})()

//测试用例
//1.订阅消息
Observer.regist(‘test’,function(e){
console.log(e.type,e.args.msg);
})

//2.发布消息
Observer.fire(‘test’,{msg:’传递参数1’});
Observer.fire(‘test’,{msg:’传递参数2’});
Observer.fire(‘test’,{msg:’传递参数3’});
6.11状态模式
1.定义:一个对象状态改变会导致行为变化
2.作用:解决复杂的if判断
3.代码
状态模式

6.12策略模式
1.定义:定义了一系列家族算法,并对每一种算法单独封装起来,让算法之间可以相互替换,独立于使用算法的客户
2.代码
策略模式

6.13.访问模式
1.定义:通过继承封装一些该数据类型不具备的属性,
2.作用:让对象具备数组的操作方法
3.代码:
访问者模式

6.14中介者模式
1.定义:设置一个中间层,处理对象之间的交互
2.代码:
中介者模式

  1. HTTP
    1.1 什么是 HTTP
    HTTP 是一个连接客户端,网关和服务器的一个协议。

7.2 特点
支持客户/服务器模式:可以连接客户端和服务端;
简单快速:请求只需传送请求方法,路径和请求主体;
灵活:传输数据类型灵活;
无连接:请求结束立即断开;
无状态:无法记住上一次请求。

7.3 怎么解决无状态和无连接
无状态:HTTP 协议本身无法解决这个状态,只有通过 cookie 和 session 将状态做贮存,常见的场景是登录状态保持;

无连接:可以通过自身属性 Keep-Alive。

7.4 请求过程
HTTP(S) 请求地址 → DNS 解析 → 三次握手 → 发送请求 → 四次挥手

三次握手过程(图片来源 CSDN)
3 次握手.jpg

在这里插入图片描述

四次挥手过程(图片来源 CSDN)
image

在这里插入图片描述

7.5 HTTP 0.9~3.0 对比
7.5.1 HTTP 0.9
只允许客户端发送 GET 这一种请求;
且不支持请求头,协议只支持纯文本;
无状态性,每个访问独立处理,完成断开;
无状态码。

7.5.2 HTTP 1.0
有身份认证,三次握手;
请求与响应支持头域;
请求头内容;

属性名 含义
Accept 可接受的 MIME 类型
Accept-Encoding 数据可解码的格式
Accept-Language 可接受语言
Connection 值 keep-alive 是长连接
Host 主机和端口
Pragma 是否缓存,指定 no-cache 返回刷新
Referer 页面路由
If-Modified-Since 值为时间
响应头内容;

属性名 含义
Connection 值 keep-alive 是长连接
Content-Type 返回文档类型,常见的值有 text/plain,text/html,text/json
Date 消息发送的时间
Server 服务器名字
Last-Modified 值为时间,s 返回的最后修改时间
Expires 缓存过期时间,b 和 s 时间做对比
注意

expires 是响应头内容,返回一个固定的时间,缺陷是时间到了服务器要重新设置。
请求头中如果有 If-Modified-Since,服务器会将时间与 last-modified 对比,相同返回 304。
响应对象以一个响应状态行开始
响应对象不只限于超文本
支持 GET、HEAD、POST 方法
有状态码
支持长连接(但默认还是使用短连接)、缓存机制以及身份认证。

7.5.3 HTTP 1.1
请求头增加 Cache-Control

属性名 含义
Cache-Control 在1.1 引入的方法,指定请求和响应遵循的缓存机制,值有:public(b 和 s 都缓存),private(b 缓存),no-cache(不缓存),no-store(不缓存),max-age(缓存时间,s 为单位),min-fresh(最小更新时间),max-age=3600
If-None-Match 上次请求响应头返回的 etag 值响应头增加 Cache-Control,表示所有的缓存机制是否可以缓存及哪种类型 etag 返回的哈希值,第二次请求头携带去和服务器值对比
注意

Cache-Control 的 max-age 返回是缓存的相对时间
Cache-Control 优先级比 expires 高
缺点:不能第一时间拿到最新修改文件

7.5.4 HTTP 2.0
采用二进制格式传输
多路复用,其实就是将请求数据分成帧乱序发送到 TCP 中。TCP 只能有一个 steam,所以还是会阻塞
报头压缩
服务器推送主动向 B 端发送静态资源,避免往返延迟。

7.5.5 HTTP 3.0
1.是基于 QUIC 协议,基于 UDP
2.特点:
自定义连接机制:TCP 以 IP/端口标识,变化重新连接握手,UDP 是一 64 位 ID 标识,是无连接;
自定义重传机制:TCP 使用序号和应答传输,QUIC 是使用递增序号传输; 无阻塞的多路复用:同一条 QUIC 可以创建多个 steam。

7.5.6 HTTPS
1.https 是在 http 协议的基础上加了个 SSL;
2.主要包括:握手(凭证交换和验证)和记录协议(数据进行加密)。

7.5.7 缓存
1.按协议分:协议层缓存和非 http 协议缓存:
1.1协议层缓存:利用 http 协议头属性值设置;
1.2非协议层缓存:利用 meta 标签的 http-equiv 属性值 Expires,set-cookie。

2.按缓存分:强缓存和协商缓存:
2.1强缓存:利用 cache-control 和 expires 设置,直接返回一个过期时间,所以在缓存期间不请求,If-modify-since;
2.2协商缓存:响应头返回 etag 或 last-modified 的哈希值,第二次请求头 If-none-match 或 IF-modify-since 携带上次哈希值,一致则返回 304。

3.协商缓存对比: etag 优先级高于 last-modified;
4.etag 精度高,last-modified 精度是 s,1s 内 etag 修改多少次都会被记录; last-modified 性能好,etag 要得到 hash 值。

5.浏览器读取缓存流程:
会先判断强缓存;再判断协商缓存 etag(last-modified)是否存在;
存在利用属性 If-None-match(If-Modified-since)携带值;
请求服务器,服务器对比 etag(last-modified),生效返回 304。

F5 刷新会忽略强缓存不会忽略协商缓存,ctrl+f5 都失效

7.5.8 状态码
序列 详情
1XX(通知)
2XX(成功) 200(成功)、201(服务器创建)、202(服务器接收未处理)、203(非授权信息)、204(未返回内容)、205(重置内容)、206(部分内容)
3XX(重定向) 301(永久移动)、302(临时移动)、303(查看其他位置)、304(未修改)、305(使用代理)、307(临时重定向)
4XX(客户端错误) 400(错误请求)、401(未授权)、403(禁止)、404(未找到)、405(方法禁用)、406(不接受)、407(需要代理授权)
5XX(服务器错误) 500(服务器异常)、501(尚未实施)、502(错误网关)、503(服务不可用)、504(网关超时)、505(HTTP 版本不受支持)
7.5.9 浏览器请求分析
在这里插入图片描述

7.5.10 总结
协议

版本 内容
http0.9 只允许客户端发送 GET 这一种请求;且不支持请求头,协议只支持纯文本;无状态性,每个访问独立处理,完成断开;无状态码
http1.0 解决 0.9 的缺点,增加 If-modify-since(last-modify)和 expires 缓存属性
http1.x 增加 cache-control 和 If-none-match(etag)缓存属性
http2.0 采用二进制格式传输;多路复用;报头压缩;服务器推送
http3.0 采用 QUIC 协议,自定义连接机制;自定义重传机制;无阻塞的多路复用
缓存

类型 特性
强缓存 通过 If-modify-since(last-modify)、expires 和 cache-control 设置,属性值是时间,所以在时间内不用请求
协商缓存 通过 If-none-match(etag)设置,etag 属性是哈希值,所以要请求和服务器值对比
8.总结
这只是 JS 原生梳理,后续会再出 react,node,小程序相关的梳理;
原创码字不易,欢迎 star!

自制ant.design表格组件教程

前言

在实际项目中我们时常有可能需要Ip地址输入框,键值对输入框这样的复杂输入组件。但是ant design
官方并没有给出这样的组件,文档中也没有提到应该如何写出这样的组件。

我在研究了一番实现了该功能,在这里与大家分享一下,希望可以方便到后来人。

源码分享

对于不需要教程的同学可以直接查看源码:

教程-编写一个IP输入组件

第一步:完全受控组件与ant design表格组件联动

首先我们需要建立一个完全受控的输入组件,并且该组件可以嵌套进ant design的表格组件当中并正常
运行。

&#123;getFieldDecorator('ip')(
  <IpInput />
)&#125;

IpInput组件代码:

import React, &#123;Component&#125; from 'react';
import &#123;Input&#125; from "antd";

export default class IpInput extends Component &#123;
  constructor(props) &#123;
    super(props);

    this.state = &#123;
      ip: '',
    &#125;
  &#125;


  hdlInputChange = (e) => &#123;
    this.setState(&#123;ip: e.target.value&#125;)
    this.props.onChange(&#123;target: &#123;value: e.target.value&#125;&#125;)
  &#125;

  render() &#123;
    return (
      <div>
        <Input value=&#123;this.state.ip&#125; onChange=&#123;this.hdlInputChange&#125; />
      </div>
    );
  &#125;
&#125;

在CodeSandbox中浏览

第二步:在IpInput组件中建立多个Input,并联动

重点是输出值的拆分与聚合

hdlInputChange = (e, block) => &#123;
    let ipAdressArr = [this.state.ablock, this.state.bblock, this.state.cblock, this.state.dblock]
    let value = e.target.value
    this.setState(&#123;[`$&#123;block&#125;block`]: value&#125;)
    ipAdressArr[blockDict[block]] = value
    this.props.onChange(&#123;target: &#123;value: ipAdressArr.join('.')&#125;&#125;)
  &#125;

详细代码请看:CodeSandbox

到这一步一个基本的ip Input输入组件已经成型,但是难点部分还未开始。

第三步:校验规则处理

如果现在我们就开始正常使用该组件,4个输入框的报错样式必定是同时出现的。一个是红框的话,其他
三个也必定是红框状态。这是ant design的程序设计,FormItem的报错样式由外层容器通过CSS控制。

上面这段话不容易理解的话自己试一下就明白了。

也就是说我们需要单独控制4个Input框的校验状态,已经有数字的应为正确状态,没有数字的校验时显示为
红色。且这个校验行为要能够被ant designForm组件触发。

首先我们建立一个类似ant design原生的Form.Item组件用来包裹每个Input并负责校验逻辑。

class FromItemValidatorWarpper extends Component &#123;
  constructor(props) &#123;
    super(props)

    this.state = &#123;
      validateStatus: ''
    &#125;
  &#125;

  componentDidMount () &#123;&#125;

  componentDidUpdate (prevProps, prevState) &#123;
    if(this.props.valiating && prevProps.validating !== this.props.valiating)&#123;
      // form 触发的 validating

      let validateStatus = ''
      if(!this.getChildProp("value"))&#123;
        validateStatus = 'error'
      &#125;
      if(this.state.validateStatus !== validateStatus)&#123;
        this.setState(&#123;validateStatus&#125;)
      &#125;

    &#125;
  &#125;

  getChildProp(prop) &#123;
    const child = this.props.children
    return child && child.props && child.props[prop];
  &#125;

  render() &#123;
    return (
      <Form.Item
        validateStatus=&#123;this.state.validateStatus&#125;
      >
        &#123;this.props.children&#125;
      </Form.Item>
    )
  &#125;
&#125;

然后用该组件包裹IpInput组件

<FromItemValidatorWarpper valiating=&#123;this.state.valiating&#125;>
  <Input value=&#123;this.state.dblock&#125; onChange=&#123;(e)=>&#123;this.hdlInputChange(e, 'd')&#125;&#125; />
</FromItemValidatorWarpper>

IpInput组件中建立valiating属性,并使其和外层ant design Form组件的validator联动。

  componentDidUpdate(prevProps) &#123;
    let dataField = this.props["data-__field"]

    if(dataField.errors && !prevProps["data-__field"].errors)&#123;
      // valitor 函数触发报错
      this.setState(&#123;valiating: true&#125;)
    &#125;
    if(!dataField.errors && prevProps["data-__field"].errors)&#123;
      // valitor 函数触发清除报错
      this.setState(&#123;valiating: false&#125;)
    &#125;
  &#125;

最后在使用IpInput的组件位置添加一个正则校验。

  &#123;getFieldDecorator('ip', &#123;
    rules: [
      &#123;required: true, message: 'Please input your ip!'&#125;,
      &#123;
        pattern: /((?:(?:25[0-5]|2[0-4]\d|((1\d&#123;2&#125;)|([1-9]?\d)))\.)&#123;3&#125;(?:25[0-5]|2[0-4]\d|((1\d&#123;2&#125;)|([1-9]?\d))))/,
        message: 'Please finish your ip!'
      &#125;
    ],
  &#125;)(
    <IpInput/>
  )&#125;

请在CodeSandbox中查看效果

OK,我们现在只差最后一步了,即在外层使用组件时使用setFieldsValue初始化组件值的功能。

第四步:setFieldsValue方法支持

componentDidUpdate方法中加入一段代码判断组件上级value是否和内部value相同,不同且没有进行
初始化时进行初始化。

if(dataField.value !== [this.state.ablock, this.state.bblock, this.state.cblock, this.state.dblock].join('.') && !this.initialized)&#123;
  this.initialized = true
  let ipAdressArr = dataField.value.split('.')
  this.setState(&#123;
    ablock: ipAdressArr[0],
    bblock: ipAdressArr[1],
    cblock: ipAdressArr[2],
    dblock: ipAdressArr[3],
  &#125;)
&#125;

请在CodeSandbox中查看效果

结尾

以上,一个IP输入组件基本完成了。细节包括样式还有很多可以优化的地方,我在这里就不继续了。maybe
以后再来更新。

我把最后的完整代码放在github上,有任何建议可
以给我提交issue或下面评论回复,谢谢。

Grafana调研和Superset对比

Grafana调研

安装

使用Docker安装

注意事项:

  1. 启动时需要设置数据库,否则Grafana会使用嵌入式数据库sqlite3。

支持图表

官方文档,只有四种类型分别为Graph,Singlestat,Table,Heatmap

项目引入

方式一:Dashboard snapshot

分享整个Dashboard,不需要额外的权限设置。

方式二:Share Panel-Embed Panel

设置AUTH_ANONYMOUS_ENABLEDtrue允许anonymous访问。即可单独分享每一个graph panel

Dashboard市场

Grafana有把Dashboard配置通过JSON文件导入/导出功能,同时官方建立了一个在线的市场,方便大家交流。但是因为数据源的差异性,实际用途不大。

目前市场是下载比较的多的配置,是一些基于通用数据源的配置。例如prometheus,实际上Prometheus也把Grafana当作自己的图形解决方案。

插件安装

Grafana提供了通过插件扩展控能,例如安装饼图插件支持饼图设置,安装数据源插件增加支持的数据源。

代码分析

进行中

MongoDB连接

待测试,MongoDB connect BI插件理论上可行

对比分析

Grafana star 29.2k+ 有企业版(收费)Live Demo

Superset star 24.7k+

开源许可证都为apache-2.0

Superset

优点:
采用D3图表库初始支持的图表类型就十分丰富

Grafana

优点:

1. 支持插件方便社区为其提供扩展能力  
2. 项目引入简单,不需要二次开发

总结&建议

经过初步的了解,我的建议是使用Grafana。原因有以下几点:

  1. 目前的流行程度Grafana略微占优,正在使用的企业数量似乎更多(superset没有找到相关数据)。
  2. Grafana有独立的公司在开发维护,并且同时就在提供商业服务。商业成熟度上远远领先Superset。
  3. Grafana的插件设计模式更加优秀,方便社区提供能量。

Grafana的潜在缺点:

没有选择类似D3的库作为图形上的支持,我觉得有长期的运营打算在其中,也许是在为今后的按图表插件来收费的模式做准备。这样的模式没什么不好,但是长期来看通过自研要做出和D3同样水平的图表库需要很长的时间,最终能否达到同样的水平很难说。

csv文件通过node.js转存postresql数据库

排坑

分享一下代码

const config = require('../config')
const pglink = config.pg
const csv = require('csvtojson')
const fs = require('fs')
const &#123;Client&#125; = require('pg')
const copyFrom = require('pg-copy-streams').from

module.exports = (targetTable, inputFile, columnStr) => &#123;
  console.log('开始导入')
  // Connecting to Database
  const client = new Client(&#123;
    connectionString: pglink,
  &#125;)
  client.connect()

  var stream = client.query(copyFrom(`COPY $&#123;targetTable&#125; ($&#123;columnStr&#125;) FROM STDIN CSV`));

  var fileStream = fs.createReadStream(inputFile)

  fileStream.on('error', (error) =>&#123;
    console.log(`Error in creating read stream $&#123;error&#125;`)
  &#125;)
  stream.on('error', (error) => &#123;
    console.log(`Error in creating stream $&#123;error&#125;`)
  &#125;)
  stream.on('end', () => &#123;
    console.log(`Completed loading data into $&#123;targetTable&#125;`)
    client.end()
  &#125;)
  fileStream.pipe(stream);
&#125;

调用代码示例

const copyToPg =  require('/* 上面的代码 */')
const path = require('path')

const inputFile = path.join(__dirname, '/csv/country.csv')

copyToPg('Country', path.join(__dirname, '/csv/country.csv'),'country, code, continent')

这里有两个坑需要特别说明targetTable这里是区分大小写的,而且只支持小写。。。

CSV需要使用utf8格式,不然会有中文乱码问题。但是你使用Excel是改不了utf8的,我的操作流程是使用记事本打开csv文件再改变编码转存。
之后再使用Excel编辑。

参考文档

官方说明档:sql-copy

How to do postgresql bulk insert with node-pg-copy-streams

ajax接口接收文件排坑(二)

错误处理问题

假设后端传回了一段包含错误message字段的JSON文件,如果将responseType 设置为 blob 将会导致无法解析后端传回的错误数据。

解决方案是将responseType 设置为 arraybuffer,根据http状态码来判断接口是否报错。假设接口返回400则将返回数据由arraybuffer
重新解析为String 或 Object 等我们需要的数据类型。

解析代码如下

function errorCallback(response) &#123;
  var encodedString = String.fromCharCode.apply(null, new Uint8Array(response.data));
  var decodedString = decodeURIComponent(escape(encodedString));
  var obj = JSON.parse(decodedString);
&#125;

其中 var decodedString = decodeURIComponent(escape(encodedString));这段代码的作用是解析utf8字符。

参考文章

Conversion between UTF-8 ArrayBuffer and String

How to read JSON error response from $http if responseType is arraybuffer

ajax接口接收文件排坑(一)

先说结论

如果你在用ajax接口接收文件时遇到文件保存后打开报错的情况,请尝试在请求header头中加入responseType: arraybuffer

排坑过程

最近在对接前后端文件传输接口时遇到的问题,后端返回xlsx文件,前端使用new blob()方法从接口返回的数据中重新生成文件。但是
生成的文件用excel打开总是报错。尝试在浏览器或Postman中调用接口保存文件都没有问题。

一开始各种尝试改变new blob方法的type参数没有效果。查看blob方法文档
最坑的就是这里Blob(blobParts[, options])blobParts what fuck is blobParts? 文档中给出的示例有
new Blob([JSON.stringify(debug, null, 2),{type : 'application/json'});so 一个包含字符串的数组?还有一段从blob中读取数据
reader.readAsArrayBuffer(blob);可以看出这里读出的数据应该是arraybuffer。

再看一下XMLHttp​Request​.response​Type的官方文档
arraybuffer的解释 是一个包含二进制数据的 JavaScript ArrayBuffer 。另外我在另一篇Creating a Blob from a base64 string in JavaScript
的提问中当中看到有高手给出的答案中指出了,想要得到blob你先要得到ArrayBuffer, text, and JSON

结论

到这里我们基本已经得到答案了,生成blob对象进而生成文件我们需要ArrayBuffer, text, or JSON这种三种数据类型的某一种。

测试验证

后端Java,代码当中使用的是 BufferedOutputStream 类来返回数据。前端分别测试了”json””text”””arraybutter””blob”四种responseType
arraybutter和blob都可以正确的生成文件。

参考文章

MDN解释
java io系列12之 BufferedInputStream(缓冲输入流)的认知、源码和示例
深入理解xhr的responseType中blob和arrayBuffer

Superset学习体验

superset代码评测:

版本还未稳定,release版本长时间脱离 master版本,且本身并不稳定。项目庞大涉及领域繁多加上人员混杂可能是导致目前代码略显凌乱bug较多的原因。尝试了几个不同的版本才获得一个可以在Ubuntu及Mac平台都可使用Docker正常运行的版本(非release版本),且该版本在前端控制台还存在肉眼可见的bug。版本号:(#6934) ea9d22b2eca6cf823380adc96d2040a5c955edd8

值得一提的问题,部署。项目的打包过程非常复杂,涉及到Linux的环境包,Python包,JavaScript包以及可能存在的其他未知类型的包。打包过程中GFW造成了巨大的阻碍。在有透明代理的路由环境下打包的成功率还是很高的,但在一般环境下成功率十不存一。甚至可能出现已经正常启动过的开发环境,重新启动时因为DNS污染的问题,把原来正确的包给破坏掉的情况。

可行的解决办法是在发布时进行本地打包Docker image,再上传至服务器启动,需要修改部分代码。