《编程珠玑》——记录每次醍醐灌顶的时刻

第一章:

1.字节换算的基础

1MB = 1000KB = 1000000字节 = 8000000bit

1000000/7 约= 142857.1 个号码

32位整数需要占用4个字节

1000000/4 = 250000 个号码

2.位图或位向量表示集会

通过N位二进制,标示0~N存在的所有数字,能够在最极限的空间内表示 0~N的所有数字

3.

使用最后2位作为散列索引  [10*10]

第二章

1.各自单独反转再整体反转

2.

预先排序,生产唯一标识

后面的章节太枯燥了。。。看不下去

Npm 版本相关问题

1.库中的npm包引用同一个库的不同版本会报错吗?

2.包引用存在交集会怎么处理

  1. npm install 会下载哪一个版本?

问题1:

亲手做了一个实验:

A(依赖C:1.0.0),B (依赖C:2.0.0)

目录结构如下:

|-node_module

|-A

|-B

    |-node_module

              |-C:2.0.0

|-C:1.0.0

并没有报错,会同时存在2个C的版本

问题2:

亲自实验:

packageA:
depend C:>=1.5.0 <2.5.0

packageB:>=2.00 <3.0.0
depend C:>=1.5.0 <2.5.0

结果下载了 C:2.4.9
结论:会下载交集里面的最高版本

问题3:

亲自实验:

先发布 packageA@1.0.0  

再发布 packageA@2.0.0

再发布 packageA@1.0.1  

然后 npm install packageA 或者 npm install  packageA@latest 

下载的是packageA@1.0.1 而不是 packageA@2.0.0

说明npm install 下载的是最新提交的版本,而不是最高的版本

docker 学习笔记

     因为我本职是前端工程师,这个docker一直都有了解,一般运维人员会用到的一个虚拟机,方便迁移和升级服务器的。

      由于我想在公司的服务器上搞一个node服务器,但是老大说所有服务器都是在docker上运行的,所有我运行这个node,也需要在docker上运行。😓~可是我还不懂怎么操作docker,那就花点时间把它攻破吧。

      之前我的vps运行了5年都没升级了,最新的wordpress,mysql,node都装不了,想搞点新花样都不行。原因是一直运行着老版本的centos6.4。之前实在没办法,嫌麻烦,一直没升级,因为升级系统需要重新装各种服务器,迁移备份文件,超级烦。所以都没弄。趁着这个docker刚刚学完的机会,感觉把vps升一升。现在用起来贼爽。

      我的wordpress是在 php+mysql+apache环境下搭建的,现在需要用docker重新搭起来
      1.apache
安装Apache的docker,在应用市场上搜索 https://hub.docker.com/ 一定要用官方提供的镜像。

      2.php

安装Php 由于php是apache的一个拓展,安装起来真的很麻烦,搜了一下 https://hub.docker.com/_/php 发现php有捆绑apache的docker的镜像直接用了。 那就可以忽略apache的单独安装了

      3.mysql

https://hub.docker.com/_/mysql
看了一下文档,不是很复杂

      4.wordpress

再看一下,居然直接有worpress集成的镜像,镜像里已经包含对应的apache和php,也就是我只要安装wordpress和mysql的镜像就行了

      秉着服务跟随应用的概念,worpress这个应用已经把apache和php集成进去了。我自己需要再分装一层把,mysql也集成进去。有可能我其它应用需要node或者其它版本的mysql,则需要安装一个新的docker服务器包含起来。

       最后,由于各种docker参数和指令结合再一起确实是能把wordpress跑起来。但是也太复杂了。正好docker-compose这个功能就是为了解决这个问题,基本上配置好以后就是docker-compose build/up/down/rm 这样就能重启,构建,删除服务等操作。

     5.nginx

理论上apache已经是能把web服务器跑起来了,但是考虑到以后还有很多的应用场景,比如https,node服务器,或其它端口的应用。我需要把http/https的协议集中管理,后来我又把nginx做了一层方向代理。

由于方向代理的http头有很多cookie,url相关信息,会影响后面的应用,配置的时候需要注意。

 参考资料如下:

https://www.cnblogs.com/randomlee/p/8761799.html

https://www.w3cschool.cn/docker/docker-hello-world.html

https://blog.csdn.net/qq_36148847/article/details/79427878

http://www.ruanyifeng.com/blog/2018/02/docker-tutorial.html

http://www.ruanyifeng.com/blog/2018/02/docker-wordpress-tutorial.html

chrome插件开发笔记

记得最初开发chrome插件大概是三年前,在恒腾的时候,需求基本上都是各种react+antd的后台表单,类似这样的

后来写多了,感觉重复枯燥,于是想根据后端的接口文档,分享并且映射出对应的jsx代码,于是就有了这个

chrome代码生成插件,主要是方便抓取页面元素而已,其实puppeteer也可以实现,感觉做起来比较麻烦而已。于是对chrome插件开发也比较熟悉。

最近入职BIGO公司,发现这边的发布特别繁杂,可以用Chrome写一个按键精灵插件实现,代替人工点击。

一开始,本来想着写死一个发脚本逻辑,只用来做发布用的。后来想法越来越多,变成一个通用的按键精灵了。目前为止我已经写了大概 7,8个脚本了 如下:

遇到几个难点:

1.设计思路
1. 面向对象:前端开发者  插件的实现本质上就是js操作DOM,这些操作逻辑都需要前端人员开发的,但是使用脚本就不需要。
2. 网页的脚本通常都需要跨页面进行的,所以跨页面通讯很重要。
3. 项目可能越来越多,需要做到多项目切换维护。

2.脚本注入

        依赖 API:chrome.tabs.executeScript,允许注入字符串,也允许注入js文件

        由于页面有可能会刷新,所以注入的时机用这个API判断:
chrome.webNavigation.onCompleted 检测到页面加载完成,则注入对应的脚步。

      由于脚步有可能运行在setTimeout上,注意使用闭包函数实现代码隔离。

3.编辑器

     由于浏览器默认的texture非常难用,主要体现在无法使用tab键,不会自动换行格式化。直接在上面编写代码是在难受,于是网上找了一下相关的代码编辑器,比如 jsrun,jsFiddle,jsBin等等,都不十分完美,只要是无法嵌入到chrome脚本的页面中。

于是只能找那种有源码安装的,终于被我找到了:Ace 代码编辑器https://ace.c9.io/) 非常辛苦的兼容到chrome插件里了

看着这个chrome 插件的脚步逐步增多,能提高团队成员的开发效率,感觉还是很棒棒的。
由于部分涉及公司业务 代码暂时不开源

关于CSS中@import阻塞的探索

之前都知道css的link在页面会柱塞页面渲染,但是css还有一种引入方式 @import

<style>   

@import url("CSS文件");   
</style>  

可以用style标签内嵌的方式引入,如果这个css文件有延迟,会阻塞渲染吗?

做一个实验看看 [demo] 

现象:1. 页面2秒后显示红色

         2. 尽管import的是蓝色在后面,div显示红色

结论:1.import引入的css也会阻塞页面渲染

         2.improt的css的权重总是比link引入的低

宏任务与微任务

//请写出输出内容
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');
/*
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
*/

这道题属于异步变成的知识点,但是底层是有一个叫 宏任务与微任务 的队列来支持

宏观任务:主进程代码,setTimeout, setInterval, UI交互事件

微任务:常见的就是.then, yield, await

68747470733a2f2f692e6c6f6c692e6e65742f323031392f30322f30382f356335643661353238626461662e6a7067.jpeg

相当于有一个渲染过程包含2个任务队列,先执行宏任务,宏任务里面行微任务(如果有),然后触发渲染,在进入下一个宏任务。

参考资料 https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/7

五种常见的算法模版

//循环
function loop(a, b, c) {
	// 终止条件
	if(a===xxx){
		return
	}
	doSomething(a, b, c);
	loop(a+1, b, c);
	reverse_state(a, b, c) //还原状态
}
//深度优先搜索
const visited = new Set();   //利用visited记忆化,防止重复计算
function DFS(node, a, b) {
	visited.add(node);
	doSomething(node, b, c);
	for(let i = 0; i<node.children.length; i++) {
		if(!visited.has(node.children[i])){
			DFS(node.children[i], a, b)
		}
	}
	reverse_state(node, b, c) //还原状态
}
//广度优先搜索
function BFS(root, a, b) {
	// 设置缓存队列
	const quene = [];
	quene.push(root);
	while(queue.length>0){
		let node = queue.pop();
		doSomething(_node, b, c);
		for(let i = 0; i<node.children.length; i++) {
			quene.push(node.children[i]);
		}
	}
}
//二分搜索
function binarySearch(array, target) {
		let left = 0;
		let right = array.length-1;
		while(left<=right){
			let mid = Math.floor((right + left)/2);
			if(array[mid] === target){
				//找到了
				return xx;
			}else if(target<array[mid]){
				right = mid-1; //左右边界调整
			}else{
				left = mid+1;
			}
		}
		return -1;
}
//DP
function DP(a, b, c){
	//定义DP[i]的含义,这里有可能是多维数组
	let DP = [];
	//设置DP[0]的值
	DP[0] = xxx;
	//这里可能是多重循环
	for(let i = 1; i<something; i++) {
		DP[i] = doSomething(DP[i-$])  //按照实际情况写出递推公式
	}
	return DP[i] //结果一般存在最后一个数组或第一个
}

主定理 与 时间复杂度

主定理最早出现在《算法导论》中,提供了分治方法带来的递归表达式的渐近复杂度分析。
规模为n的问题通过分治,得到a个规模为n/b的问题,每次递归带来的额外计算为c(n^d)
T(n) <= aT(n/b)+c(n^d)
那么就可以得到问题的复杂度为:

  • T(n) = O(n^d log(n)), if a = b^d

  • T(n) = O(n^d ), if a < b^d

  • T(n) = O(n^logb(a))), if a > b^d

二分搜索

  • 每次问题规模减半,a=1,b=2,c=0

  • 复杂度为n^0 O(log n)

二叉树遍历

    每次问题规模减半,a=2,b=2,c=0

    复杂度为n O(n)

快速排序

  • 随机选择待排序序列中的一个数字作为划分字问题的标准,划分是否平均影响算法复杂度

  • 每次问题规模减半,a=2,b=2,c=1

  • 复杂度为n^2 O(n log n)

  • 最差情况下,复杂度为O(n^2)

归并排序

  • 数据列均分为两部分,分别排序,之后以O(n)的复杂度进行合并,空间复杂度O(n)

  • 每次问题规模减半,a=2,b=2,c=1

  • 复杂度为n log(n)

PWA 学习小结

  1. 创建主屏幕入口

<link rel="manifest" href="manifest.json" />

manifest.json文件大概是这样的

{
  "name": "微博Lite",
  "short_name": "微s博",
  "description": "随时随地分享新鲜事",
  "icons": [{
      "src": "https://luoyongjie.cn/lab/pwa/icon/weibologo.png",
      "sizes": "606x606",
      "type": "image/png"
    }],
  "start_url": "/static/pwa/",
  "scope": "/static/pwa/",
  "display": "browser",
  "orientation": "portrait",
  "background_color": "#F3F3F3",
  "theme_color": "#F3F3F3",
  "related_applications": [],
  "prefer_related_applications": false
}

 

这里我是抄m.weibo.com/beta的manifest.json

内容太多,不一一称述,无非就是设置logo图片,访问地址,app打开的屏幕,是否显示导航栏,就像一个HirdApp一样

这里说一下关键问题

  1. 设置主入口的交互是如何的?

image.png

image.png

点击添加之后就会在桌面出现icon入口了

感觉这样的交互,用户几乎不会这样做,太鸡肋了,那么有没有可以用js控制提醒用户是否需要添加到主屏幕的方式呢?没有。。。。。

于是我带着怀疑的态度,尝试了

chrome IOS版本 居然没有开放这个操作

chrome Android版 操作有了,确认添加到主屏幕了,但是屏幕就是找不到icon入口

2.server worker 离线缓存

入口文件处载入如下代码:

<script>
function registerServiceWorker(){
    return navigator.serviceWorker.register('./sw.js').then(registration => {
        alert('注册成功');
        return registration;
    })
    .catch(err => {
        alert('注册失败', err);
    });
}
window.onload = function () {
    if (!('serviceWorker' in navigator)) {
        return;
    }
    registerServiceWorker()
}
</script>
const CURCACHE = 'CURCACHE_test_1'
const RUNTIME = 'runtime';
var CURCACHE_URLS = [
  '/lab/pwa/index.html',
  '/lab/pwa/main.css'
];
self.addEventListener('install',e=>{
    e.waitUntil(
      //存储缓存路径对应的资源
        caches.open(CURCACHE).then(cache=>{
            cache.addAll(CURCACHE_URLS)
        }).then(
            self.skipWaiting()
        )
    )
})
 
 
   
  //代理请求,使用缓存,请求发送之前
  self.addEventListener('fetch', e => {
    e.respondWith(
      //缓存是否匹配 
      caches.match(e.request).then(function(response) {
        if (response != null) {
          //命中缓存返回缓存,结束请求
          return response
        }
        //未命中缓存,正常请求
        return fetch(e.request.url)
      })
    )
  });

 image.png

只要caches设置了缓存,就能在离线状态下访问页面,同时可以在sw中控制缓存哪些内容。

sw.js文件每次都会请求服务器获取最新的server worker文件

注意:如果浏览器本身对sw.js进行缓存的话,也不会得到最新代码,所有代码会变成死代码,无法更新。所以对sw文件最好配置成cache-control: no-cache

经过测试,ios safair,chrome 都支持,但是微信页面不支持 navigator.serviceWorker == undefined,估计是微信为了推广小程序,故意封掉的

3.消息推送

https://firebase.google.com/docs/web/setup

按照这里的教程可以利用firebase的安装包,可以做出一个消息推送的demo。不过必需要翻墙。

实际体验如下:

需要在服务器环境模拟一个请求到google的推送服务器

比如

curl -X POST -H "Authorization: key=YOUR-SERVER-KEY" -H "Content-Type: application/json" -d '{
  "notification": {
    "title": "Portugal vs. Denmark",
    "body": "5 to 1",
    "icon": "firebase-logo.png",
    "click_action": "http://localhost:8081"
  },
  "to": "YOUR-IID-TOKEN"
}' "https://fcm.googleapis.com/fcm/send"

然后在安装好firebaseSDK的页面上打开

电脑端:焦点在当前窗口的话,会即使调用当前页面的js messaging.onMessage 方法收到msg

            如果焦点不在当前窗口,则启动ServerWorker后台的self.registration.showNotification

        image.png

        浏览器就会在系统层发出一个消息通知。

        如果断网的情况下,等到网络恢复,这个消息会自动弹出来

手机端:ios的 微信,safair,chrome 完全不兼容

            android的chrome 比较乐观

            1541054402791967.png

           可是只是收到几次,突然就不行了,不知道是不是系统做了限制,卸载再安装又可以收到几次。

            如果把chrome进程杀掉,也是收不到推送的,这一点挺坑的。

          如果要关注兼容问题,可随时关注 

            https://caniuse.com/#search=Push%20API

            https://caniuse.com/#search=Notifications 这个API的兼容情况

小结:

注意以上所有环境都需要在https或localhost或127.0.0.1进行,因为server worker规定了只能在这些调节下才能开启。如果是用手机做测试,那你必需拥有一个https的站点才能测试,这一点非常坑,于是我把站点升级成https了。

实际情况:PWA最重要的一点是离线缓存,(首屏入口和消息推送用的场景不是刚需)提高用户首屏加载体验,可是这么关键的一点,居然被微信封杀了,在中国h5的流量几乎都在微信,场景限制非常大。如果某个h5不是在wx推广的,我觉得pwa离线缓存是提高用户体验的利器。

vue响应式源码实现

png

const Observer = function(data) {
  // 循环修改为每个属性添加get set
  for (let key in data) {
    reactive(data, key);
  }
}
const Dep = function() {
  const self = this;
  //这里可以用数组,就能实现同一个data,观察多个vue实例
  this.depend = ()=>{
    if (Dep.target) {
	  this.sub = Dep.target
	}
  }
  this.notify = ()=>{
	  this.sub.update();
  }
}
const Watcher = function(vm, fn) {
  //这样的写法是为了把target传到dep实例里面,当然也可以不挂载到dep,但要有一个地方暂存起来
  Dep.target = this;
  this.update = function() {
    console.log('in watcher update');
    fn();
  }
  //初始化运行一次render函数
  fn();
  Dep.target = null;
}
const reactive = function(obj, key) {
	const dep = new Dep();
	let val = obj[key];
	Object.defineProperty(obj, key, {
		enumerable: true,
		configurable: true,
		get() {
		  console.log('in get');
		  dep.depend();
		  return val;
		},
		set(newVal) {
		  if (newVal === val) {
			return;
		  }
		  val = newVal;
		  dep.notify();
		}
	});
}
const Vue = function (options) {
	this._data = options.data.call(this);
	//设置data的get,set
	Observer(this._data);
	this.mount = (id)=>{
		this.dom = document.querySelector(id);
		new Watcher(this, this.render);
	}
	//渲染函数,这里主要是触发get方法
	this.render = ()=>{
		//这里vue应该会有虚拟dom的算法实现
		this.dom.innerHTML = `<div>${this._data.text}</div>`;
	}
}
const vue = new Vue({
  data() {
    return {
      text: 'hello world'
    };
  }
})
vue.mount('#app');
vue._data.text = '123';

首先看一下主入口 Vue,包含 Observer, mount, render 三个方法

其中 Observer 的作用是为了获取data中的每一个get,set操作,以达到监听数据变化的手段(可用ES6的Proxy实现)

Watcher函数有2个作用,第1个是在程序首次运行的时候,用于触发Observer中的get方法,收集依赖,并把当前watcher的实例绑入到dep中。

                                    第2个作用是,当数据被修改,也就是触发set操作的时候,通知对应的dep中的watch实例触发updata->render,达到页面重新渲染的目的。

render函数这里只是简单的一行代码,实际vue里面的实现应该是非常复杂的,包括 jsx,vue模版引擎,虚拟dom之类的实现

github地址: https://github.com/gaxking/vue-deeplearn