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

第一章:

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 下载的是最新提交的版本,而不是最高的版本

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引入的低

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 

内容安全策略( CSP )

CSP 通俗的来说就是可以根据header的字段,或者meta标签,限制页面的script,图片,video等外部自由的加载来源,以达到防止被XSS的目的

下面来实践一下:

header 可以设置:Content-Security-Policy

或者在

meta 设置 <meta http-equiv="Content-Security-Policy" content="default-src 'self'">

这里方便我做测试,就使用meta方式吧

常用设置如下:

1.所有内容均来自站点的同一个源

Content-Security-Policy: default-src 'self'  

[demo]

2.允许所有内容来自信任的域名,或指定域名

Content-Security-Policy: default-src 'self' *.baidu.com

[demo]

3.分别控制网页的图片,音频或视频,脚本

Content-Security-Policy: default-src 'self'; img-src *; media-src media1.com media2.com; script-src luoyongjie.cn

[demo]

4.  Content-Security-Policy-Report-Only: /something/

这个属性只会产生警告不强制失败

[demo]

5.还能发送违法请求

Content-Security-Policy: default-src 'self'; report-uri http://reportcollector.example.com/collector.cgi

这里就不写demo了

常用图片格式对比分析

常用图片无非就以下4种

  1. PNG

  2. JPG(JPEG)

  3. GIF

  4. WebP

1.png分为png8 和 png24

png8 只支持2^8(256)种颜色,半透明和全透明

仔细看一下png8的妹子图片,颜色看起来确实不够鲜艳

image.png

这幅是我随便画的图片,图层设置50%半透明,但是png8 只支持 全透明和全不透明

image.png

png8 适合应用于颜色比较单一的logo,icon图片。

png24相比,支持半透明,色彩也显示得更加丰富,但是相应的图片大小更大,适合应用于摄影作品

jpg也可以叫jpeg,色彩支持24bit,同png24一样能显示丰富的色彩,但是不支持半透明,但是图片体积比较小,适用于网络上显示比较丰富的图片


在图片经过编辑保存之后,jpg图片会逐渐失真,png不会。

原因是jpg采用的是有损压缩,png采用的是无损压缩。


gif:gif可以支持动画,类似于png8的布尔透明类型,只有全透明跟全不透明,没有半透明,是无损耗的图像格式。

webP:是有损压缩,

WebP:可以选择区别无损和有损压缩

在有损压缩情况下,相比JPEG减少25%~34%的大小,有损WebP也支持透明通道,大小通常约为对应PNG的1/3

在无损压缩情况下,相比PNG减少26%大小

于gif相比支持动画,动态WebP相比GIF支持更丰富的色彩,并且也占用更小空间,更适应移动网络的动图播放。

1539156399134726.png

唯一的缺点是,技术实现的成本比较高,而且苹果和ie对它完全不支持

​《你不知道的Javascript-中册》阅读笔记

第一部分

第一章

1、

var a;

a; // undefined

b; // ReferenceError: b is not defined

undefined 和 is not defined 是两回事

2.

// 这样会抛出错误

if(DEBUG) {

    console.log( "Debugger is starting" )

}

//这样是安全的

if (typeof DEBUG !== "undefined") {

    console.log( "Debugging is starting" )

}

第二章

1.

var b = 42.;  //42.这样的写法没问题,只是不常见,但从代码的可读性考虑,不建议这样写。

42.toFixed(3) 是无效语法,因为 . 被视为常量42.的一部分 , 42..toFixed(3) 则没问题

2.

Object.is() 主要是用来处理那些特殊的相等比较 

Object.is(NaN, NaN) //true

Object.is(0, -0) //true

3.

对包含循环引用的对象执行JSON.stringify(..) 会出错

第4章

1.

以下这些是假值:

undefined

null

false

+0, -0 和NaN

""

假值的布尔强制类型转换结果为false

理论上 假值列表以外的都应该是真值。  

2.

使用 Date.now() 来获取当前时间戳,比new Date().getTime() 简单

3.

 ~x 大致等于 -(x+1) 

~42; // -(42+1) ==> -43

4.

var a = "Hello World";

if(a.indexOf("ol") == -1){

    //没有匹配

}

这里是指用-1作为失败时的返回值,这些细节应该被屏蔽。

var a= "Hello World";

~a.indexOf("lo"); // -4 真值

if (~a.indexOf("of")) {  //true

    //找到匹配

}

~a.indexOf("ol"); //  0 <–假值

!~a.indexOf("ol"); // true

if(!~a.indexOf("ol")){ //true

    //没有匹配!

}  

5.

~~49.6

~~x 能对小数进行截取,取代Math.floor(xxx),但需要注意负数的情况

~~-49.6 // -49,    ~~中的第一个~执行ToInt32并反转字位,然后第二个~再进行一次字位反转,即将所有字位反转回原值,最后得到仍然是ToInt32的结果

Math.floor( -49.6 ) //-50

6.

[] + {} //[Object object]   "" + [Object object] 

{} + [] //0     {}被当作一个独立的空代码快,不执行任何操作。  代码块结尾不需要分号, 最后+[] 将[]显示强制类型转换

7.

true || false && false;  //true
(true || false) && false;  //false 
true || (false && false);  //true

这说明&&运算符优先于||执行

8.

function foo( a = 42, b = a+1) {
    console.log(
        arguments.length, a, b,
        arguments[0], arguments[1]
    )
}
foo();                             //0 42 43 undefined undefined
foo( 10 );                       //1 10 11 10 undefined
foo( 10, undefined );    //2 10 11 10 undefined
foo( 10, null );              //2 10 null 10 null

在ES6中,虽然参数a和b都有默认值,但是函数不带参数时,arguments数组为空.

如果向函数传递undefined值,则arguments数组中会出现一个值为undefined的单元

9.

<script>foo();</script>

<script>

    function foo() { .. }

</script>

报错,不会进行变量提升

10.

在调试的过程中,有可能对象在console.log(…)语句之后被修改,看到了意料之外的结果,要意识到 这可能是I/O的异步造成的,可以用JSON.stringify()快照一次

第四章

1.Promise中,多次调用reject或resovles,只有第一次有效

new Promise((resovles, reject)=>{
        //something
        resovies(); //只有第一次有效
        resovies(); //无效
})

12.Promise中,只能返回第一个参数

new Promise((resovles, reject)=>{
        //something
        resovies(a, b, c); //只返回a
})

13. 如果Promies中发生错误,则不往下执行,而是返回一个promise,包含错误信息

var p = new Promise(()=>{xxxx});
var p2 = p.then(()=>{
    make some error code
    //不会报错,也不会运行到下一行
    some code
},()=>{
    //不会执行到这里
})
p2.then((e)=>{
    console.log(e) //e就算错误信息
})

14. Promise的局限性有6个

    1.Promise链中的错误信息很容易被无意中默默忽略掉

    2.只接受单一值返回

    3.单决议 // 就是 resolve或rejuect只能运行一次

    4.惯性 //就是通常要封装request之类的

    5.无法被取消

    6.性能稍微低一点点

15. 

function *something(){
    while(true){
        console.log(xxxx)    
    }
    finally{
        console.log("cleaning up!")
    }
} 
var it = something();
for(var v of it) {
    console.log(v);
    if(v > 500){
        it.return("end").value;
    }
}

调用it.return(…)之后,它会立即终止生成器,运行finall语句

16.

function *foo(){
    console.log( "*foo() starting" );
    yield 3;
    yield 4;
    console.log( "*foo() finished");
}
function *bar() {
    yield 1;
    yield 2;
    yield *foo(); // yield委托
    yield 5;
}
var it = bar();
it.next().value;   //1
it.next().value;   //2
it.next().value;   //*foo() starting   注意观察这里,*bar() 把自己的迭代控制委托给了*foo()
                      //3
it.next().value;   //4
it.next().value;   //*foo() finished
                   // 5

第五章

1.尾调用优化

function bar(y){
    return foo( y + 1);  //尾调用
}
function baz(){
    return 1 + bar( 40 );  //非尾调用
}

《你不知道的Javascript-上册》阅读笔记

第一部分  作用域和闭包

第三章

 1.

{
    console.log( bar ); //Error
    let bar = 2;
}

说明1. let 函数没有变量提升这个说法, 

       2. 在大括号内就能产生块作用域

第四章

1.

foo(); //error
{
function foo() {console.log("b")}
}

说明函数的提升只在自己的块级作用域有效

console.log(a) //undefined 注意不是error
{
var a;
}

说明var的声明提升可以在子级的块级作用域有效

2.

JavaScript中的作用域就是词法作用域

function foo() {
  console.log(a); //2
}
function bar() {
  var a = 3;
  foo();
}
var a = 2;
bar();

3.

var fun = function foo() {console.log("b")}
fun();  //b
foo(); // error

说明同时使用2个变量命名的函数,会忽略后面一个

第二部分 this解析

当判断this的真实对象时,需要应用下面四条规则中的其中一条:

  1. 默认绑定

     this 一般指向全局对象,严格模式是undefined

      function foo(){
            console.log(this.a);  //use strict 只对当前块级作用域有效,所以这里不是undefined
     }
    var a = 2;
    
    (function(){
        "use strict";
        foo();//2
    })();

    foo() 是直接使用不带任何修饰的函数进行调用的,因此属于默认绑定

    2.隐式绑定

    obj.foo()  //类似这种有上下文调用的

    3.显示调用

     call,apply,bind

    4. new 一个实例

2. 

function foo() {
    console.log(this.a);
}
var a = 2;
foo.call( null );//2

如果call,apply,bind传入null,则应用默认绑定

3.

function foo()
{
    return (a)=> {
    console.log(this.a)
    }
}
 
var obj1 = {
    a:2
}
 
var obj2 = {
    a:3
}
 
var bar = foo.call(obj1);
bar.call(obj2); //2

foo()调用一次时,箭头函数中的this获得 obj1 , 由于箭头函数一旦获得this,就无法被修改,所以还是obj1

 第三章 对象

  1. 深度复制对象时,要注意循环引用的问题

第五章 原型

  1. 判断两个对象之间的关系,可以用

 b.isPrototypeOf( c );

2. 使用Class的一个缺点:

class C{
    constructor(){
        C.prototype.count++
        console.log("Hello:" + this.count);
    }
}
C.prototype.count = 0;
var c1 = new C();
//Hello:1
var c2 = new C();
//Hello:2
c1.count === 2;//trte
c1.count === c2.count;//true

这种方法问题是,它违背了class语法的本意。在实现中暴露了.prototype

如果使用 this.count++ 我们会发现 c1,c2上都创建了 .count属性,而且互相共享