Git Product home page Git Product logo

blog's Introduction

blog's People

Contributors

dependabot[bot] avatar lzake avatar penglih avatar plh97 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

blog's Issues

强大的css选择器

很早很早以前,我对css选择器的概念一直停留在 #id .class ,直到我看到了mdn的Pseudo-classes(伪类class).

image

p:not(.class)

<span class='class'>不被选择</span>
<span class="bbb">选中</span>

p:last-child;;;p:first-child p:nth-child(2)

选择第一个子元素,最后一个子元素 ,第二个子元素,强大的select选择器配合document.querySelectorAll('p:last-child')进行筛选简直方便极了,和正则选择字符串一样方便

div ul:not(:first-child):not(:last-child) {
    background-color: #900;
}

:empty 选择没有子元素的元素

div.container:empty {
    background-color: #900;
}

button > button > button 选择兄弟元素的兄弟元素

button>button>button {
    background-color: #900;
}

input:checked 选择已经被勾选的 input元素

结论:善用伪类元素选择器,省去了大量无效class类名,id,,无效js代码。增加了代码的可读性

ES6标准入门(二)

第18章 class Class基本语法

JavaScript语言的传统方法是通过构造函数定义并生成新对象。下面举个例子。

  this.x = x;
  this.y = y;
Point.prototype.toString = function(){
  return `(${this.x} , ${this.y})`
}

上面这种写法和Java,c++区别很大,这让程序员很困惑。
es6提供了新的语法糖ckass

class Point{
  constructor(x,y){
    this.x = x;
    this.y = y;
  }
  toString(){
    return `(${this.x} , ${this.y})`
  }
}

constructor是构造器的方法。而this则代表了该对象。class就是es5的构造函数的另一种写法。更加语义化。

class Point{}
Point === Point.prototype.constructor.Point   //true

下面写法也是对等的

class Point {
  constructor() {
    // ...
  }
  toString() {
    // ...
  }
  toValue() {
    // ...
  }
}
Point.prototype = {
	constructor(){}
	toString(){}
	toValue(){}
}

而在类的实例上面调用方法,其实就算调用原型上面的方法。

class B{}
let b = new B();

这些方法都是不可枚举的.通过class内部命名的方法,和通过prototype命名的方法最大的不同是一个是可枚举的,另一个是不可枚举的。而添加static关键字的方法会被添加到Point的原型,因此仅仅只可以内部使用。

class Point {
  constructor() {
    // ...
  }
  static toString() {
    // ...
  }
  toValue() {
    // ...
  }
}
class Point1 {}
Point1.prototype = {
  constructor() {},
  toString() {},
  toValue() {},
};
const p = new Point();
const p1 = new Point1();
console.log(Object.keys(Point.prototype));
console.log(Object.keys(Point1.prototype));
console.log(p);
console.log(p1);
console.log(Point.prototype.constructor === Point);      // true
console.log(Point1.prototype.constructor === Point1);      // true
console.log(Point1.prototype.constructor == Point1);      // true  

image
其中虚的是不可枚举的。

constructor 方法

这个方法是默认存在的方法,,通过new命令生成对象实例时候自动调用的方法,默认返回它本身这个实例化对象this,当然可以指定返回其他对象。

实例对象

生成实例对象和es5一样,如果没有new命令,则会报错,除非你在他前面添加static 作为静态方法,就可以。下面可以看到,constructor里面的x,y, 静态aa方法,普通bb方法

class Point {
  constructor() {
    // ...
    this.x = 1;
    this.y = 1;
  }
  static aa() {}
  bb() {}
}
const p = new Point();
console.log(p);
console.log(Object.keys(Point.prototype));

image

但是当aa,bb都是普通方法的时候,又是另一个样子,奇了个葩

image

不存在的变量提升hoist,他这个和es5的函数提升完全不一样

new Foo(); // referenceError: Me is not defined
class Foo{};

class 的继承

class Foo exntend foo {}
下面介绍super()

class Point {
  constructor() {
    this.x = 1;
    this.y = 1;
  }
  getname() {
    return this.name;
  }
}
class ColorPoint extends Point {
  constructor() {
    super();
    this.name = 'ColorPoint';
  }
  aaa() {
    return 'ColorPoint';
  }
}
const p = new ColorPoint();
console.log(p);

image
这里ColorPoint存在两条原型链,一条是继承方法,另一条是继承constructor里面定义的变量。
下面由3钟继承的特殊情况

  • 第一种,子类继承Object
    class A extends Object {}
    其实就算对Object的复制
  • 第二种不继承
    class A{}
    其实就是默认继承Function。
  • 第三种继承null
    class A extends null{}
    真正的不继承任何东西

super 关键字

他代表了父类的实例。

class类中的getter和setter

class Point {
  constructor() {
    this.x = 1;
    this.y = 1;
    this.name = 'pengliheng';
  }
  getname() {
    return this.name;
  }
  get prop() {
    return this.name;
  }
  set prop(value) {
    console.log(`setter: ${value}`);
  }
}
const p = new Point();
p.prop;                             // setter: pengliheng
p.prop = 'hhhah';            
p.prop;                            //  setter: hhhah

定义一个属于自己的Array,不要再原生Array上面扩展方法,如果需要,请看下面代码

class VersionArray extends Array {
  constructor() {
    super();
    this.history=[[]];
  }
  commit() {
    this.history.push(this.slice());
  }
  revert(){
    this.splice
  }
}

18.5 class 的 静态方法

const a = 'test';
const b = 'test';
a===b // true

const a = {};
const b = {};
a===b // false

const a = [];
const b = [];
a===b // false
[] === []  //false

const a = [];
const b = a;
a===b // true    ,为什么呢? 因为point   ,a point b

下面看一下他们为什么不相等

class Point {
  constructor() {
    this.x = 1;
    this.y = 1;
    this.name = 'pengliheng';
  }
  static getname() {
    return this.name;
  }
  getTName() {
    console.log(new Point() === this);
  }
}
console.log(Point.getname());     // 静态方法,不需要实例化就能获取
const p = new Point();
p.getTName();                             // false,{} !== {},即便是同样的对象,他们也互不相等。     普通方法需要实例化。

静态属性

19章装饰器

装饰器是一种表达式,他能对类的行为发生改变,,这种改变发生在编译阶段,
有点超前,自己去了解吧,如果你不是用的react或者经常用class面向对象编程,并且需要继承属性的时候,你都用不到装饰器。

20章module,这个以后再说,书里面没有提到module模块化的原理,如何引入的。没有讲的深入。

21章编程风格(关于es5过度到es6的使用建议)

####使用 let / constant 替代 var
由于模块化默认使用'use strit'严格模式
所以以后应该是严格模式默认的。
使用优先级
const>let>var
const 的使用是因为他是常量,能够给人直观的,如果你要改变常量,建议去定义一个新的常量。这也是函数式编程风格所要求的。
坚持遵循 先声明,再使用的编程规范。

####使用模板字符串添加变量
使用单引号

// bad
const a = '<div>'+value+'</div>'
// good   更具语义化
const a = `<div>${value}</div>`

解构变量

// bad
var a = 1,b=2,c=3;
//good
const a = 1;
const b = 2;
const c = 3;
// best,,,,,更具语义化。
const [a,b,c] = [1,2,3];

image

对象

熟悉typescript的你可能知道,它推崇的是尽量使用常量,以及变量+类型
声明了一个对象,尽量不要去改变它,如果真的要改变它,建议使用以下

// bad
const a = {};
a.x = 3;
// 

使用...扩展符号来复制数组

var a = [...arr];
var b = Array.from(arr);

箭头函数 => 推荐使用,

他消灭了 绑定 bind

推荐使用class来命名对象

使用Eslint代码检查工具。。。

统一规范编程风格,

结束语,不写了。烂书越写越烦。

vue - 无法数据同步更新到view视图区

前言

经常遇到的问题,vuex当中的数据可以同步到每一个组件视图区,但是。复杂数据中的属性被修改却无法同步

vue数据绑定

vue会自动循环state当中的所有属性,为每一个属性添加set和get,进行监听,但是问题是,如果我们修改的是其中某个深层属性呢?

当我修改下面这个地址设置的默认属性,我希望通过v-for循环所有地址数据,再通过v-if判断所有地址中id和默认地址id相同,则显示他为默认地址。这个时候我发先vuex当中的属性是 正确被改变了的,但是真实视图区view中的中的v-if判断却没有做出正确显示。

image

这个时候,我建议取巧,利用组件中的computed计算属性,

export default {
  store,
  data() {
    return {
      myInfo: store.state.myInfo,
      company: store.state.company
    };
  },
  computed:{
    defaultAddress: ()=> store.state.myInfo.address.default
  },
  methods: {
    setDefault(id){
      store.commit("syncState", {
        stateName: "myInfo",
        stateValue: {
          address: Object.assign({}, store.state.myInfo.address, {
            default: id
          })
        }
      });
      store.commit("syncSession", "myInfo");
    }
  }
};

设置好计算属性之后,发现属性可以同步了。计算属性会时时刻刻监听vuex中的某个深层属性的变化,同步到v-if判断。
好吧,就这样
结束。

es6标准入门-阅读笔记

由于本人实力不够,还是坚持继续阅读

第一章

ECMAScript7

js的新标准从提案变成正式总共经历5个阶段

  • Sstage 0:strawman(展示阶段)
  • Sstage 1:Propasal(征求意见阶段)
  • Sstage 2:Draft(草案)
  • Sstage 3:Candidate(候选阶段)
  • Sstage 4:Finished(定案阶段)

第三章 解构变量

交换

[a,b] = [b,a]

提取

let {a,b,c} = json;

传入函数的参数解构

function func({a,b}){}
func({a:1,b:2});

传入函数参数自带默认值

function func(json={
  a:1,
  b:2
}){
  return true;
}

第四章 字符串的扩展

js允许采用\uxxx的形式表示一个字符,其中xxxx表示字符串码点。
"\u0061" // "a"
但是这种表示法只限于\u0000 ----\uFFFF之间的字符。超出这个范围的必须用两个字节表示
"\uD842\uDFB7" \"𠮷"
ES6对此进行了改进
"\u{42}\u{43}\u{44}"
JavaScript内部,字符串以UTF-16的形式储存,每个字符固定2个字节,对于那些需要四个字节储存的字符,JavaScript认为他们是占两个字节。
image
ES6提供codePointAt(0)的方法,能正确处理4个字节的字符,并返回一个字符的码点。

padStart padEnd

ES7推出字符串不全长度功能,如果字符串长度未达到,会自动在头尾补全,
'x'.padStart(5,'ab') // 'ababx';
'x'.padEnd(4,'ab') // 'xaba';

4.10 模板字符串

`
    hi,i am crazy
    hihih${ iamchangevalue }
`

4.11 实例:模板编译

下面例子,一个通过模板字符串生成真正正式模板的实例
各种奇淫技巧我就不一一详述。

第八章 函数的扩展

写代码经常遇到一个这样的问题,他没有forEach的方法,我们运用扩展符号,轻松将他转为数组。

var list = document.querySelectorAll('div');
var arr = [...list];

a.name即返回函数名字

a = function func(a){}
a.name // 'func'

构造函数的名字

(new Function).name    // "anonymous"

bind绑定的函数的名字,返回的是 'bound '

a = function func(a){}
a.bind({}).name    // "bound func"

8.5 箭头函数(关键)

var func = () => {};   // 这是最常见的箭头函数,这里不再复述

当返回的是一个对象的时候呢??意想不到的是,报错了,因为函数默认识别{作为一段代码块,而你则把{当作对象的开头,所以这种情况下必须加上小括号。(),同时箭头函数可以配合变量结构一起使用,相当让人愉悦的一段代码。

var getObject = ({a,b}) => {a:1,b:2}

image
返回的对象和传入的对象是一样的值,但是对象早已不是之前的对象,用这种写法来写函数式编程应该会非常舒适。

let obj = ({a,b}) => ({a,b});
obj({a:1,b:3});
[1,2,3].map(function(e){
  return e*e;
})
[1,2,3].map(e=>e*e);

总结一下,箭头函数最主要的效果是简化回调函数,但这需要建立在对函数熟悉的基础上。但是使用过程中要注意以下几点:

  • 1.函数体内的this对象就算定义时所在的对象,而不是使用时所在的对象。这看起来比普通函数要合理的多。。我就爱用箭头函数。
function functhis() {
  return this;
}
const arrowthis = () => this;
console.log(functhis()); // window
console.log(arrowthis()); // {}
const a = function() {
  console.log(functhis()); // window
  console.log(arrowthis()); // {}
};
a();
  • 不能当作构造函数,不然会报错。不可以使用new命令,这看起来也是比较合理的。
function afunc(){}   // undefined
new afunc()
afunc {}
afunc = () => {}
() => {}
new afunc()          // VM19499:1 Uncaught TypeError: afunc is not a constructor   .at <anonymous>:1:1
  • 不能使用arguments对象,该对象在函数存在,但是你在里面拿不到参数
  • 不能使用yield命令,因此箭头函数不能用作Generator函数。

call和普通调用不同,call内传入的参数是就算函数内的this,而此处箭头函数,this永远指向函数本身。而如果是普通函数,this指向window,严格模式下,this指向undefined.,不错的es6。他让js变得简单优雅。。。

function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}
foo();
foo.call({ id: 1 });

下面代码,this永远指向函数本身,因为他是箭头函数。

const handler = {
  id: '123456',
  init() {
    document.addEventListener(
      'click',
      e => this.dosomething(e.type), false,
    );
  },
  dosomething(type) {
    console.log(`handle ${type} for ${this.id}`);
  },
};

这里定时器内部箭头函数的this同样指向对象本身

function Timer() {
  this.sec = 0;
  setInterval(() => this.sec++, 100);
}
const timer = new Timer();
setInterval(() => {
  console.log(timer.sec);
}, 300);

this指向的固定化,为什么呢,因为箭头函数内部根本没有this,所以this永远指向对象本身。正因为如此,所以箭头函数同样不能做构造函数。

函数绑定

箭头函数可以用以将this指向函数本身,这大大简便了js绑定,举个例子react官方说的一个绑定例子

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // This binding is necessary to make `this` work in the callback
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);

react采用this.handleClick.bind(this),这样的绑定函数会返回一个函数,而返回的新函数通过bind绑定的新函数,下面看mdn对于bind的定义。返回的新函数this永远指向,意思就是,bind函数传入的第一个参数,就算新函数的this,无论你怎么调用,都是这个this,这和箭头函数的核心含义是不谋而合的。
image
下面是react官方的第二种写法,省区了bind步骤,就是因为使用了箭头函数。

class LoggingButton extends React.Component {
  // This syntax ensures `this` is bound within handleClick.
  // Warning: this is *experimental* syntax.
  handleClick = () => {
    console.log('this is:', this);
  }
  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}

8.6 函数绑定

箭头函数可以绑定this,大大减少了显示绑定this对象的方法(call,apply,bind),但是es7提出了新的提案,::用来绑定

fo0::bar
// 等同于
bar.bind(foo)

foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments);

8.7 尾调用优化

说的就算高阶函数

let highFunc = (arr) => {
  let a = 1;
  let b = 2;
  return g(a+b);
}

js的函数可以说是非常灵活的可以在任何地方被调用,包括if(内部){},关键不在于函数书写形式,关键在于所返回的是函数还是对象还是字符串,或者根本没有return,func()()()只要返回的是所需要的东西即可,不管你后面跟了几个括号。

尾调用极其不同的地方在于其特殊的调用函数的位置。

具体可以了解相关调用栈(call stack #17 )由于函数尾部是一个新的函数,所以调用它的函数会被先存入调用栈,等待新函数被找到之后,新函数也会被放入调用栈,然后调用栈按照FILO(先入后出)队列原则,依次执行。

let highFunc = (arr) => {
  let a = 1;
  let b = 2;
  return g(a+b);
}
highFunc()
// 等价于
function highFunc(){
  return g(3);
}
// 等价于
g(3);

上面代码,如果g不是尾调用,函数f就需要保存内部变量m,n,以及g的调用位置,。但是当g被调用之后,函数f就结束了,所以f(x)函数完全可以被删除。
上面说的就是尾调用优化,可以节省部分内存??

尾递归

函数调用自身称之为递归,在尾部调用自身,称之为尾递归,
下面的是递归函数,调用栈最大承受量为11370次,容易发生栈溢出效应。其复杂度为O(n)

function factorial(n) {
	if (n === 1) return 1;
  return n + factorial(n - 1);
}
console.log(factorial(11370));

image
但是对于下面这个函数,尾递归由于全程只发生一个调用栈,复杂度为O(1),当然此刻依然是调用栈溢出,因为只是es6对尾递归进行了优化,你需要开启chrome实验室新功能。

function factorial(n, total) {
  if (n === 1) return total;
  return factorial(n - 1, n + total);
}
console.log(factorial(11300, 1));

第十一章 Proxy和Reflect

proxy 概述

proxy用于修改某些操作的默认行为,等同于在语言层面做一些事,所以这属于元编程。,即对语言进行编程。

const obj = new Proxy({}, {
  get(target, key, receiver) {
    console.log(`getting ${key}`);
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    console.log(`setting ${key}`);
    return Reflect.get(target, key, value, receiver);
  },
});

上面的代码对一个空对象做了一层拦截层,重定义了读取属性的set和设置set行为。这里暂时先不解释其具体做法。看看运行结果。

obj.count = 1;    // setting count
++obj.count;     // setting count, getting count

12.二进制数组

ArrayBuffer对象,TypedArray,DataView视图,是JavaScript操纵二进制数据的一个接口,
二进制数组由三个对象组成

  • 1.ArrayBuffer 对象:代表内存中的一段二进制数据,可以通过视图进行操作。视图部署了数组接口,这意味着,可以用数组的方法操作内存。(简单来说代表了原始的二进制数据)
  • 2.TypedArray视图:共包括9中类型的视图,比如Uint8Array(无符号8位整数)数组视图`,(用于读/写简单类型的二进制数据)
  • 3.DataView视图:可以自定义复合格式的视图,(用于读/写复杂类型的二进制数据)
数据类型 字节长度 含义 对应c语言
int8 1 8位带符号整数 signed char
Uint8 1 8位不带符号整数 unsigned char
Uint8C 1 8位不带符号整数 unsigned char
Int16 2 16位带符号整数 short
Uint16 2 16位不带符号整数 unsigned short
Int32 4 32位带符号整数 int
Uint32 4 32位不带符号整数 unsigned int
Float32 4 32位浮点数 float
Float64 8 64位浮点数 double

很多浏览器API用到了二进数组操作二进制数据,下面是其中的几个。

  • File API
  • XMLHttpRequest
  • Fetch API
  • Canvas
  • Websockets

12.1 ArrayBuffer 对象

概述

ArrayBuffer对象代表储存二进制数据的一段内存,他不能直接读/写,只能通过视图来读写。视图的作用是指以指定格式解读二进制数据。
ArrayBuffer也是一个构造函数,可以分配一段可以存放的数据的内存区域。
下面这段代码生成了一段32字节的内存区域,每个字节的默认值都是0,可以看到ArrayBuffer构造函数的参数是所需要的内存大小(单位位字节)。

var buf = new ArrayBuffer(32);
buffer
var buf = new ArrayBuffer(12);
buffer

image
为了读写这段内存,需要为它指定视图。创建DataView视图。需要提供ArrayBuffer对象实例作为参数

var buf = new ArrayBuffer(32);
dataView = new DataView(buffer); 
dataView.getInt8(16)    // 0

上面代码对一段32字节的内存建立DataView视图,然后以不带符号的8位整数格式读取第16个元素得到0,因为他的所有都是0。

12.2 TypedArray视图

ArrayBuffer对象作为内存区域存放多种类型数据。同一段内存不同数据的不同解读方式,这就叫做视图。ArrayBuffer存在2种视图,一种是TypedArray视图,另一种是DataView视图,前者所有数组成员都是同一个数据类型吗,后者数组成员可以是不同的数据类型。

13.章 set 和map

set作为es6新的一种数据结构,类似于数组,但是他的成员的值是唯一的。没有重复的值。
set的一些方法如下
image
set.add({})不断添加{},因为{} === {} // false
image

WeakSet

和set类,但不同之处在于

  • 1.只能添加对象
  • 2.弱引用,如果其他对象不再引用,它会自动启用垃圾回收机制。同时他是不可遍历的。

Map

map结构的目的和基本用法。

插一道面试题。

目前存在3个ajax访问,当url1和url2同时访问某个接口,只有当两个接口同时拿到数据的时候,url3开始执行去拿某个接口的数据。url1,和url2拿数据的速度未知。可能url1更快,也可能url2更快。只有当2个接口的数据都拿到之后才去拿url3的数据。闲话不多说,上代码

const p1 = new Promise((resolve) => {
  setTimeout(() => {
    console.log('p1');
    resolve();
  }, 2000);
});
const p2 = new Promise((resolve) => {
  setTimeout(() => {
    console.log('p2');
    resolve();
  }, 3000);
});
const p3 = new Promise((resolve) => {
  setTimeout(() => {
    console.log('p3');
    resolve();
  }, 1000);
});
Promise.all([p1, p2]).then(() => {
    console.log('values');
    p3;
});

14章lterator 和 for循环

遍历器(lterator)就是这样一种接口机制,为各种不同的接口机制提供统一的访问机制。任何数据结构只要有lterator接口,就能遍历循环。
lterator的作用有三种,

  • 为各种数据提供统一的简便的访问接口,
  • 使得数据结构的成员依靠某种次序依次访问。
  • es6创造了一种新的遍历命令------for...of循环

lterator的遍历过程是这样的,

  • 1.创建一个指针对象,指向当前数据结构的起始位置。也就是说遍历器对象本质上就算一个指针对象。
  • 2.第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
  • 3.第二次调指针对象,指针就指向数据结构的第二个成员。
  • 4.不断调用指针对象的next方法,知道他指向数据结构的结束位置。

每一次调用next方法,都会返回数据结构当前成员的信息。下面定义一个makeIterator函数用来遍历循环

function makeIterator(array) {
  let nextIndex = 0;
  return {
    next() {
      return nextIndex < array.length ? {
        value: array[nextIndex += 1], done: false,
      } : {
        value: undefined, done: true,
      };
    },
  };
}
const itt = makeIterator(['a', 'b', 'b', 'b', 'b', 'b', 'b']);
itt.next()   // { value: 'b', done: false }
itt.next()   // { value: 'b', done: false }
itt.next()   // { value: 'b', done: false }
itt.next()   // { value: 'b', done: false }

14.3 调用Iterator调用的默认场合

[...new Array(3)]    // [undefined,undefined,undefined]
[...'23r3r23r']        // ['a','b'....]

还有菊花函数 function* (){ yield [1,2,3,4,5] }

const generator = function* () {
  yield 1;
  yield* [2, 3, 4];
  yield 5;
};
const iterator = generator();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
======得到的值======
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: 4, done: false }
{ value: 5, done: false }
{ value: undefined, done: true }

上面的[]数组内部内容被自动解构,他后面如果跟的是一个可遍历的结构,他会被用于遍历器的接口。

其他场合
  • for...of
  • Array.from()
  • Map()。Set()。WeakSet()。
  • Promise.all()
  • Promise.race()

14.4 字符串的Iterator接口
字符串本身其实也可以遍历,他也具有遍历接口

str = '23r23r'[Symbol.iterator]();
str.next();  // {value: "2", done: false}
str.next();  // {value: "3", done: false}
str.next();  // {value: "r", done: false}
str.next();  // {value: "2", done: false}
str.next();  // {value: "3", done: false}
str.next();  // {value: "r", done: false}
str.next();  // {value: undefined, done: true}
[...'wefe233']  // ["w", "e", "f", "e", "2", "3", "3"]

14.6 遍历器对象的return()。throw()

Set 和 Map 结构

Set 和 Map结构具有Iterator接口,可以直接使用for...of循环

const es6 = new Map();
es6.set('eee',1);
es6.set('fff',1);
es6.set('ggg',1);
es6.set('hhh',1);
es6.set('iii',1);
for(var [name,value] of es6){
    console.log(name,value)
}
// eee 1
// fff 1
// ggg 1
// hhh 1
// iii 1

16 章 Promise

下面then会生成2个promise,先new一个promise,then里面再生成一个新的promise,通过不断的生成一个新的promise,而产生链式调用,这个和jQuery是不谋而合的。

const getJsono = new Promise((resolve, reject) => {
  resolve(x + 1);
});
getJsono
    .then((json) => {
	console.log(json);
    })
    .catch((err) => {
	console.log(err);
    });

promise.all

多个rpomise一起完成。
在进行下一步。
下面请看async/await,虽然他也只是基于Generator的语法糖,但是配合promise少不了它。

17章 async和异步操作。

异步编程对于js这门语言非常重要。js是单线程,如果没有异步,就会卡死。
es诞生之前,异步编程大概有以下几种方案

  • 回调函数
  • 事件监听
  • 发布订阅
  • promise函数

异步

所谓异步就算将一段任务分成两段,先执行一段,然后转而执行其他的任务,等好之后,又回过头来重新执行之前的代码。
下面的就算以回调的方式实现异步代码。但是他会先 输出 123,然输出文件内容,这是典型的异步代码,但是async/await绕来绕去,原本js阻塞的同步代码被写成异步,现在async/await有将他们变成同步的 阻塞代码,醉醉醉。同步=>异步=>同步

fs.readFile('./test/index.html', 'utf-8', (err, data) => {
    console.log(data);
 });
console.log('123');

callback function 回调函数

我个人觉得这种渣翻译非常容易引起误解,所谓回调函数就算把第二段单独写入一个函数中,等到重写执行该函数的时候重新调用,所以callback直译过来应该是重新调用的意思,而不是所谓的回调函数。
image

promise

回调函数本身并没有问题,问题在于多个回调函数嵌套,假定读取A文件后再读取B文件,代码如下

fs.readFile('./test/index.html', 'utf-8', (err, data) => {
	fs.readFile('./test/index.html', 'utf-8', (err, data1) => {
		console.log(data, data1);
	});
});

为了避免多重嵌套,所以出现了异步函数。然而我觉得这个好失败。怪不得TJ大神要转去玩GO,
17.2 Generator 函数

协程

传统的编程语言早已有异步解决方案,(其实是多任务的解决方案)。其中有一种叫做协程(coroutine),意思是多个线程相互协作,完成异步任务。
协程有点像函数,又有点像线程,流程如下:

  • 第1步,协程A开始执行
  • 第2步,协程A执行到一半,暂停,执行权交给B去执行。
  • 第3步,一段时间后,暂停,协程B交换执行权。
  • 第4步,协程A恢复执行。

上面的协程A就算异步任务。因此他分成2段执行。

Generator函数的概念

他会将函数执行权交出去,即暂停执行。需要暂停的地方用yield,

function* gen(x){
    var y = yield x+2;
    return y;
}
var g = gen(1);
g.next();    // {value:3,done,false}
g.next();    // {value:undefined,done,true}

上述代码,调用Generator函数会返回一个内部指针,g,这是生成器函数不同于普通函数的地方,他返回的指针每次调用next都会指向下一个yield,也就是返回一个对象。
Generator可以暂停执行,这是它可以封装异步函数的根本原因。

关于Generator中的 then,他返回的是什么??,async返回的then呢?

  • next 返回下一个数
  • return 推出迭代
  • throw 返回错误
  • Symbol(Symbol.iterator) 说明构造器Generator函数是可迭代的。
    image

关于Promise

  • then.每一个then都返回一个promise,从而实现链式调用
  • finally 回调会在当前promise运行完毕后被调用,无论当前promise的状态是完成(fulfilled)还是失败,
  • catch 捕捉错误。
    image
Promise.all(),和Promise差不多。

image

17.3 Thunk 函数

参数的求值策略

传入的是一个函数,那么传入的函数等到传入的一刻才计算。尾递归就是一个例子,但是尾递归存在栈溢出的性能问题。

17.4 co模块

#####基本用法
co模块是TJ发布的一个小工具。用于generator函数自动执行。

var gen = function* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

co模块可以让你不用编写Generatar函数的执行器。

var co = require('co');
co(gen);

上面的代码中,Generator函数只要传入co函数就会自动执行。
co函数返回一个Promise对象,因此可以用then方法添加回调函数。

co(gen).then(function(){
  console.log('Generator 函数执行完成');
})

上面的代码中,等到Generator函数执行结束,就会输出一行提示。
####co模块的原理
为什么co模块可以自动执行Generator函数?
前面说过,Generator就是一个异步执行函数的容器。它自动执行需要一种机制,当异步操作有了结果能够自动交回执行权。

  • 1.回调函数。将异步操作包装成Thunk函数,在回调函数中交回执行权。
  • 2.Promise对象。将异步操作包装成promise对象,用then方法交回执行权。

co模块其实就是将两种自动执行器(Thunk函数和Promise对象)包装成了一个模块。使用co的前提条件是,Generator函数的yield命令后面只能是Thunk函数或Promise对象。
下面讲一下基于generator函数的自动执行器。这是理解co函数的关键。
####基于Promise对象的自动执行
还是沿用上面的例子。首先把fs模块的readFile方法包装成一个Promise对象。

const readFile = fileName => new Promise(((resolve, reject) => {
  fs.readFile(fileName, (err, data) => {
    if (err) reject(err);
    resolve(data);
  });
}));
const gen = function* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};
const g = gen();
g.next().value.then((data) => {
  g.next().value.then((data) => {
    g.next(data);
  });
});

手动执行的方法其实就算用then方法层层添加回调函数,理解这一点,就能写出自动执行函数,

function run(gen) {
  const g = gen();
  function next(data) {
    const result = g.next(data);
    if (result.done) return result.value;
    result.value.then((data) => {
      next(data);
    });
  }
}

上面的代码中,只要Generator函数还没执行到最后异步,next函数就调用自身以实现自动执行。
####co模块的源码
co就是上面这个自动执行的扩展,他的源码只有十几行,非常简单。
首先,co函数接受Generator函数作为参数,返回一个Promise对象。

function co(gen){
  var ctx = this;
  return new Promise(function(resolve,reject)=>{})
}

在返回Promise对象中,co先检查参数gen是否为Generator函数。如果是,就执行该函数,得到内部指针对象;如果不是就返回,并将promise对象的状态改为Resolve。

function co(gen) {
  const ctx = this;
  return new Promise((resolve, reject) => {
    if (typeof gen === 'function') gen = gen.call(ctx);
    if (!gen || typeof gen.next !== 'function') return resolve(gen);

    onFullfilled(res);
    function onFullfilled(res) {
      let ret;
      try {
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }
  });
}

最后最关键的就算next函数

function next(ret) {
  if (ret.done) return resolve(ret.value);
  const value = toPromise.call(ctx, ret.value);
  if (value && isPromise(value)) return value.then(onFullfilled, onRejected);
  return onRejected(new TypeError(`You may only yield a function, promise, generator, array, object, but not the following object was passed: ${String(ret.value)}"`));
}

上面代码中,next函数内部的代码一共只有4行命令。
第一行,检查当前是否为Generator函数最后一步,如果是就返回。
第二行,确保每一步的返回值是Promise对象。
第三行,使用then方法为返回值加上回调函数,然后通过onFullfilled函数再次调用next函数。
第四行,在参数不符合要求的情况下,将promise对象状态改为Rejected,从而终止执行,

并发处理异步操作

co支持并发的异步操作,即允许某些操作同时进行,等到他们完成,才进行下一步。这时要把并发的操作都放在数组或者对象里面,跟在yield语句后面。

// 数组写法
co(function* () {
  const res = yield [
    Promise.resolve(1),
    Promise.resolve(2),
  ];
});

// 对象写法
co(function* () {
  const res = yield {
    0: Promise.resolve(1),
    0: Promise.resolve(2),
  };
});

// 下面是一个例子
co(function* (){
	var values = [n1,n2,n3];
	yield values.map(somethingAsync);
})

function* somethingAsync(x){
	// do something async
	reyurn y;
}

上面代码允许并发3个somethingAsync异步操作,等到他们全部完成,才能进行下一步。

17.5 async 函数

他就是Generator函数的语法糖
前文有一个Generator函数,一次读取2个文件

const readFile = fileName => new Promise(((resolve, reject) => {
  fs.readFile(fileName, (err, data) => {
    if (err) reject(err);
    resolve(data);
  });
}));
const gen = function* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};
//写成async函数就是下面这样
const asyncReadFile = async function () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

其实就只是将*号换成async,将yield换成await的语法糖。
async函数对Generator函数进行了改进,以下4点体现

  1. 内置执行器。Generator必须要有执行器,而async内置了执行器。所以有了co模块,而async自带执行器,async函数写法与普通函数一样只要一行。
var result = asyncReadFile();

2.上面代码调用了asyncReadFile函数,然后他就会自动执行,输出结果,完全不想Generator函数需要调用next方法,或者co模块,才能执行。
3.更好的语义。async和await比起yield语义更加清楚。
4.更广的适用性,co模块约定,yield命令后面只能是Thunk函数或者promise对象,而async函数的await后面能是promise对象和原始类型的值,无论数字对象字符串等。
5.返回值Promise。async函数的返回值是Promise对象,这比Generator函数的返回值是Lterator对象方便多了,你完全可以用then方法指定下一步。
进一步说明,async函数完全可以看作由多个异步操作包装成一个Promise对象,而await命令就算内部then语法糖。

async函数的实现

async函数的实现的实现就是把Generator和自动执行函数包在一个函数中。

async function fn(args){
    // ....
}
// 等同于
function fn(args){
    return spawn(function*(){
        // ...
    });
}

35条优化网站的建议

1.减少http请求

80%的页面初始化响应时间花费在前端,而大部分时间都是在下载所有的页面组件,例如图片,css,js等。所以减少请求数量是让网页变快的关键。
其中一种方法是将页面设计的更简单,但是有没有方法是能既将页面设计得丰富多彩,同时又拥有高性能呢?线面提供一些建议:

  • 合并文件 是一种方法来减少http请求,我们可以将多个js文件合并成一个js文件,css合并,但是合并通常会带来一些不可预知的后果,因此,对于css:less + css-module 是一种不错的组合。js =>模块化开发。rollup.js => 浏览器环境打包成iife格式,nodejs环境打包成cjs格式,通用node+浏览器 打包成umd格式。至于commonjs不了解。
  • css 雪碧图是一种不错的方法来减少图片请求。将多个小图标他们合并成单个背景图片是一种不错的方法,但是现在推荐svg。
  • 在线图片将图片转换成data64格式直接写入页面。会增加页面体积,一般情况不推荐使用,仅仅当你想做一次性的图片展示的时候推荐使用,例如input上传图片展示,这个时候推荐使用。自定义鼠标图标,直接将它写入css文件,推荐使用data64直接写入css文件。
    减少http请求次数仅仅只是开始。同时他也是提高用户初次访问时候页面性能的重要知道方针。

2.使用内容交付网络。(参照七牛cdn静态资源加速)

用户与web服务器的物理距离对响应时间有影响。因此同时布置多个web服务器分布在华南,华北,华中,深圳,上海,等集中地区,可以加速,但是你又应该从哪开始呢?

  • 第一步。作为实施物理距离分散的第一步,劝你不要试图重新设计你的web应用来使得它以分布式来架构,根据应用程序的不同,更改结构体系可能包括艰巨的任务。例如 sync session state,又或者是复制数据库等。。。尝试缩短用户和内容之间的距离,可能会造成结构体系等的延迟,或者根本通不过。(个人经验-七牛cdn静态资源,加速后,原本页面未经过压缩有1M+,首次加载需要7s+,使用七牛cdn加速后,1.5s即加载成功,但同时,每次更新页面,都必须要去七牛后台个人中心页面手动更新缓存我的七牛空间页面文件缓存,或许可以通过代码来更新资源,但我又不懂。这仅仅只是使用静态页面就变得这么麻烦,如果有数据库等,又要怎么做?)
  • 80%-90%的用户响应加载页面的时间都消耗在加载js,css,image,flash等资源文件。分解你的静态资源内容相对于重构页面架构更容易实现。这个不仅仅是实现了响应时间缩短,而且由于CDN(content delivery networks)的存在,这更加容易实现。
    image
  • CDN(content delivery networks)是一个分布在不同位置的网络服务器集合,用来更高效的向用户提供内容。有限选择ping值最低的来提供服务。
    image
  • 有些大公司有自己的cdn,但更高效的方式是用CDN服务供应商提供的服务(七牛cdn)。例如Yahoo.

3.添加Expires或者Cache-Control

image

2个规则

  • 对于静态组件,设置超长过期时间。
  • 对于动态组件,使用适当的Cache-Control来帮助浏览器提供有条件的请求。
    我们的网站设计得日益复杂,这意味着更多的静态资源,但是通过Expires的设置,可以避免多余的请求,通常公司图标需要这样设置,但是其实我们对于每一个资源都需要设置过期时间。
    浏览器和代理商使用缓存来减少http请求,使得页面加载更快,
      Expires: Thu, 15 Apr 2018 20:00:00 GMT                  // 在2018年4月15日8点过期
如果你的服务器是Apache
      ExpiresDefault "access plus 10 years"                       // 10年后过期

如果设置了超长过期时间,那么更新文件只能通过 更改文件后缀的hash值来更新。

4.Gzip Components 组件压缩(服务器端)

尤其对于单页面应用(SPA),这种将所有页面打包在一起,通常可以压缩至1/3.
image
image
HTTP/1.1开始,web客户端通过请求的头部添加Accept-Encoding来告诉浏览器要压缩。

      Accept-Encoding: gzip, deflate

如果浏览器又看到这个的话,会做出如下响应

      Content-Encoding: gzip

image
Gzip是现在用的最多的高效小压缩的方法。它可以压缩70%,90%的浏览都经过Gzip压缩。如果你是Apache服务器,1.3版本是mod_gizp, 2.x版本用mod)deflate.,,
但是值得注意的是,压缩要基于文件类型,像html,js,css,json文件都需要压缩,但是图片和pdf是不需要压缩的,因为他们本身就经过压缩了,再去压缩他们只会浪费CPU,而且压缩后文件体积反而会增大,

5.将css样式文件放在顶部

Yahoo!性能研究,我们发现将css文档放在<head></head>种,页面会更快加载,具体请参照bootstrapt Starter template

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">

    <title>Hello, world!</title>
  </head>
  <body>
    <h1>Hello, world!</h1>
    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
  </body>
</html>

前端工程师在意页面性能,所以想要页面逐步加载,这也是Yahoo!工程师想要的,这就是,我们想要浏览器更快显示他的内容,这对于网速慢的用户尤其重要,当浏览器逐步加载页面的时候,标题导航栏顶部徽标最为等待页面的用户的视觉返回,这大大改善用户整体体验。而如果你将css文件放在文档底部的话,无法做到内容渐进式呈现,例如IE,这样子的话,页面会停留在空白页。
嗯~ o( ̄▽ ̄)o,HTML规范文档也是这么要求的。

6.将js放在文档底部

js文件如果异常报错,会阻止同步的下载内容,一般建议延迟加载,给<script>标签添加defer属性,不过firfox又不支持,如果将脚本放在底部,并添加defer属性延迟加载,页面加载速度更快。

7.避免css 表达式

css表达式是一种强大(危险)的方式动态设置css属性,适用ie5-ie8,他可能会在极短的时间内执行1w次,

      background-color: expression( (new Date()).getHours()%2 ? "#B8D4FF" : "#F08A00" );

8.是否要将js,css 和html文件分离

通常html文档都会下载,而css和js则会从缓存中加载。如果分离开,则html依然要下载,但是体积会减小。这个关键在于,用户从缓存中拿文件和重新加载新文件哪种情况更多。一种可以减少请求次数,另一种可以html文件体积。但是也有例外情况,比如Yahoo!百度的首页的css和js就是内嵌的,因为大多数这种首页需要极速访问,也经过实际调研,也确实如此。这种会话只有很少的页面视图的主页,内嵌可以缩短最终用户的响应时间。
image
通常对于许多网站他们的第一个首页,有一些技术可以减少内联提供的http请求,以及通过外部文件实现缓存优势,其中一种方法,就是在首页内嵌js和css,但是在页面加载完成后,动态下载外部文件,后续页面将引用应该以及存在浏览器缓存中的外部文件。

9.减少DNS(Domain Name System)查找。

DNS将主机名映射到IP地址,就像查询人的手机号码一样,但是DNS映射是需要20-120ms才能找到的,所以DNS会缓存他之前的查找,例如chrome浏览器就有自己的DNS缓存,IE默认存储30分钟DNS缓存,减少唯一主机名数量,可以减少页面中查找DNS的并行下载时间。

10.压缩js和css

通常删除注释,以及空白字符,换行符,制表符(\n\t\s),这个webpack,grunt,gulp,parcel等打包工具都可以做到,同样的,html标签之间的空白,幽灵节点,同样可以通过压缩消除。

11.避免重定向(Redirects)

例如nginx通常就将http => https,重定向是使用301和302状态码完成的,

      HTTP/1.1 301 Moved Permanently
      Location: http://example.com/newuri
      Content-Type: text/html

浏览器会自动将用户带到位置字段中指定的URL。重定向所需的所有信息都在标题中。响应的主体通常是空的。尽管名称不同,301和302响应在实践中都不会被缓存,除非额外的头文件(例如Expires或Cache-Control)指示它应该是。元刷新标记和JavaScript是将用户引导到不同URL的其他方式,但是如果您必须执行重定向,首选方法是使用标准的3xx HTTP状态代码,主要是为了确保后退按钮正常工作。
重定向会减慢用户体验,并且在HTML页面到来之前不会下载任何内容。
最浪费的重定向经常发生,web开发人员通常不知道例如当你访问https://www.baidu.com会发生重定向https://www.baidu.com/,然后默认加载https://www.baidu.com/目录下的index.html文件。
将就网址链接到新网站,是重定向最常见的用途,创建一个CNAME文件在被定向的url目录下。

12.删除重复的js文件,

13.设置 ETags

实体标签ETags唯一标识符

      HTTP/1.1 200 OK
      Last-Modified: Tue, 12 Dec 2006 03:03:59 GMT
      ETag: "10c24bc-4ab-457e1c1f"
      Content-Length: 12195

14.将ajax数据实现可缓存

Ajax引用的好处之一,他想用户提供返回,因为它从后台请求数据。为了提高性能,优化Ajax响应非常重要,提高Ajax性能的最重要的方法就是将响应缓存,如添加过期或缓存控制头中所属,其他规则同样适用,

15.尽早清洗缓存

当用户请求页面时,后端服务器可能需要200到500毫秒才能将HTML页面拼接在一起。在此期间,浏览器在等待数据到达时处于空闲状态。在PHP中,你有函数flush()。它允许您将部分就绪的HTML响应发送到浏览器,以便浏览器可以在您的后端忙于HTML页面的其余部分时开始获取组件。好处主要出现在繁忙的后端或前端。
考虑刷新的好地方就在HEAD之后,因为头部的HTML通常更容易生成,并且允许您在浏览器中包含任何CSS和JavaScript文件,以便在后端仍在处理时开始并行读取。

例子

      ... <!-- css, js -->
    </head>
    <?php flush(); ?>
    <body>
      ... <!-- content -->

16.尽量使用Get请求

Yahoo! 团队发现,当使用ajax XMLHttpRequest,的时候,
POST实现浏览器,分为2个步骤,

  • 1.发送headers,
  • 2.再发送data
    POST相对GET的不同,
  • 1.POSTGET更安全;
  • 2.发送的数据量更大(因为url长度有限制);
  • 3.发送的数据类型更多,例如图片转化为二进制Blob格式,
  • 4.get会将数据缓存,post不会(在chrome里面,get会将静态资源缓存,而json数据不会缓存,ie则将全部缓存)

但是post更慢,
post请求过程

  • 1.浏览器请求tcp链接(第一次握手)
  • 2.浏览器答应tcp连接(第二次握手)
  • 3.浏览器得到确认,就将header头部发送给后台,(第三次握手,这个请求较小)
  • 4.浏览器返回100 continue响应
  • 5.浏览器开始发送数据
  • 6.服务器返回200 🆗响应
    get请求过程
  • 1.浏览器请求tcp链接(第一次握手)
  • 2.浏览器答应tcp连接(第二次握手)
  • 3.浏览器确认,并发送get的header头部,以及data数据(第三次握手,这个报文比较小)
  • 4.服务器返回200 🆗响应
    所以,最优使用get请求,当然如果你的cookie比较多又另算,如果仅仅只是想要得到数据,用get。

17.后加载组件

你可以看一下你的页面,哪些是为了呈现最初页面,必须要呈现的组件,而其余的组件可以稍后加载。

js在onload事件之后比较适合初始化某些库,原生js的onload事件和jQuery的$( document ).ready()有区别,jquery会在js文件加载完就执行,而原生onload事件,则要等到dom里面的图片全部加载好才能触发事件。

18.预加载组件

他是后加载组件的对立面,它可以节省时间

    1. 无条件预加载
  • 2.有条件预加载

19.减少DOM元素数量

复杂dom结构,js访问速度更慢,要遍历更多元素。

20.拆分组件跨域

比如说你的图片网站html存在https://www.example.com/,而你的静态资源,储存在http://static.example.com/,这样可以让你最大限度的平行下载,由于DNS查找惩罚,请确保域不超过2-4个。

21.减少ifrane'数量

iframes允许一个HTML文档插入父文档,了解iframe如何工作以便可以有效使用,这很重要。

<iframe>优点:

  • 像徽章和广告等第三方内容,例如github的徽章
  • 秘密沙盒
  • 并行脚本下载

<iframe>缺点:

  • 即使是空白页面也很昂贵
  • 阻止页面载入
  • 非语义化

22.不要404s

HTTP请求比较昂贵,因此提出http请求获得无用的响应完全没有必要。而有些网站提供了404前台页面,用户体验不错,但也浪费了服务器资源,特别糟糕当外部错误并是404时候,首先此下载会阻止并行下载,浏览器会尝试解析她,就好像他是js代码。

23.减少cookie体积

http cookie被用来做用户验证,以及其他个性化东西,信息关于cookie可以在服务器和浏览器之间交换,降低cookies大小可以减少用户响应时间,这很重要。
请注意以下四点

  • 消除不必要的cookie
  • 尽量减少cookie大小
  • 设置cookies域名,保证其他子域名不受营销
  • 设置过期时间

24.为组件使用无cookie域名

当浏览器制作一个请求,比如请求一个图片,这种完全没有cookies的必要。所以你要专门设置一个托管静态资源的子域名。

25.最小化dom访问

js遍历dom很慢,请注意以下三点:

  • 缓存对访问元素的引用
  • 更新节点 ’offline‘,并将他们加入到树中。
  • 避免使用js修复布局坍塌

26.发展智能事件处理

事件总代理,比如你有10个按钮的导航栏,不要分别给他们设置事件触发。请给他们的父容器设置总代理,对e.path做路径判断,ie没有path,请自行合成path,这个就是事件冒泡。
你不需要等到onload事件触发在添加事件代理,应为只有图片成功加载后才出发onload事件,你可以用jq的ready,或者原生的DOMContentLoaded

<script>
  document.addEventListener("DOMContentLoaded", function(event) {
      console.log("DOM fully loaded and parsed");
  });
</script>

27.选择<link>而不要@import

css放在最顶端,

28.避免filters这个css属性

29.优化图片

当设计师完成图纸,将图片传到web服务器之前,仍可以进行一些操作

  • 您可以检查GIF并查看它们是否使用与图像中颜色数相对应的调色板大小。使用imagemagick很容易检查使用 标识-verbose image.gif 当您在调色板中使用4色和256色“插槽”看到图像时,还有改进的空间。
  • 尝试将GIF转换为PNG并查看是否有保存。往往不是,有。由于浏览器支持有限,开发人员经常不愿意使用PNG,但这已成为过去。唯一真正的问题是真彩色PNG中的alpha透明度,但再次,GIF不是真正的颜色,也不支持可变透明度。因此GIF可以做的任何事情,调色板PNG(PNG8)也可以做到(动画除外)。这个简单的imagemagick命令会生成完全安全的PNG:
  • 在所有PNG上运行pngcrush(或任何其他PNG优化器工具)。
  • 在所有JPEG上运行jpegtran。该工具可以执行无损JPEG操作,例如旋转,也可用于优化和删除图像中的注释和其他无用信息(如EXIF信息)。

30.优化css 精灵

  • 将水平方向上的图像与垂直方向进行排列通常会导致文件较小。
  • 在精灵中组合相似的颜色可帮助您将颜色数量保持在较低的水平,理想情况下在256色以下,以适应PNG8。
  • “适合移动设备”并且不要在精灵图像之间留下很大的空白。这不会影响文件大小,但需要较少的内存供用户代理将图像解压缩为像素映射。 100x100图像是10万像素,其中1000x1000是100万像素

31.不要用scale缩放图片在html

请设置width或者height来缩放图片

32.将favicon.ico变得更小,可储存。

浏览器每次都会请求他,所以最好不要回应404,最好缓存它。
请注意以下3点

  • 保持它在1k以下。
  • 设置Expire 在headers里面,最好几个月以上。

33.保持组件低于25k

为什么呢?因为iphone不会缓存25k以上的组件。

34.将组件打包成多部分文.

35.避免空的img.src

为什么下述行为有害?

  • 通过发送大量意外流量来瘫痪您的服务器,特别是对于每天获得数百万页面浏览量的页面。
  • 浪费服务器计算周期生成一个永远不会被查看的页面。
    空的img.src会毁坏您的网站

参考:

Outh2.0

前言

第三方登陆验证

高性能Javascript阅读笔记

第六章:快速响应的用户界面

没有比页面点击没有响应更能令用户烦心的事情了。JavaScript是单线程,js脚本在解析过程中,用户点击界面没有响应。
下面这个例子,用户点击按钮的时候,他会触发UI触发两个进程,一个考虑css有没有添加:active等伪类属性,如果有就改变它的样式,另一个进程是js进程,即如下位于<script>标签内的代码,js代码创建一个新的div元素最后将他appendChild到body里面,这个时候会引发页面重绘+重排,进程如图
image

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
</head>
<body>
  <button onclick="handleClick()">click me</button>
  <script>
    function handleClick (){
      const div = document.createElement('div');
      div.innerHTML = 'Clicked!'
      document.body.appendChild(div)
    }
</script>
</body>
</html>

定时器基础(从定时器的角度去了解浏览器页面进程,GUI线程以及js线程和setTimeou线程以及事件线程组成的线程执行的先入后出的执行顺序)

对于js来说,多久运行时间算久,事实证明,超过100ms的都会引起用户的操作不适应,因此超过100ms就算久,那么应对业务需要,定时器应用而生。
下面这段代码,将在250ms后在UI队列中插入一个执行test()函数的JavaScript任务。

function test(){
  console.log('settimeout');
}
setTimeout(test,250);

总之,任何情况下创造一个定时器会造成UI线程暂停,因此定时器会重置所有有关浏览器的限制,包括长时间运行的脚本定时器,调用栈也在定时器的代码中重置为0,这一特性使得定时器成为长时间运行JavaScript代码理想的跨浏览器解决方案。setTimeoutsetInterval几乎相同,除了前者重复添加js任务到UI队列中,。他们最主要的区别就是如果UI队列中已经存在由同一个setInterval创建的任务,那么后续任务不会被添加到UI队列中。
尝试运行如下代码

setInterval(()=>console.log(1),0);

进程,线程简单说明
JavaScript是单线程的,这个是由于为了防止一个dom元素被绘制的时候同时又被修改
js单线程以及css对页面的渲染,这两个呈现互斥关系,css渲染则js被暂时挂起,js运行的时候,css渲染又被挂起,他们共同在UI多线程中来回切换,按顺序执行,其中还有setTimeout的定时器线程,setTimeout是独立于js单线程之外的一个线程,因此js在由setTimeout的延迟执行的时候才不会被阻塞后面的代码继续执行,这样子的js配合另一个setTimeout线程才能实现异步函数Promisse等的能力,
一般情况下浏览器至少有三个常驻线程(前三个)

  • GUI渲染线程(渲染页面)
  • JS引擎线程(处理脚本)
  • 事件触发线程(控制交互)
  • setTimeout(延迟执行)
  • AJAX线程(后台交互)

从下面这段代码了解js单线程与setTimeout线程执行关系,

setTimeout(function(){
    console.log('timer');
}, 0);
console.time('js耗时:')
for(var i = 0; i < 1e4; i++){
    console.log(1);
}
console.timeEnd('js耗时:')

image
为什么这样呢?
因为setTimeout已经被放入另外一个线程中执行,所以定时器的存在才不会对js单线程造成阻塞。所以setTimeout的延迟时间无论被设置成多少,他都会被放在js这个线程之后再执行。也许你会怀疑,因为某些浏览器默认setTimeout执行时间默认最少16ms,假设我将setTimeout后面的js代码执行1w次循环,浏览器耗时7s+才循环完毕。
但看上图我截图的,timer依然在他之后输出。这就说明了setTimeout必然是被放在了另外一个独立于js线程之外的线程,只有在js线程执行完毕后才回去执行setTimeout线程,(chrome的每一个网页都是一个独立的进程,因此才会在一个网页卡死后,另一个网页照样过可以流程运行打开,同时也防止了不同网页之间的相互干扰,因为不同进程之间在chrome里面被保护以至于他们之间相互隔离)

同样的道理,通过上面的例子可以看出,定时器所设置的延迟时间 的计算规则,它是从整一段js代码执行完毕之后开始计算的(包括我那段用了7s来循环一万次的代码,);定时器通过浏览器的时间戳来在js主线程代码执行完毕后延迟0s再执行。所以任何不报错代码但是执行时间很长的代码放入setTimeout中不会造成js阻塞。

事件循环,

通常发生在当你为浏览器滚动添加事件监听的时候,浏览器会高频触发事件,但是同样的道理,事件监听里面的代码也是被放入另一个事件触发线程之中,只有当你document触发屏幕滚动事件的时候才会触发这段代码,这和setTimeout有点不一致,但是和他的兄弟setInterval(间隔固定时间执行js代码)却很类似。他们都和js主线程相独立开来。

Node.js的Event Loop

image

hahah上面这张图好有意思,v8威武

这个另外写,你也可以看阮一峰的再谈Event Loop,或者nodejs官方介绍,也许以后我会再写吧。毕竟我自己也要去搞nodejs

document.addEventListener('scroll',()=>{
  console.log('scroll')
},false)

web Workers

web worker 的运行环境,由以下组成

  • navigator对象,4个属性,appName,appVersion,userAgent和platform。
  • 一个location对象,
  • 一个self对象,它指向window
  • 一个importScript()方法,用来加载worker所有用到的外部js文件(chrome v.65没看到)。
  • 所有ECMAscript方法,如Object,Array,Date等。
  • XMLHttpRequest构造器。
  • setTimeout() 和 setInterval()方法
  • 一个close()方法,他能立刻停止运行worker
    image

与web worker通信

他与网页通过实践接口进行通信 ,网页代码可以通过postMessage()方法给worker传递数据,

var work = new worker('code.js');
worker.onmessage = function(){
  alert('event data');
}
worker.postMessage('Nicholas');
// code.js内部代码
self.onmessage = function(event){
  self.postMessage('hello, ',+event.data+ "!");
}

最终的字符串在worker的onmessage事件处理器中构造完成。消息系统是网页和worker通讯的唯一途径。

worker加载外部文件,

worker内部由importScripts()方法加载外部文件,该方法可以同时接受多个文件。他的加载过程是阻塞的,但是由于worker在UI之外的线程运行,所以这种阻塞在这里却是优点,并不会造成UI响应,例如

// code.js内部代码
importScript('file.js','file2.js');
self.onmessage = function(){
  self.postMessage('hello, '+event.data+'!');
}

worker 的兼容性

// chrome 。。。。用不了,但看别人用的又没问题。。
image

worker 的实用价值,

原因,渲染dom的时候不能执行javascript代码,执行javascript代码的时候,UI界面会暂停响应。
那么和setTimeout相比,web worker脱离独立与UI进程,而setTimeout则被放入js接下来的UI线程,这个遵循先入后出原则,因此是会影响接下来的操作的,所以超过100ms的JSON解析都应该被放入web worker去解析,以免造成网页界面卡顿无响应现象发生。。。而任何小于100ms的代码都可以考虑放入setTimeout去执行,因为小于100ms,用户根本察觉不到,利用好setTimeout可以让js和谐运行,而运用好web worker则可以让UI线程中的队列和谐进行下去。
如果javascript代码执行时间很长,那么UI就会无响应,这就是所谓的页面卡死。Web Workers是 HTML5 提供的一个javascript多线程解决方案,我们可以将一些耗时的javascript代码交由web Worker运行而不冻结用户界面。也就是说web worker和UI界面是运行在不同线程中的。它可以被用来处理一些接近500kb的json文件数据,计算中。。。这种一般被放入另外的事件线程,要么放入setTimeout定时器线程,要么放入worker线程,因为他在UI线程之外,不影响任何web操作。

结论:js和用户界面更新在同一个进程中进行,因此一次只能处理一件事情。这就意味着js运行过程中,用户不能输入,高效管理UI进程就是要确保js不能运行太长时间。

  • 任何js代码都不应该超过100ms,过长会导致UI更新出现延迟,
  • js运行期间,浏览器响应用户交互行为存在差异,无论如何js长时间运行会导致用户体验变得混乱和脱节,
  • 定时器可以让代码延迟执行,它可以使得你的代码从长时间运行分解成一系列小的任务,
  • web worker 是新版浏览器支持的特性,它允许你在UI线程外部执行JavaScript代码,从而避免锁定UI。
    web应用越复杂,积极主动的去管理UI进程就越重要,即使js代码再重要,也不能影响用户体验。

第七章Ajax

ajax是高性能JavaScript的基础,

数据传输

在不刷新页面前提下获取后台数据,从而更新dom内容

请求数据

五种常用技术用于向服务器请求数据:

  • XMLHttpRequest(XHR)
  • Dynamic script tag insertion 动态脚本注入
  • iframe
  • Comet
  • Multipart XHR
    推荐使用XHR,动态脚本注入,Multipart XHR
    不推荐使用iframe,Comet

XMLHttpRequest(XHR)

这里由jquery封装的ajax,有fetch(es6),有(axios,jsonp)等package,
这里就不过多介绍。

get 和 post

get请求的数据会被缓存起来,而post则不会,只有当你get请求的参数长度超过2048个字符的时候才应该使用post,尽管post比get安全,

动态脚本注入

var scriptElement = document.createElement('script');
scriptElement.src = 'https://amy-domain.com/js/lib.js'
document.head[0].appendChild(scriptElement);

但是和XML请求不同的是,你不能通过它设置请求的头信息,并且,参数传递也只能是get而不是post,并且她只能是纯粹的js代码,不能是json或者XML或者其他任何数据,无论任何代码都只能通过script.onload调用他们,尽管如此,这种技术速度却非常快,但是当他是不可控的时候,就是某种hack行为,因为无论这段动态注入的代码可以让网页重定向到其他网站,将用户密码发送出去,或者追踪用户操作并将他们发送到第三方服务器上面,因此引入的如果是外部不可控的js代码请格外小心。

Ajax 性能指南

一旦你确定了选择某种数据格式来传输,(建议用Graphql),那么接下来考虑其他优化方式

缓存数据

最快的Ajax请求就是没有请求,下面两种方法都可以

  • 在服务端,设置http头信息以确保你的响应会被浏览器缓存(便于维护)
  • 在客户端,吧获取的信息储存到本地,从而避免再次请求(一般是localStorage)(给你最大控制权)
设置http头

如果你希望响应被浏览器缓存,那么用get请求,还必须响应中加入正确的header

Expires: Mon, 28 July 2014 23:30:00 GMT

第八章 编程实践

####避免多重求值
js提取一个包含代码的字符串有4中方法

  • eval(),这个太经典不用说
  • Function()构造函数
const sum = new Function('arg1', 'arg2', 'return arg1 + arg2');
console.log(sum(1, 2));
  • setTimeout()
var sum1 = 1;
var sum2 = 1;
setTimeout('sum = sum1 + sum2', 0);
  • setInterval()
var sum1 = 1;
var sum2 = 1;
setInterval('sum = sum1 + sum2', 0);

以上4中方法都比直接求值要慢很多,因为每次都要创建一个新的解释器/编译器

应该使用Objecty/Array直接量

这个自己领悟

避免重复

例如下面这段代码,同时兼容ie和chrome

function addHandler(target,event,handler){
  if(target.addEventListener){
    target.addEventListener(event,handler,false);
  }esle{
    target.attachEvent('on'+event,handler)
  }
}

应该避免多次检查addEventListener是否支持。
如下所示:初始化的时候就检测是否支持IE,

const isIE = target.addEventListener; 
function addHandler(target,event,handler){
  if(isIE){
    target.addEventListener(event,handler,false);
  }esle{
    target.attachEvent('on'+event,handler)
  }
}

延迟加载

第一种消除函数中重复工作的方法是延迟加载,意味着信息在被加载之前不会做任何事情,在函数调用之前,没有必要判断该用那个方法去绑定或者取消绑定事件,采用延迟加载的函数版本如下:

function addHandler(target, event, handler) {
  // 复写现有函数
  if (target.addEventListener) {
    addHandler = function (target, event, handler) {
      target.addEventListener(event, handler, false);
    };
  } else {
    addHandler = function (target, event, handler) {
      target.attachEvent(event, handler, false);
    };
  }
  addHandler(target, event, handler);
}

这个函数实现了延迟加载的加载模式,这两个方法第一次被调用中,先决定使用哪种方法调用,随后函数会被包含正确操作的新函数覆盖,最后一步调用新函数,随后每次调用addHandler()都不会再做检测,因为检测代码以及被新函数覆盖。
调用延迟加载函数的时候,第一次事件较长,因为它必须检测接着调用另一个函数完成任务,但随后调用函数会更快,因为不需要在做检测。当一个函数在页面不会立即调用执行时,延迟加载是最好的选择。

条件预加载

他会在脚本预加载期间提前检测,而不会等到函数被调用,检测操作依然只有一次,只是他在过程中来的更早,例如:

const addHandler = document.body.addEventListener ?
  function (target, event, handler) {
    target.addEventListener(event, handler, false);
  } :
  function (target, event, handler) {
    target.attachEvent(`on${event}`, handler);
  };

这个例子先检查addEventListener()是否存在,然后根据结果指定选择最佳函数,如果他们存在的话,三元运算符号,返回指定函数,这个三元函数在一开始就判断了,所以称之为条件预加载,
条件预加载确保所有函数调用消耗的时间相同,其代价是需要在脚本加载时就检测,而不是在加载后。条件预加载适用于一个函数马上就要用到,并且在整个页面的生命周期中频繁出现的场合。

使用速度快的部分

尽管js经常被指责运行慢,但它这个语言在某部分运行速度快的让人难以置信。

位操作符
1&2 //两个操作数对应位都是1,该位返回1
1|2 //两个操作数对应位一个是1,该位返回1
1^2 //两个操作数对应位只有一个是1,该位返回1
~1    // 遇0则返回1,反之亦然
const newArr = [1, 2, 3, 4, 5, 6].map((arr, i) => {
  if (i & 2) {
    return 'event';
  }
  return 'odd';
});
console.log(newArr);

这样以上代码可被改写

const newArr = [1, 2, 3, 4, 5, 6].map((arr, i) => {
  if (i % 2) {
    return 'event';
  }
  return 'odd';
});
console.log(newArr);

第十章 构建部署高性能JavaScript应用

这个建议看我翻译的Yahoo的35条优化网站建议

面试 集合

阿里 外包

阿里的同学真给力,长达50分钟的电话初面。

hr: 你好,我是阿里的,说说你现在工作内容吧。
我: 我现在在做公司后台系统,用vue重构,同时做小程序。

hr: 你在做的过程中,遇到的难题,克服的困难。
我: 小程序商品详情页面有个弹框,我负责实现他的功能,让他在同时满足3个条件的情况下弹出,(这不是我的店铺,我第一次进到商品详情页面,我有店铺可以回去)才弹出该弹框。

hr: 嗯嗯,还有呢?
我: 公司业务做得少。(本来想说上家公司的那个webpack分批打包业务的。有点捉急mmp)说个自己的项目吧,最近有做一个围住神经猫的小游戏,遇到的困难自然是A算法,路径问题,……%¥&……

hr: 这个用库就可以了,网络上一堆,我想问的是,你做的东西对公司有什么帮助,你能不能说一些和公司业务相关的东西。这个问题就算了,你说一下,现在有一个数组,用一个函数就可以得到他的所有名字
我: arr.filter(e=>e.name==='peng').map(e=>e.name); O(1)复杂度吧。
hr: 用一个函数就做到它。
我: 不会,只能用两个。答案如下。。。

var arr = [
	{name:'xiaoming',id:"123"},
	{name:'xiaoming',id:"456"},
	{name:'xiaoming',id:"789"},
	{name:'xiaohua',id:"101112"},
	{name:'xiaowang',id:"131415"},
	{name:'xiaohong',id:"161718"}
];
res = arr.filter(e=>e.name==='xiaoming').reduce((all,e)=>{
	return {name: 'xiaoming',id:[...all.id , e.id]}
},{name:'xiaoming',id:[]})
console.log(res);

hr: 看过你写的 #8 这个issue,35条网络优化建议,你有哪些在公司这里用过。

我: 三级域名 api.vmei.com, static.vmei.com这种,静态资源设置缓存,同时静态资源不携带cookie,可以加速访问静态资源。开启http2,加速网络资源。

hr: 说一下http2与http1的区别.

我: http2采用二进制数据输送,比http1快很多,可以实现资源传输的优先级顺序,实现先加载css,再加载js,http2可以开启google那个加速模块。

http2开启了线路复用, 即可以共享连接,他是之前Google加速模块SPDY的升级版,采用二进制传输,有更强健壮性。header的压缩,服务端推送。有请求优先级别选择。

hr: 还有呢?你为什么开http2?

我: 因为看到京东也开了http2,所以我也开。

hr: 那你知道我们公司都没有全部开启https,你知道为什么么?你都没有去理解其中的原理,就跟风。。那你说一下http2如何开启吧,

我: 先开https,要有一个证书,用的let encrypt.

hr: https和http区别,以及你为什么要用https

我: 中间套一层tcl或者ssl加密层,对数据加密,https更加安全。其实翻墙也是同样的原理,中间套一层ssl加密,让防火墙无法识别数据,从而达到防火墙无法过滤信息的目的。

这个真说错了,shadowsock是基于socket5协议来做的,只不过搬瓦工刚好占用了443端口而让你误以为是https.

hr: 大概说说react的生命周期。以及ajax函数放在生命周期那个阶段最好。

我; 大概分为三部分 生成组件,挂在组件,销毁组件,以及一些componentwillReciveProp,shouldcomponentupdate之类的吧。ajax请求自然是放在created里面最好,放在组件生成的阶段。

hr: 你说的是vue吧,算了setState也懒得问你了。

😰尴尬的一b,其实是应该放在componentDidMount,因为真实Dom还没挂载,无法实现ajax请求,太久没做,忘光了。

hr: 如何防止this作用域紊乱。

我: 箭头函数内部没有this,所以多用箭头函数,bind也可以改变this指向。

hr: 说说webpack如何进行性能优化吧,在原有脚手架基础上。

我: 我觉得vue-cli已经做的非常好了,没有优化必要。我用的webpack3-4,从我接触到webpack的时候就已经 是webpack3了,

hr: 这个肯定是有的,最优配置都是自己做出来的。像我们公司从前的webpack打包要6分钟,现在我们优化到只要 1分钟,我们从1-4一直在做优化,我们自己有研发一个happypack的wepback打包插件,极限提速,666,顺带案例自己的库一波(mmp,10秒jsp编译过程我都嫌久了,真的没做过复杂项目呢。)

hr: 你说说vue-cli脚手架内部实现原理吧,

我: 首先自然是 merge(webpack.base + prod + dev)(居然不问我webpack/babel/vscode插件怎么写。外包还没到这个级别吧。又也许是知道我不会,懒得问直接跳过)

hr: 项目测试用的什么

我: eslint+vscode

hr: 如何禁止不符合eslint规范的代码被提交,

我: 当然是用自定义的命令咯,内部调用node,然后调用eslint对代码进行检查,错误数量>0则不执行git add .的代码咯,


小店铺 - SEE

面试官很nice...无论是深度还是广度,都面面俱到...离开的时候觉得没啥好说的,到宿舍再想想,面试官真的很棒.

页面优化

cdn加速,静态资源压缩,
接口数据持久化.
css只发生重绘不要重排,

跨域的几种几种实现方式

nginx / nodejs代理服务器
服务端头部添加'header('Access-Control-Allow-Origin:*')'即可接受所有...
jsonp....

很明显我说错了一半以上,但是面试官也不戳破....

科里化函数的实现....

mark,后面再补...

react 中的纯组件与class extends Component {}定义的组件的区别...

不会...

原生js实现一个promise.....

我不会.....

nodejs Express实现模型..... 以及和koa的区别...

express通过回调函数,洋葱模型,,,

beforeCallback()
content()
afterCallback()

koa才是洋葱模型,express不是啊,,...

很明显我又说错了...koa才是洋葱模型, express是啥,我也不清楚...

除了koa,其他python ,go,java, php会不会...

不会..

docker 会么,

会一点,docker分层, repository image container,
说具体怎么用吧,
通过exec可以进入到image快照里面,就像linux系统,
attach 和 exec 的区别知道么,,,
不知道....

好吧,面试官非常自信,走的时候,连还有什么你知道的,但我没有问到的么?都不用问了,知识点十分全面,好吧,是我太菜....多参加高水平公司的技术面试是否能提高个人水平,查漏补缺......?

JavaScript 引擎 背后所做的事

字符串为什么会有方法??看下面代码

var name = 'peng';
a.length      //  4

真实js执行过程,字符串本身是没有属性的 new String(),才拥有属性。

var name = 'peng';
var temp = new String(name);
temp.length    // 4
temp = null;    // 用完销毁

{} === {} 不相等!!!

为啥?
同样是指针,{}并不保存在字面量上面,{}所指向的对象在内存里面,而他们指向的堆内的对象却不是同一个对象,所以不相等。

[] == ![] 相等

又是一个神奇的问题,== 会先将两边Number(str) 做一次转换 进而转化为 Number([]) === Number(![])的问题, 而 Number([]) // 0 =>>> ![] // false =>>> Number(false) // 0 ==>>> 0 === 0 // true

new 背后所做的一些事情

其实可以先看下面这个问题,new到底做了什么,先说结果 a === [];就是这样,a最后成了一个空数组,即一个空对象,它的原型是继承了Array的方法,他的原型就是Array,那么先就初步推测,

  • 1.生成一个新的对象。
  • 2.这个对象的原型就是new 后面的那个对象。
var arr = new Array();

image

那么进一步,实例化一个class,看下面代码, new到底做了什么???先看结果,根据结果进行推测,容易得出,new所做的

  • 1.先生成一个新的对象。
  • 2.这个对象的原型就是new 后面的那个对象。
  • 3.对象内部的属性就是class constructor 内部this的name属性。
class Test {
  constructor(){
    console.log('constructor');
    this.name = 'peng';
  }
  say() {
    console.log(this.name);
  }
}
var t = new Test();

image

那么再看看标准答案,google到第一个答案。这句中,new做了以下几件事情。
1、创建一个新的对象,这个对象的类型是object;
2、查找class的prototype上的所有方法、属性,复制一份给创建的Object
3、将构造函数classA内部的this指向创建的Object
4、创建的Object的__proto__指向class的prototype
5、执行构造函数class
6、返回新创建的对象给变量b

其实也不是那么标准
好吧js引擎偷偷干的一些事,被我发现的就这些。毕竟js也只是单线程,而在浏览器环境中,丰富的交互体验仅靠单线程js是无法实现的。有兴趣就看看我写的 #24 浏览器中的事件循环。

Reference:JavaScript面向对象精要 - Nicholas C.Zakas 著(高手高手高高手)

vue的数据双向绑定实现原理

前言

其实最没有必要的就是去学习一个框架的用法,然而我上个月都在纠结这个事情。终于也算简单做了一个商城demo(请在移动端下观看)。下面准备写一篇关于vue双向绑定实现原理,并写个demo实现他那个TODO list demo。

set和get

随便谁都知道双向绑定是基于Object.defautlProptotype(),以及setget.

var bValue;
Object.defineProperty(o, "b", {
  get : function(){
    return bValue;
  },
  set : function(newValue){
    bValue = newValue;
  },
  enumerable : true,
  configurable : true
});
o.b = 38;

通过自动给data数据下面每一个数据都设置set和get,来同步实现MVVM双向绑定。

好,下面的坑,交给以后的我来填坑。

做一个靠谱的人

靠谱是一种性格,更是一种,与技术无关。所以说,靠谱也挺难的。

一个人靠不靠谱,其实就看这三点:

  • 凡事有交代
  • 件件有着落
  • 事事有回音。

有些人就是这样,他跟你说“好”,你不会觉得他是在敷衍你。

我回忆起当年一位我特别喜欢且令人尊敬的老师。
他教的班级很多,但是他记得每一个人的名字。无论我什么时候跟他讨教问题,即使当时他忙不过来,但不管多久也总能记得回答我。
给他发邮件,永远在12小时内答复,或者他会自动留言——“不好意思,我在xxxx,你的邮件我将在x月x日答复”。他回邮件的时间永远是约定那天的上午。

相信每个人聊天时都遇到过这样的情况,聊着聊着对方突然不说话了。

如果对方隔了一段时间回复你第一句话就是交待他刚刚因为什么事情没回信息。这种人实在不多了。
事事有回音,遇到这种人,都会让人的好感大增。
前几天在朋友圈看到表妹发了张聊天截图,这是她跟老公的聊天记录,也没有什么特别的内容,晚上挺晚了,表妹发信息想让应酬的老公回来时带一份小龙虾做宵夜,她老公看到后回复:好的。
接着他老公发来一张手机屏保的截图,显示电量已经百分之五。又说到:等下打不通别着急,我应该马上就可以回家了,跟你说一下。
凡事有交代,一个很小的举动,但却避免了不必要的误会,也让女生的心中感到温暖。
看来靠谱的人,真的是时时刻刻都能流露出靠谱的基因。

无论做什么事情都有始有终,突然消失回来后会说明原因,时间观念强,提前安排有规划。

今年春节我的工作并没有停止,不仅是我,我的设计师,我的助理也都拼尽全力准备各项工作。
然而过年七天假期,我并不忍心看到他们没有休息时间,谁家都要拜年,谁家也都有亲戚来走动,朋友同学聚会也一定少不了。
让我欣慰的是,每天在我们的微信群里都有大家提前安排的工作流程,跟完成时间,并且最终都保质保量地完成了自己的那部分工作,可能他们失去了一些时间灵活度,但并没有让工作质量受到丝毫影响。
那一刻,我是庆幸的,我为拥有这样的团队感到骄傲。
一个成熟的企业里,需要少数聪明的人和多数靠谱的人。
马云说过,我不需要提点子的人,阿里这么多人,提点子的人太多了,我需要有执行力的人。

真正成熟的人,就是事事有责任,事事有担当,事事有始有终。

都说分手见人品,冷暴力代表的是没责任心,没担当,无法善始善终。
靠谱的人最性感,不靠谱的人只会让别人不安。趁早远离,也别遗憾。

我们一生的幸福都需要和靠谱的人共同建立。

和有智慧的人交流,和情商高的人谈恋爱,和靠谱的人共事,和幽默风趣的人同行。
人生若能如此,风景无限好。
成功和幸福没有捷径,这个世界也没有一条道路可以通往靠谱,因为靠谱本身就是一条道路。

结论:要想改变别人对自己的看法,太难了,把别人对自己的看法当作标准,同时不就赋予了别人伤害自己的机会??因为我们依靠别人的评价而定义自己的价值,因为靠谱往往只能从别人口中说出。所以房间堆满书,充实自己的精神世界。我遨游在代码的世界里,一会儿是js的数据结构与算法,一会儿是js函数式编程,一会儿又是JavaScript语言精粹,一会儿再是web建站进阶指南,未必也是一件坏事(也会少与外界交流)。人生匆匆,我选择我钟爱的生活方式,虽然这样的人不太可能拿到高薪,,但自己的人生,自己满意就好。

javaScript - 变量

关于js的变量(为了统一,加上'use strict');

词法作用域

类似于面向对象的原型链, local -> global -> window ,如果局部拿不到变量,则从全局拿取,如果全局拿不到变量,就从window里面拿变量。

全局变量

'use strict'; 
// global variable
window.variable = 'window variable';
variable = 'global variable';
console.log(window.variable)  // 'window variable'

局部变量

"use strict";
window.variable = 'window variable'
variable = 'global variable'
// var variable = 'var variable'
let testFunc = e => {
   console.log('window:',window.variable); // global variable
   console.log('var:',variable); // undefined
   var variable = 'local variable'
   return variable
}
console.log(
   testFunc()
);// local variable

但是严格模式下有个问题,直接variable = 'variable'会报错。这里还要说明:为什么console.log('var:',variable); // undefined,问题在于var 函数内部声明的时候会发生一个提升('hoisting'),请看下文,这就是为什么variableundefined;

let testFunc = e => {
   var variable;
   console.log('window:',window.variable); // global variable
   console.log('var:',variable); // undefined
   variable = 'local variable'
   return variable
}
var foo = function (){
  var a = 3;
  var b = 5;
  var bar = function (){
    var b= 7;
    var c = 11;
    console.log(`a:${a},b:${b},c:${c}`);    // a:3 b:7 c:11
    a += b+c;
    console.log(`a:${a},b:${b},c:${c}`);    // a:21 b:7 c:11
  };
  console.log(`a:${a},b:${b},c:${c}`);      // a:3 b:5 c:未定义会报错
  bar();
  console.log(`a:${a},b:${b},c:${c}`);      // a:21 b:7 c:undefined
}
foo()

闭包 - 私有变量

这是重要的一种技术,闭包内可以保存变量了,在下一次调用的时候可以记住变量。

"use strict";
let pingpong = (e => {
  var PRIVATE = 0;
  let let_private = 10;
  return {
    inc: e => {
      PRIVATE += e;
      return PRIVATE;
    },
    dec: e => {
      PRIVATE -= e;
      return PRIVATE;
    }
  }
})()
console.log(
  pingpong.inc(3),
  pingpong.inc(3),
  pingpong.dec(10)
);

动态作用域 - 变量

在《JavaScript 函数式编程》里面看到的一种变量命名方式,将{a:['first',second]}将他的值以数组形式储存起来,这样就可以得到上一次的命名值;

An Overview of Arrays and Memory

首先内存条和硬盘有什么不同,
电脑将数据写入内存的速度远远快于写入硬盘。

var a = 1;
1   =>   000000000001;   // 转换成2进制储存。

a bit = 1 or 0
-> 32 bits for init
memory = a long tape of bytes

为什么用koa

为什么是koa而不是express呢?

  • koa更小

image

  • 易于理解的源码
    主线结构application用于构造整个koa,content.js用于构造ctx,request用于重写req,response用于重写res,源码容易读懂,koa作为基础框架,读懂源码是很重要的,便于日后开发流程以及扩展的可控制。express太大了懒得看。

image
image

  • 基于es7的async/await的中间件调用机制
class koa{
  constructor(){
    this.middleware = []; //存放中间件
  }
  listener(){
    this.middleware.forEach(async mid=>{
      await mid();    // 让中间件异步执行
    })
  }
}

async / await + promise,同步与异步的完美结合,不得不说,如果不用这一新功能真的是一种遗憾。
koa2相比于koa1最显著的不同是,koa1基于yield,而koa2基于async/await将这一异步流程简化到了极致,对比express则比较恶心的使用了 嵌套回调。next()即是下一个嵌套中间件。
image
而对于koa,表面上看也差不多,但其实await后面的next调用的下一个中间件已经交给下一个js线程去实现了,
image

如果你对 线程 进程不清楚

image
通过我多次google查询async/await原理,估计他是基于多线程之间的操作,也就是说,async内部将后面的函数放入了另一个线程,这和和callback回调函数是有本质区别的,原因在于回调函数是基于js单线程的实现,假如说让js的回调函数与setTimeout函数结合使用会发生什么事情,都知道setTimeout会创建另一个线程叫做setTimeout线程,单独去执行他内部的js函数,下面图片实验一下。
额。。好像依然是单线程,只是函数暂停了。
image

  • 重写req与res=> ctx
    将req 与 res 写成一个ctx对象
  • 生态圈的繁荣

image
image
image

  • koa/express的洋葱模型

image
下面这段代码很好的说明了koa的洋葱模型,从1-2-3-3-2-1的,next()表示下一个中间件
image
同样的道理,express也是基于这种洋葱模型来。这点koa和express没有区别,而koa运用了异步函数之后可以依靠.then()来进行下一步,google大量资料(几篇文章+stackoverflower),其实这里的异步函数依然是基于单线程的js语法糖,async/await。then这种异步并没有实现真正意义上的多线程,就像es6的class也一样只是语法糖,js并没有真正意义上的类。而这种是基于js底层封装好的语法糖,应该不存在性能问题,因此应该放心使用。。。好吧,由于个人水平问题,我不打算继续深入写koa简易版框架,我先读一下nodejs事件池

JavaScript 面向对象精要 - Nicholas C.Zakas(二)

看一本js书好不好,主要看他对于面向对象的描述以及原型继承的描述。

第五章 继承

如何学习创建对象时理解面向对象js编程的第一步,而第二部是理解继承。

5.1 原型对象链和Object.prototype

JavaScript内建的继承方法被称之为原型对象链,又称之为原型对象继承。如果上一章所看,原型对象的属性可经由对象实例访问,这就是继承的一种形式。对象实例继承了原型对象的属性。因为原型对象也是一个对象。他也有自己的原型并继承其属性。因此可以说所有对象都继承自Object。

5.1.1 继承自 Object.prototype 的方法

前几章里用到的多个方法其实都是定义在Object.prototype上的。因此可以被其他对象继承,这些方法如下。

方法 定义
hasOwnProperty() 检查是否存在一个给定名字的自有属性
propertyIsEnumerable() 检查一个自有属性是否可枚举
isPrototypeOf() 检查一个对象是否是另一个对象的原型对象
valueOf() 返回一个对象的值表达式
toString() 返回一个对象的字符串表达式

这5种方法经由继承出现在所有对象种。当需要让对象在JavaScript中以一致的方式工作,最后尤为重要,有时候你甚至会想要自己定义他们。

1. valueOf

返回原本的值。当每一个操作符被用于一个对象时候就会调用valueOf的方法。默认返回对象实例本身。。原始封装类型重写valueOf,对于字符串返回字符串本身,对于Boolean返回一个布尔值。对number返回一个数字。

var now = new Date();
var earlier = new Date(2010, 1, 1);

console.log(now > earlier);

上面这个例子,now是一个代表当前时间的Date,而earlier是一个过去的时间,当使用<比较的时候,在两个对象都调用了valueOf()的方法,另外,你甚至可以对比两个Date相减来获得他们在epoch时间上的差值。

2.toString()

一旦value()返回的是一个引用值而不是原始值的时候,就会回退调用toString()方法,另外,当JavaScript期待一个字符串的时候,也会对原始值隐式调用toString()。例如,当加号操作符的一边是一个字符串,另一边会被自动转化成字符串。如果另一边是一个原始值,就会自动转化成字符串。不懂请看下面例子,重写Object的toString原型方法,'name'+{}; // namename ,字符串和布尔值相加会先将右边的布尔值转化成字符串,通过toString()这个方法。,首先如果右边是引用值,会先调用value的方法,如果value返回的还是一个引用值,那就调用toString()
image
image

const str = {
  name: 'peng',
  toString() {
    return 'toString';
  },
  valueOf() {
    return 'val';
  },
};
Object.prototype.valueOf = function (){
  return 'value'
}
Object.prototype.toString = function (){
  return 'staring'
}
console.log(`name${str}`);    // namestring

修改Object.prototype

修改Object会影响所有对象,这很危险。

Object.prototype.add = function (num) {
  return this + num;
};
console.log({}.add(5));   // [object Object]5

这个新添加的属性是可枚举的.请看下面的例子,我给原型对象添加add方法,返回this+val,,这个会返回本对象并且因为他是引用值,所以会调用toString()方法,而toString方法又被我改写了,所以返回的就是'to stirng5'。
同时被新添加的add方法是实体字,说明他是可以被枚举的。

image

image

对象继承

对象继承是最简单的原型继承,你唯一需要做的就是指定新对象的[[Prototype]]指向原型对象。
同时可以使用Object.create()方法指定,他接受2个参数,第一个是需要被设置成新对象的[[Prototype]]的对象,,第二个参数和Object.definedProperties()中使用的一样。

var book = {
  title: 'your now Principles of Object-Oriented Javascript.'
}
===========**the same as below**============
var book = Object.create(Object.prototype, {
    title: {
        configurable: true,
        enumerable: true,
        value: 'The Principles of Object-Oriented Javascript',
        writable: true,
    }
});

两种声明具有相同效果,第一种使用了字面量自定义属性,而第二种使用了Object.create,显示用了同样的操作,这种默认继承无趣,但是如果你继承其他对象就有意思了。

const person1 = {
  name: 'peng',
  sayName() {
    console.log(this.name);
  },
};

const person2 = Object.create(person1, {
  name: {
    configurable: true,
    enumerable: true,
    value: 'Greg',
    writable: true,
  },
});

person1.sayName();    // 'peng'
person2.sayName();    // 'Grey'
console.log(person1.hasOwnProperty('sayName');    // true
console.log(person2.hasOwnProperty('sayName');    // false
console.log(person1.isPrototypeOf(person2);    // true

person2继承person1 继承Object

假设你通过Object.create()创建时第一个参数为null,那么继承指向空。看下图,他没有任何方法,因此同时,他也是一个完美的hash容器,因为你给他命名任何方法,都可以,不存在任何冲突。
image
image

无法转换,因为不存在toString方法来进行隐式转换。一个很有意思,你可以通过它创建一个没有原型的对象。
image

构造函数继承

当你声明一个类的时候,JavaScript引擎默认帮你做了如下事情,

function Person(){}
Person.prototype = Object.create(Object.prototype, {
  constructor: {
    configurable: true,
    enumerable: true,
    value: Person,
    writable: true,
  }
})

image

上面可以看出,你不需要做任何事情,这段代码帮你构造一个构造函数,其原型指向另一个继承自Function的的对象,并且它有一个新属性constructor,其值指向构造函数,如此循环下去,但是他们是一种循环式的自引用,众所周知,原型链仅仅只是一个指向对象的指针。这只是一种自己指向自己的循环引用point。下图例子说的很清楚,我这里只有一个a对象,a对象属性a指向它本身。那这就是循环自引用。而构造函数仅仅只是这样一个例子的复杂化而已。所有引用类型都是指针指向(point)
image

function Rectangle(length, width) {
  this.length = length;
  this.width = width;
}

Rectangle.prototype.getArae = function () {
  return this.length * this.width;
};

Rectangle.prototype.toString = function () {
  return `[Rectangle ${this.length}x${this.width}]`;
};

// inherits from Rectangle
function Square(size) {
  this.length = size;
  this.width = size;
}

Square.prototype = new Rectangle();
Square.prototype.constructor = Square;

Square.prototype.toString = function () {
  return `[Square ${this.length}x${this.width}]`;
};

const rect = new Rectangle(5, 10);
const square = new Square(6);

console.log(rect.getArae());
console.log(square.getArae());

console.log(rect.toString());
console.log(square.toString());

console.log(square instanceof Square);
console.log(square instanceof Rectangle);
console.log(square instanceof Object);

这个代码有两个构造函数:Rectangle和Square。Suqare构造函数的prototype属性被改写成Rectangle的一个对象实例。此时不需要给Rectangle的调用提供参数,因为他们不需要被使用,而且如果提供了,那么所有Square的对象实例都会享有共同的维度,用这种方法改变原型链,你需要确保构造函数不会再参数却是的时候抛出错误。(很多构造函数包含的初始化逻辑会需要参数)且构造函数不会改变任何全局状态。Square.prototype被改写后,constructor属性被重置为Square。
然后,rect作为Rectangle的实例对象呗创建,而square则被作为Square的实例创建。两个对象都有getArea()方法,因为他被继承自Rectangle.prototype和Object的对象实例。instanceof使用原型对象链检查对象类型。
Square.prototype并不需要真的被改写为一个Rectangle对象,毕竟Rectangle构造函数并没有真的为Square做什么必要的事情,事实上,唯一相关的部分是Square.prototype需要指向Rectangle.prototype,使得继承得以实现,这意味着你可以用Object.create()简化例子,代码如下

// inherits from Rectangle
function Square(size){
  this.length=  size;
  this.width=  size;
}
Square.prototype = Object.create(Rectangle.prototype, {
  constructor: {
    configurable: true,
    enumerable: true,
    value: Square,
    writable: treu
  }
});
Square.prototype.toString = function() {
  return `[Square ${this.length}x${this.width}]`;
};

在这个版本的代码中,Square.prototype被改写成一个新的继承自Rectangle.prototype的对象,而Rectangle构造函数没有被调用。这意味着,你不再需要担心不参加构造函数会导致的错误。除此之外。

构造函数的窃取

啥意思?

由于JavaScript的继承通过原型对象继承来实现,因此不需要调用对象的父类的构造函数,如果你却是需要再子类构造函数中调用父类构造函数,那你就需要利用JavaScript函数工作的特性。
第二章提到过,call和apply方法允许你再调用函数是提供不同的this值,那正好是构造函数窃取的关键。而这个就是构造函数窃取的关键,只需要再子类构造函数中调用call或者apply调用父类的构造函数,并将新的创建的对象传进去即可。实际上,就是用自己的对象窃取父类的构造函数,如下例子。

function Rectangle(length, width) {
  this.length = length;
  this.width = width;
}

Rectangle.prototype.getArae = function () {
  return this.length * this.width;
};

Rectangle.prototype.toString = function () {
  return `[Rectangle ${this.length}x${this.width}]`;
};

// inherits from Rectangle
function Square(size) {
  Rectangle.call(this, size, size);

  // optional: add new prototype or override existing ones here
}

Square.prototype = Object.create(Rectangle.prototype, {
  constructor: {
    configurable: true,
    enumerable: true,
    value: Square,
    writable: true,
  },
});

Square.prototype.toString = function () {
  return `[Square ${this.length}x${this.width}]`;
};

const square = new Square(6);

console.log(square.getArae());
console.log(square.width);
console.log(square.length);

Square构造函数调用了Rectangle构造函数,并传入了this和size量词,一次作为length,另一次作为width,这样做会在新的对象上创建length,和width属性并让他们等于size,这是一种避免再构造函数理重新定义你希望继承的属性的手段。你可以在调用完父类的构造函数后继续添加新属性覆盖已有的属性。

这分两步走的过程在你需要完成自定义类型之间的继承是比较有用,你经常需要修改一个构造函数的原型对象,你也经常需要在子类的构造函数中调用父类的构造函数的原型对象。一般,需要修改prototype来继承方法并且构造函数窃取来设置属性,由于这种做法模范了那些基于类的语言的类继承。,通常呗成为伪类继承。

5。5 访问父类方法

前面例子中,Square类型有自己的toString方法,,子类覆盖父类,但是如果你要访问父类怎么办???代替方法是在通过call或apply调用父类的原型对象的方法时传入一个子类的对象。

function Rectangle(length, width){
  this.length = length;
  this.width = width;
}

Rectangle.prototype.getArae = function () {
  return this.length * this.width;
};

Rectangle.prototype.toString = function () {
  return `[Rectangle ${this.length}x${this.width}]`;
};
// inherits from Rectangle
function Square(size) {
  Rectangle.call(this, size, size);

  // optional: add new prototype or override existing ones here
}
Square.prototype = Object.create(Rectangle.prototype, {
  constructor: {
    configurable: true,
    enumerable: true,
    value: Square,
    writable: true,
  },
});

Square.prototype.toString = function () {
  vartext = Rectangle.prototype.toString.call(this);
  return text.replace("Rectangle, "Square");
};

在这个版本的代码中,Square.prototype.toString()通过call调用Rectangle.prototype.toString()。该方法只需要在返回文本结果钱用"Square","Rectangle"。这种做法看起来有一点冗长,但是唯一有效访问父类的方法。

第六章 对象模式

终于讲到这一节,非常经典的啊。该书也只生写最后10页。精华就在后面,坚持看下去吧。

本章讲述创建高可管理和创建对象的能力。

6.1 私有成员和特权对象

  • 模块模式,俗称IIFE返回一个对象,而变量外部不可见,
var yourObject = (function (){
 const private = 'pengliheng'

 return {
   a:1,
   b:2
 }
}());

console.log(yourObject);

上面创建了匿名函数立即执行,。同时这意味着,这个函数仅存在于被调用瞬间,一旦执行就立即销毁了。IIFE常见的用于浏览器端环境打包模式。适用于模块化。

6.1.2 构造函数的私有成员

模块定义单个对象的是由属性上十分有效i,但对于那些同样需要私有属性的自定义类型又要怎么做?你可以在构造函数内部使用类似模块来创建每个实例的私有数据。如下例子:

function Person(name) {
  // define a variable only accessible inside of the Person constructor
  let age = 25;
  this.name = name;
  this.getAge = function () {
    return age;
  };
  this.growOlder = function () {
    age += 1;
  };
}

const person = new Person('Nicholas');

console.log(person.name);
console.log(person.getAge());

person.age = 100;
console.log(person.getAge());

person.growOlder();
console.log(person.getAge());
console.log(person);

上面代码中Person构造函数就有一个本地变量age。该变量被用于getAge()和growOlder()方法。当你创建Person的一个实例时候,该实例接受其自身的age变量,getAge()方法和growOlder()方法。这种做法很多时候都有类似模块模式,构造函数创建一个本地作用域返回this对象。上一章讨论过,将对象直接放在对象实例上不如放在其原型上面有效,如果你需要实例私有数据,这是唯一有效方法。
但是如果你需要所有实例都可以共享私有数据,就好像它被定义在原型上面那样,可以结合模块模式和构造函数,如下。

const Person = (function () {
  // define a variable only accessible inside of the Person constructor
  let age = 25;

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

  InnerPerson.prototype.getAge = function () {
    return age;
  };

  InnerPerson.prototype.growOlder = function () {
    age += 1;
  };
  return InnerPerson;
}());

const person = new Person('Nicholas');

console.log(person.name);
console.log(person.getAge());

person.age = 100;
console.log(person.getAge());

person.growOlder();
console.log(person.getAge());
console.log(person);

image
上面代码InnerPerson构造函数被定义在IIFE中。变量age被定义在构造函数外,但是在模块内部,并被两个原型对象的方法使用。IIFE返回InnerPerson构造函数作为全局作用域里面的Person构造函数使用,最终Person实例全部共享age作为闭包内部变量。

6.2 混入

JavaScript大量使用了伪类继承和原型对象继承,还有另一种就是混入。第一个对象接收者,通过直接赋值第二个对象提供者的属性从而接受了这些属性。

funtion mixin(receiver, supplier) {
  for(var property in supplier){
    if(supplier.hasOwnPrototype(property){
      receiver[property] = supplier[property];
    }
  }
}

函数mixin()接受2个参数,接收者和提供者,,通过枚举方式,将所有可枚举的属性赋值给接收者,通过for...in循环所有可枚举属性。

作用域安全的构造函数

涨见识。。

如果不通过new就创建实例函数

var a  =Array();

image

上图为什么呢,因为它上了安全套

function Person(name){
  if(this instanceof Person){
    this.name = name;
  } else {
    return new Person(name);
  }
}

对于上面这个构造函数,当自己呗new调用的时候,就设置name属性,如果不被new调用的时候,则以new递归调用自己来为自己创建正确的属性。这么做就能保证行为一致性。

var person1 = new Person('peng');
var person2 = Person('peng');

console.log(person1 instanceof Person);   // true
console.log(person2 instanceof Person);   // true
全书完。这是我写的最细的一本书了,短短92页,我每一页都给照抄下来了😭。

reference:JavaScript面向对象精要

javaScript数据结构与算法

javaScript 面向对象

老方法

function Checking(amount) {
  this.balance = amount;
  this.deposit = deposit;
  this.withdraw = withdraw;
  this.toString = toString;
}

// 存amount的钱
function deposit(amount) {
  this.balance += amount;
}

// 花掉
function withdraw(amount) {
  if (amount <= this.balance) {
    // 够钱花就减去
    this.balance -= amount;
  } else if (amount > this.balance) {
    // 不够钱就打印不够钱
    console.log('钱不够花啊');
  }
}

// 展示还有多少钱
function toString() {
  return `Balance: ${this.balance}`;
}

// 新建一个钱包
const account = new Checking(500);
account.deposit(1000);
console.log(account.toString());
account.withdraw(1501);
console.log(account.toString());

新的es6这样新建一个对象

class Checking {
  constructor(amount) {
    this.balance = amount;
  }
  deposit(amount) {
    this.balance += amount;
  }
  withdraw(amount) {
    if (this.balance < amount) {
      console.log(`你只有${this.balance},不够花啊`);
    } else {
      console.log(`你只有${this.balance},不够花啊`);
      this.balance -= amount;
    }
  }
  toString() {
    return `余额:${this.balance}`;
  }
}
// // 新建一个钱包
const account = new Checking(500);

account.deposit(1000);
console.log(account.toString());
account.withdraw(750);
console.log(account.toString());
account.withdraw(850);
console.log(account.toString());

计算平均数

class WeekTemps {
  constructor() {
    this.dataStore = [];
  }
  add(temp) {
    this.dataStore.push(temp);
  }
  average() {
    let total = 0;
    for (let i = 0; i < this.dataStore.length; i += 1) {
      total += this.dataStore[i];
    }
    return total / this.dataStore.length;
  }
}
const thisWeek = new WeekTemps();
thisWeek.add(2);
thisWeek.add(2);
thisWeek.add(22);
thisWeek.add(2);

console.log(thisWeek.average());

创建一个可展示学生成绩,添加成绩,求成绩平均数的对象

class Student {
  constructor() {
    // constructor 中的数组可用于储存// 类似于私有变量
    this.arr = [];
  }
  // 记录学生成绩
  show() {
    return this.arr;
  }
  // 添加学生成绩
  add(achievement) {
    this.arr.push(achievement);
  }
  average() {
    const all = this.arr.reduce((a, b) => a + b);
    return all / this.arr.length;
  }
}

const student = new Student();
student.add(94);
student.add(93);
console.log(student.show());
console.log(student.average());
student.add(93);
console.log(student.show());
console.log(student.average());

将一组单词存在数组中,提供正向,反向排序的方法;

class ShowArr {
  constructor(arr) {
    // props 就是class在创建时候传入的参数;
    this.arr = arr;
  }
  sort() {
    return this.arr.sort((a, b) => (a - b > 0));
  }
  reverse() {
    return this.arr.sort((a, b) => (a - b > 0)).reverse();
  }
}
const showArr = new ShowArr([1, 2, 3, 4, 5, 66, 22, 34]);
console.log(showArr.sort());
console.log(showArr.reverse());

如何判断对象是否拥有属性(不来自于继承)

showArr.hasOwnProperty('sort')    // true => 自有属性   // false 属性来自于继承&&根本没有这个属性
showArr instanceof ShowArr         // true   => 属性来自于继承
showArr instanceof Object            // true   =>  原型链继承   showArr => ShowArr => Object

对象的属性和方法的不同(其实没什么不同,只是创建的方法不一致)

class ShowArr {
  constructor(arr) {
    this.arr = arr;
  }
  sort() {
    return this.arr.sort((a, b) => (a - b > 0));
  }
}
ShowArr.prototype.propAdd = function () {
  return 'proptest';
};
const showArr = new ShowArr([1, 2, 3, 4, 5, 66]);
showArr.test = 'test';
console.log(showArr);

image

结论

在用原型继承编写复杂代码之前,了解原型继承模型非常重要。同时,要注意代码中的原型链的长度,并在必要时将其分解,以避免潜在的性能问题。此外,永远不要扩展原生对象的原型,除非是为了兼容新的JavaScript特性。

BST二叉树基础

先新建一个节点类,将以将这个想像成超级简化版的dom树,每个节点最多2个子节点
节点拥有信息,data储存数据,储存子节点this.left < this.data < this.right,遵从这样的规律

class Node {
  constructor({ data, left, right }) {
    this.data = data;
    this.left = left || null;
    this.right = right || null;
  }
  show() {
    return this.data;
  }
}

下面新建BST类

class BST {
  constructor() {
    this.root = null;
  }
  insert({ data }) {
    const n = new Node({ data });
    if (this.root === null) {
      this.root = n;
    } else {
      let current = this.root;
      let parent;
      while (true) {
        parent = current;
        if (data < current.data) {
          current = current.left;
          if (current == null) {
            parent.left = n;
            break;
          }
        } else {
          current = current.right;
          if (current == null) {
            parent.right = n;
            break;
          }
        }
      }
    }
  }
  inOrder(node) {
    if (!(node == null)) {
      this.inOrder(node.left);
      console.log(`${node.show()} `);
      this.inOrder(node.right);
    }
  }
}

以上给他写了插入的方法,通过下述代码描述

const nums = new BST();
nums.insert({ data: 23 });
nums.insert({ data: 45 });
nums.insert({ data: 16 });
nums.insert({ data: 37 });
nums.insert({ data: 37 });
nums.insert({ data: 3 });
nums.insert({ data: 99 });
nums.insert({ data: 22 });
nums.inOrder(nums.root);
console.log(nums.root);

image

vue - MVVM在vue中扮演的角色

前述

众所周知vue基于MVVM模型,那么既然要学vue,这个必须懂。下面说几个前提。

  • MVVM是一种概念,他不是一个程式,它可以存在任意系统(Android/web/java)

概念

  • Model 模型
    模型是指代表真实状态内容的领域模型(面向对象),或指代表内容的数据访问层(以数据为中心)。

  • View 视图
    就是你在浏览器页面所看到的按钮等

  • ViewModel 视图模型
    视图模型是暴露公共属性和命令的视图的抽象。MVVM没有MVC模式的控制器,也没有MVP模式的presenter,有的是一个绑定器。在视图模型中,绑定器在视图和数据绑定器之间进行通信。
    在vue当中,viewmodel指的就是一个Vue对象,他简称vm,他将真实的dom和数据模型data联系在一起。所以尤大大经常写如下代码:

var vm = new Vue({
   // todo
})

image

  • 各种箭头指向 绑定器
    声明性数据和命令绑定隐含在MVVM模式中。

理论基础

MVVM旨在利用WPF中的数据绑定函数,通过从视图层中几乎删除所有GUI代码(代码隐藏),更好地促进视图层开发与模式其余部分的分离。

MVVM有助于将图形界面的开发与业务逻辑或者后端逻辑分离开来,MVVM的视图模型是一个值转换器,这意味着视图模型负责从模型中暴露(转换)数据对象,以便轻松管理和呈现对象。在这方面,视图模型比视图做得更多,并且处理大部分视图的显示逻辑。视图模型可以实现中介者模式,组织对视图所支持的用例集的后端逻辑的访问。

MVVM来自于MVC模式

MVVM也被称为model-view-binder。

下面一个基于vue单个组件实现双向绑定的 MVVM模型

MVVM在vue组件中分别对应的是什么?
<template>
  <div>
    {{test}}                                   // view   视图
    <input type="text" v-model="test">         // view   视图
  </div>
</template>

<script>
export default {
  data(){
    return {
      test: 'ttt',                             // model  数据模型
    }
  },
}
</script>
这就是我所理解到的vue中对应的MVVM,M(数据模型)展示在V(视图),V(视图)中的input绑定了M(数据模型)test变量,进而同时test数据模型改变,同步到view数据模型展示中。MVVM就是vue一直在推崇的双向数据绑定。

image

Reference:

HTTP 权威指南

以前一直以为http就是个协议,内容很少,直到我买了http权威指南这本600页厚的书,我才发现,我的基础实在太差了。涨知识。只是觉得这本书开头部分印证了我之前零散的观点,然后就要今天一整天在家看了60页。我终于不害怕整一天宅房间里了。
http分为三部分 1.起始行。2.首部,3.主体。
网络5个层次 1.http应用层。2.TCP-传输层。3.IP-网络层。4.网络特有链路接口-数据链路层。5.物理网络硬件-物理层

前言

HTTP(Hypertext Transfer Protocol, 超文本传输协议),http起初是一个简单协议,因此你也许以为这个没什么好说的,请看目录,你会发现这本书是web架构“圣经”。
纵观全书,我们对于http不仅仅停留在表现,而是深入浅出解释了。这是一本cs(compute science)基础书籍。好吧,我应该补基础了。

本书分为5部分

  • http,web基础,
  • HTTP结构
  • 识别认证,与安全。
  • 实体编码和国际化
  • 内容发布与分发
  • 附录。

第一章 http概述

1.1 http ,多媒体信使,

每天网络上,充斥着各种,资源,jpg,html页面,mpeg电影,WAV音频文件,java小程序,等,而http可以从全世界各个地方的web服务器上将这些信息迅速便捷搬移到人们桌面上的web浏览器上面,
http采用可靠的数据传输协议,因此,即使数据来自地球另一端,也可以准确的把数据传给我,并不发生损坏。下面来讲一下,近距离观察,http是如何传输web流量的。

1.2 web客户端和服务端

http客户端发出请求,服务器会根据协议,解析,发送提供数据。这是万维网的基本组件。
当你浏览一个页面比如www.baidu.com的时候,浏览器向服务器发送请求,,服务器默认发送index.html文件给浏览器。

1.3 资源

非常长知识啊。

web服务器是web资源的宿主,而web资源是web内容的源头,这些文件包括任意内容,如html,word文件,图片文件,avi电影,
但同时资源文件不一定是静态文件,资源也可以是根据资源生成的软件程序。例如JSON数组。

媒介类型

MIME(Multipurpose Internet Mail Extension, 多用途因特网邮件扩展),这个是为了在不同邮件系统中挪移报文。http同样采用了它,并且用它标记多媒体内容,
web服务器会为所有HTTP对象附加一个MIME类型,MIME是一种文本标记,表示对象类型,和特定子类型,

  • html格式文本文档 text/html
  • ASCII格式文本文档 text/plain
  • JPEG格式的图片为image/jpeg
  • GIF格式的图片为image/jpeg
  • Apple的QuickTIme电影 video/quicktime类型
  • 微软的PowerPoint 演示文件为application/vnd.ms-powerpoint类型

常见的类型有数百种。

URI

每一个web服务器都有一个名字,服务器资源名称 为 统一资源标识符(Uniform Resource Identifier, URL)。URL就像一个地址一样,在全世界范围内标识并定位资源信息。
URI有两种形式 URL,和URN

  • URL
    大部分URL分为三部分
    • 第一部分称之为方案,通常就是http://这部分
    • 第二部分称之为因特网地址,www.baidu.com
    • 第三部分称之为服务器上面某个资源 /image/logo.png

1.3.4URN

作为网络上唯一名称使用的,这种与资源所在地无关,就可以将资源四处搬移,

1.3 方法

http支持不同几种请求命令,常见的包括如下

  • GET 从服务器端向客户发送命名资源
  • PUT将来自客户端的数据储存到一个命名的服务器资源中去。
  • DELETE从服务器端删除命名资源
  • POST将客户端数据发送到一个服务器网关应用程序。
  • HEAD仅仅只发送命名资源响应中的httl首部

状态码

  • 200 文档正确返回
  • 302 Redirect重定向,去其他地方获取资源
  • 404 资源找不到

1.5 报文

HTTP报文有一行行简单的字符串组成,HTTP报文都是纯文本,而不是二进制码,
http报文主要分为下面3部分

  • 起始行 主要用来说明请求用来做什么,在响应报文中描述了出现了什么情况,是200还是302

  • 首部字段 起始行后面有0或者多个首部字段,每一行都包括一个名字和一个值,

  • 主体 空行之后就是可选的报文主体,其中包含了所有类型的数据,请求柱体包括了要发给服务器的数据,响应主体包括了返回给客户的数据,可以是任意的二进制内容,也可以是文本。

image

1.6 连接

讲一下,报文如何通过传输控制协议(Transmission Control Protocol,TCP),连接从一个地方搬移到另一个地方去。

1.61 TCP/IP

http是应用层协议,HTTP无需操心网络通信的具体细节,他把网络的细节都交给了通用可靠的因特网传输协议:TCP/IP
TCP提供了:

  • 无差错的数据传输
  • 接序传输(数据总按照发送的顺序接受)
  • 未分段的数据流

因特网本身就是基于TCP/IP的,这个是全世界计算机和网络设备常用的层次化分组交换网络协议,TCP/IP隐藏了硬件的弱点和特点,。安全通讯。

网络术语 对应层次
HTTP 应用层
TCP 传输层
IP 网络层
网络特有链路接口 数据链路层
物理网络硬件 物理层

1.6.2 连接 IP地址 以及端口号

在http客户端向服务端发送报文之前,需要用到网络协议(Internet Protocol , IP),在网络协议地址和端口号在客户和服务器之间建立一条TCP/IP连接。

至于如何获得HTTP服务器和IP地址呢?当然是通过URL了,URL就是资源地址,
步骤如下:

  • 通过URL解析出服务器主机名字
  • 浏览器将服务器的主机名转换成ip
  • 得到端口号
  • 浏览器建立一条与web服务器的TCP连接
  • 浏览器向服务器发送一条http请求
  • 服务器向浏览器发送一条http响应报文

1.8 Web的结构组件

本章重点介绍2个web应用程序,web浏览器和web服务器

  • 代理
    位于客户端和服务器之间http中间实体。

  • 缓存
    http仓库,是常用页面的副本得到保存,可以离客户更近的地方

  • 网关
    连接其他应用的特殊web服务器

  • 隧道
    对http通信报文进盲转发的特殊处理

  • Agent 代理
    发起自动http请求的半智能web客户端。

1.8.1 代理

首先看 http代理服务器,这是web安全,应用集成以及性能优化的重要组成模块。处于安全考虑,比如企业对下载内容进行病毒检测,或者屏蔽成人网站,,代理可以对请求和响应进行过滤。

1.8.2 缓存

这是一种特殊的http代理服务器,可以将经过代理传送的常用文档复制保存起来。

1.8.2 网关

网关是一种特殊服务器,作为接受其他服务器中间体使用,通常用于将http流量转换成其他协议。

1.8.3 隧道,

http隧道是一种常见的用途是通过http连接承载加密安全套接字层流量,

1.8.5 Agent代理

用户agent代理是代表用户发起http请求的客户端程序。所有发布web请求都是通过agent代理。最常见的agent代理就是浏览器,但是还有很多其他的代理,比如网络蜘蛛,或者web机器人。自动搜索引擎就是web蜘蛛,它可以从世界范围获取web页面,

第二章 URL与资源

所有的东西都有编号,比如快递收货地址,只有这样,整个城市协作才能进行下去。

2.1浏览因特网资源

那么不得不提到URL,他是URI的子集,它通过资源位置来识别资源。URL分成3部分

同时,URL可以通过HTTP之外的其他协议访问资源,他们指向因特网上的任意资源,

  • E-mail账户:: mailto:[email protected]
  • 其他协议ftp://ftp.lots-o-books.com/pub/complete-price-list.xls
  • 从流视频服务器上下载电影 rtsp://www.joes-hardware.com:554/interview/cto_video

URL提供了一种统一的资源命名方式,大多数URL都有同样的:"方案://服务器位置/路径"

2.2 URL语法

<scheme>://<user>:<password>@<host>:<prot>/<path>;<params>?<query>#<frag>

2.5 常见的web方案

方案 描述
http http://baidu.com
https https://baidu.com
mailto email地址,mailto:
ftp 文件传输协议,用于向服务器传输下载文件
rtsp,rtspu 实时流传输协议,音频视频传输协议
file 表示一台指定主机,本地硬盘,局域网共享文件
news 访问特定新闻
telnet 方案telnet用于访问交互式业务

第三章 http报文

简单来说http报文就是http所搬运的东西。

3.1.1 报文流入源端服务器

http使用术语流入和流出,分别代表发送,request,response,
所有报文都是向下移动的。

报文组成部分
  • 方法,method,包括get post head
  • 请求URL
  • 版本version
  • 状态吗 status-code
  • 原因短句(reponse-phrase)
  • 首部 header
  • 实体部分entity-body
1.起始行

require使用哪种http,get还是post方法,reponse 200还是404

2.响应行

http方法+200/404

3.方法

get post

4.状态码200
5.原因短语

ok

6.版本号

http1.1/http2。0

3.2.3 首部

Content-length: 19

3.3 方法。

  • get方法 最常用的方法,一般用于获取文件

  • head,方法,获取头部信息,不获取 主体信息,

    • 一般用于查看在不获取资源的情况下了解资源的情况,类型。
    • 通过查看响应中的状态码,看看某个对象是否存在,
    • 通过查看首部,测试资源是否被修改了。
      只有当服务器开发者为了保证head返回的和get请求所返回的完全相同,遵循http1.1规范,那就必须实现HEAD方法,
  • PUT方法
    与get完全相反,put用于向服务器写入文档,有些发布系统允许用户创建web界面,并用put直接将其安装到web服务器上面去。

  • POST方法
    用于向服务器传输数据,一般用于发送表单。

  • TRACE
    客户端发送一个请求,这个请求可能穿过防火墙,代理,网关,或者其他地方,这些都有可能修改原始的http请求,Trace允许用户查看最总请求服务器的时候是什么样。

  • OPTIONS
    允许请求服务器告知其支持的各种功能,可以询问服务器通常支持哪些方法,或者对某些方法,支持哪些方法。

  • delete
    用于删除文件,

扩展方法

http被设计成可扩展的,这样。

方案 描述
lock 允许用户锁定某些资源
MKCOL 允许用户创建资源
COPY 便于在服务器上修复资源
MOVE 在服务器上移动资源

3.4 状态码

总共五大类1xx,2xx,3xx,4xx,5xx

状态码 含义
100 说明收到了请求的初始部分,请客户端继续
101 说明服务器正在根据客户端指定,将协议切换成Update首部所有协议

1。 客户端与100 Continue

如果客户端在向服务器发送一个实体,并且愿意在发送实体之前等待100 Continue响应,那么客户端就要发送一个携带了值为100 Continue 的Expect请求首部,如果客户端没有实体,就不应该100 Continue Expect 首部,因为这样会让服务器误以为客户端要发送一个实体。

从多个角度来说,100 Continue都是一种优化。客户端应用程序只有避免向服务器发送一个服务器无法处理或使用的大实体时候,才应该使用100Continue。

由于起初对100 Continue状态存在一些困惑(而且以前有些实现在这里出过的问题),因此发送了值为100Continue响应,超时一定的时候,客户端应该直接将实体发送出去。

实际上,客户端程序的实现者也应该做好应对非预期 100 Continue响应的准备。
作为三次握手的第一个步骤,发送100

  • 1:A发,B收, B知道A能发
  • 2:B发,A收, A知道B能发收
  • 3:A发,B收, B知道A能收

2.服务器与 100 Continue

如果服务器收到了一条带有值为100 Continue的expect首部请求,他会用到100 Continue响应或一条错误码来进行响应,服务器永远不应该向没有发送100 Continue期望客户端发送100 Continue状态码。但是错误的服务器可能会这么做。

这就是为什么要三次握手,第一次a发,b收,b可以得出结论a能发验证码,第二次b发a收,a可以得出结论,b正常,可以接收到他发的信息,同时可以发回信息给他,第三次,a又重新发信息给b,b受到信息后得出结论,不单是可以发信息的,同时,他也收到了我发的信息。通过三次握手,得出a,b互相得出结论,他们都具备收发功能。

3.代理与100 Continue的关系,

代理收到100 期望请求,他需要做如下事情,

  • 下一个服务器兼容http1.1,或者代理也不知道下一个服务器兼不兼容,他会将expect首部放在请求中进行转发吗,但是如果他知道下一个服务器只能兼容HTTP1.1之前的版本,那么就应该以417 Expectation Failed进行响应。
    但是如果代理服务器与http/1.0之前版本兼容,在请求中放入expect首部和100Continue 值,那么,他就不该将100Continue响应发给客户端,
    代理维护一下关于下一条服务器是否支持所支持http版本的状态信息是有好处的,这样就能更好的处理哪些带有100 Continue请求。

2xx 状态码

状态码 原因短句 含义
200 OK 请求没有问题,实体的主体部分包含了所请求的资源
201 Created 用于创建服务器对象的请求,比如PUT,响应的实体部分中应该包含各种引用了已创建的资源URL,但是服务器必须在发这个请求之前创建好对象
202 Accepted 请求已经被接受,但服务器没有任何动作
203 Non-Authoriative Information 实体首部验证信息不来自于本服务器
204 No Content 响应报文中包含若干首部和一个状态行,但是没有实体部分,主用于浏览器不转为显示新文档,只是刷新一个页面的表单
205 Reset Content 另一个主要用于浏览器的代码,负责告知浏览器清除当前页面所有HTML表单元素
206 Partial Content 部分请求成功

3xx:::重定向请求

状态码 原因短句 含义
300 Multiple Choices 客户一个请求指向多个资源
301 Move Permanently 在请求的URL已经被移除的时候,响应的Location首部中应该包含了资源现所在处的URL
302 Found 与301相似,但是,客户端应该使用location首部给出URL来临时定位资源
303 See Other 告诉客户应该去另一个URL来获取资源,新的url应该位于报文Location首部
304 Not Modified 未修改,看不懂啥意思。。
305 Use Proxy 用户必须通过一个代理才能访问资源
306 未使用 没有用过
307 Temporary Redirect 与301类似,但客户端应该使用Location首部给出的URL来临时定位资源

4xx客户端错误状态码

状态码 原因短句 含义
400 bad request 用于告知客户他发送了一个错误的请求
401 Unauthorized 未验证身份
402 Payment Required 未使用
403 Forbidden 请求被拒绝
404 Not Found 找不到资源
405 Method Not Aollowed 所请求的方法不支持
406 Not Acceptable 服务器端找不到客户端所指定的资源类型
407 Proxy Authentivation Require 代理服务器要求验证
408 Request Timeout 请求超时
409 Conflict 请求主体冲突
410 Gone 找不到

5xx 服务器错误状态码

状态码 原因短句 含义
500 Interal Server Error 服务器遇到一个妨碍它为请求提供服务的错误时,使用
501 Not Implemented 客户端发起请求超出服务器能力范围
502 Bad Gateway 作为代理或者网关,使用从服务器清秀响应链的下一条链路上收到了一条伪响应
503 Service Unavailable 说明服务器现在无法提供请求,但是将来可以,如果服务器什么时候可以了,可以在响应中包含一个Retry-After 首部
504 Gateway Timeout 和408类似,只是这里响应来自一个网关代理,他在另一端响应超时了
505 HTTP Version Not Support 服务器收到的请求使用了他无法或者不愿意支持的协议版本时,使用此状态码,有些服务器应用程序会选择不支持协议的早期版本。

3.5 首部

首部有以下5种类型

  • 通用首部

Date:Tue,3 Oct 1974 02:16:00

  • 请求首部
    请求报文所特有的

Accept: "/"

  • 响应首部

Server: Tiki - Hut/1.0

  • 实体首部

Content-Type: text/html; charset=iso-latin-1

  • 扩展首部

通用首部

有些首部提供了与报文相关的最基本信息,他们称之为通用首部,他们向和事佬一样,无论是报文是什么类型,都为其提供一些有用信息。
例如:不管是构建请求报文还是响应报文,创建报文的日期和时间都一样。

首部 描述
Connection 允许客户端和服务端指定与请求一致的选项
Date 提供日期和时间标志
MIME-VERSION 给出了发送服务端使用的MIME版本
Trailer 如果报文采用了分块传输编码方式,就可以使用这个首部列出位于报文拖挂部分的首部集合
Trailer-Encoding 告诉接受端为了保证报文的可靠传输,对报文采用了什么编码方式
Update 给出了发送端可能想要升级使用的新版本或者协议
Via 显示了报文经过的中间节点(代理?)

通用缓存首部

HTTP/1.0引入了第一个允许HTTP应用程序缓存对象本地副本的首部,这样就不能需要总是从服务端获取了。

1. Accept 首部

首部 描述
Accept 告诉服务器能够发送哪些媒体类型
Accept-Charset 告诉服务器能够发送哪些字符集
Accept-Encoding 告诉服务器能够发送哪些编码方式
Accept-Language 告诉服务器能够发送哪些语言
TE 告诉服务器可以使用哪些扩展传输代码
  1. 条件请求首部
    有时候客户端洗碗加上某些限制,比如,如果客户端已经有了一份文档副本,就希望只在服务器上的文档与客户端拥有的副本所有区别时,才请求传输文档,
首部 描述
Expect 允许客户端列出来某些请求所要求的服务器行为
If-Match 告诉服务器能够发送哪些字符集
If-Modified-Since 除非在某个指定的日期
if-none-match 如果提供的实例标记与当前文档实体标记不相符,就获取文档
if-range 允许对文档的某个方位按条件进行请求
if-unmodified-since 除非在某个指定日期范围之后资源没有没修改过,否则就限制这个请求
Range 如果服务器支持范围请求,就请求资源的指定方位

3.安全请求首部

HTTP本身就是支持一种简单机制,可以对请求进行质询/响应认证,这种机制要求客户端在获取特定的资源之前,先对自身进行认证,这样就可以使事物稍微安全一些,

首部 描述
Authorization 包含了客户端提供给服务器,以便对自身进行认证的数据
Cookie 客户端向它服务器传送一个令牌---他并不是真正的安全首部,但确实包含了安全功能
Cookie2 用来说明请求段支持cookie版本

4.代理请求首部

随着internet,发展,网上代理普遍应用,人们定义了几个首部来协助其他更好的工作,

首部 描述
Max-Forward 在通往源服务端路径上,将请求转发给其他代理或者网关最大次数--与TRACE方法一起使用
Proxy-Authorization 与Authorization首部相同,但是这个首部是在与代理进行认证使用
Proxy-Connection Connection首部相同,但是这个首部是在与代理进行连接时使用

3.5.3 响应首部

响应报文有自己的响应首部。响应首部为客户端提供了一些额外信息,比如谁在发送响应,响应者功能,

首部 描述
Age 从最初创建开始,响应持续时间
Public 服务器为其资源支持的请求方法列表
Retry-After 如果资源不可用的化,就在日期或者时间重试
Server 服务器应用程序软件的名称和版本
Title 对html文档来说,就是html文档的源端给出的标题
Warning 比原因短句中更详细一些的警告报文

1.协商首部

日过资源有多种表示方法

首部 描述
Accept-Ranges 对此资源来说,服务器可接受的范围类型
Vary 服务器查看其他首部的列表,可能会使响应发生变化,也就是说,这是一个首部列表,服务器会更具这些首部内容挑选出最适合的资源版本发送给客户端。

2.安全响应首部

首部 描述
Proxy-Authorization 与Authorization首部相同,但是这个首部是在与代理进行认证使用
Set-cookie 不是真正的安全首部,但隐含有安全功能,可以在客户端设置一个令牌,以便服务器对客户进行标识
Set-cookie2 与set-cookie类似,
www-authenticate 来自服务器的对客户端的质询列表

3.5.4 实体首部

许多有首部的可以用来表示http报文的负荷,由于请求响应报文中都可以可能包含实体部分,所以在这两种类型的报文都可以出现这些首部。

实体首部提供了有关实体及其内容的大量信息,,总之,实体首部可以告诉报文的接收者他在对什么进行处理。

首部 描述
Allow 列出了可以对此实体执行的请求方法
Location 告知客户实体实际上位于何处,用于将接受端指向资源的

1.内容首部,

内容首部提供了与实体内容有关的特定信息,说明了类型,尺寸以及处理他们所需的其他有用信息,比如web浏览器可以通过查看返回的内容类型,

首部 描述
Content-base 解析主体中相对url使用的基础url
Content-Encoding 对主体执行的任意编码方式
Content-Language 理解主体时最适宜使用的自然语言
Content-length 主体长度
Content-Location 资源所在处实际位置
Content-MD5 主体MD5校验和
Content-Range 在整个资源中,此实体表示的字节范围
Content-Type 这个主体的对象类型

2.实体缓存首部

首部 描述
Etag 于此实体相关的实体标记
Expires 实体不在有效,要从原始源资源在次获取次实体的日期和时间
Last-Modified 这个实体最后一次被修改的日期和时间

nodejs里面的Timers

Timers在nodejs以内和以外

Timers模块在nodejs包括函数,执行一些代码,在一系列时间之后。Timers不需要通过require()来引用,因为作为全局变量。为了完全理解,当Timers函数将被执行,这是一个好主意来读取,在ndoejs时间循环。

控制nodejs控制事件连续

nodejsAPI提供一系列计划代码来执行一些观点,在某个时刻执行。这个函数可能看起来很熟悉,直到他们大多数浏览器都能用。但是nodejs实际上提供他们自己履行下面几个方法。Timers整合关键系统,并且尽管这个事实,API镜像浏览器API,这里有一些不同的实现方式

什么时候说? 执行 setTimeout()

setTimeout()能被使用计划代码执行在一个特定毫秒。这个函数相似于window,setTimeout(),来自于浏览器jsAPI,然而一个字符串代码不能通过来执行。
setTimeout()接受第一个参数作为函数来执行。第二个参数作为毫秒来延迟。另外参数能够被包括,并且这些将会传递通过函数的形式。这是一个例子

function myFunc(arg) {
  console.log(`arg was => ${arg}`);
}
setTimeout(myFunc, 1500, 'funky');

上面这个函数myFunc()将会被执行在接近1500ms之后,
这个超时间隔,是一个设置成在一定间隔事件后执行。这是因为其他阻止或保留在事件循环中的执行代码会将超时执行推回。唯一的保证是超时将不会比声明的超时间间隔更早执行。
setTimeout()返回一个Timeout对象,这个能被用来参照超时。这个返回的对象能被用来取消超时,例如clearTimeout()

“Right after this”执行setImmediate()

setImmediate()将会执行代码在最后,当前事件循环,将会被执行在I/O操作之后。并且在所有顶实际被计划在下个事件循环。这个代码执行能够相称作为即可执行。意味着所有的代码在setImmediate()之后的函数调用将被执行在他之前。
第一个参数是一个函数,setImmediate()将会是函数被执行,所有随后的参数将会被传递给函数,当他被执行,下面是一个例子:

console.log('before immediate');

setImmediate((arg)=>{
  console.log(`executing immediate: ${arg}`)
},'so immediate');
console.log('after immediate');

上面函数传递setImmediate()将会执行在所有可运行代码。并且打印输出会如下所示:

before immediate
after immediate
executing immediate: so immediate

setImmediate()返回一个Immediate对象,这个能被用作取消计划执行。就是说,这个返回的东西能够取消定时器。
注意:不要得到setImmediate()拒绝,通过process.nextTick().这里有一些主要的方式,他们的不同。这第一个是process.nextTick()将会运行在所有Immediate,所以它被同时设置成所有I/O。这个时刻,是process.nextTick(),这个解释不能停止,就像函数内。参照前面的导航来更好的明白process.nextTick()操作。

无限循环解释器setInterval()

如果这里有一个阻塞的代码,它应该执行多次的事件,setInterval()能够被用来执行这些代码。setInterval()运行函数参数,能运行无数次,就像setTimeout运行的函数调用自己一样。他的延迟无法保障,就像setTimeout那样,因为操作,取决于事件循环,因此应该将他当作事件循环来对待,看下面的例子:

function intervalFunc() {
  console.log('Cant stop me now!');
}
setInterval(intervalFunc, 1500);

在上面的例子,每隔1.5秒执行一次。
返回的对象能被用来重置。

清理未来。

如何停止定时器。用他们返回的对象可以重置。看如下例子:

const timeoutObj = setTimeout(() => {
  console.log('timeout beyond time');
}, 1500);

const immediateObj = setImmediate(() => {
  console.log('immediately executing immediate');
});

const intervalObj = setInterval(() => {
  console.log('interviewing the interval');
}, 500);

clearTimeout(timeoutObj);
clearImmediate(immediateObj);
clearInterval(intervalObj);

离开超时之前

记住,定时器对象通过i哦setTimeout()setInterval返回.这个定时器对象提供两个函数,只在用unref()ref()来增强超时行为。如果这里有一个Timeout对象计划用一个set函数,unref()能被调用在一个对象。这将会轻微的改变行为,并且不调用Timeout对象,如果他是最后一个代码来执行。这个定时器对象将不会继续保持进程活动状态,等待执行。
这是一个相似的时尚,一个定时器对象,他有unref()被调用,他能移除,行为,被ref()调用的行为,在相同的定时器对象,这个将会确保他的执行。意识到,然而,这是不确切,这个初始行为性能理由的。看下面这个代码。

const timerObj = setTimeout(() => {
  console.log('will i run?');
});
// 如果左边独立,这个声明将保留在上面的超时来运行。知道超时将会是唯一的事情来确保程序退出
timerObj.unref();
// 我们也可以给他回复原状通过ref来给,ref写在immediate里面
setImmediate(() => {
  timerObj.ref();
});

进一步深入事件循环

这里有更多对于事件循环和定时器,比我们的导航更多的。为了学习他们学习nodejs内部事件循环,以及定时器如何执行的。去阅读The Node.js Event Loop, Timers, and process.nextTick().

Vue - 源码解析

从开头到结尾,感觉有点像黑客破解网站一样😄。

前言

从年初开始,用了将近6个月的vue,起初很懵逼,但是撸完一遍vue官方教程后,会用一点,但是vuex我还是很懵逼,偶然看到一篇博客解析vue源码,很受启发,决定挖坑,我也要做vue源码解析,我觉得我也可以做到。虽然我仅仅只是知道vueMVVM双向绑定是基于Object.definedPrototype()来设置set 和get 。

版本

version": "2.5.17-beta.0

主线索

当然就是vue这个对象了。先看web端的吧,服务器端的不看先。Vue.js\src\platforms\web\entry-runtime.js这就是我第一次见到的最表层的vue对象了,
image

进一步深入,Vue对象在runtime/index.js文件中引入,

image

点开一看,发现依然在更深层次core中的index.js文件中引入
image

继续点开,终于发现了Vue的构造函数,结构如下图。它位于Vue.js\src\core\instance\index.js中,
image

构造函数,做了啥???不难看出,this instanceof Vue,this即是vue构造函数本身,vue构造函数是实例化的 new Vue所返回的vue实例,this指向vue实例,实例原型必须是有vue,否则报错,意思就是强制使用new 实例化vue对象。

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

image

  • 首先看 initMixin函数,他到底做了啥??他为vue扩展了_init方法

  • stateMixin到底做了啥??他为vue扩展了$watch方法,

  • 接下来是eventsMixin函数,他为vue扩展了$on$once$off,$emit方法;

  • 接下来是lifecycleMixin函数,他为vue扩展了_update,forceUpdate,destroy,方法,表面意思就是声明周期混合。

  • 接下来是renderMixin函数,他为vue扩展了$nextTick_render,方法,

好吧,拥有这些初始化方法的vue构造函数成为了一个最基本的vue对象。
下面的百度脑图清晰描述vue构造函数下面的基本方法
其中_开头的是内部方法,而$开头的方法是外部方法,供外部使用。

image

那么vue构造函数就基本方法已经清楚,那么接下来从初始化一个vue实例开始看,vue源码。

import App from './App.vue';
window.app = new Vue({
  data: {
    msg: 'sfdf'
  },
  render (h) {
    return h('p', this.msg)
  }
}).$mount('#root')
setTimeout(() => {
  app.msg = '232423'
}, 1000)

在浏览器控制台打印app,发现有msg这条属性,同时有set和get
image
打开源码发现首先执行下面代码,option就是以下内容

this._init({
  data: {
    msg: 'sfdf'
  },
  render (h) {
    return h('p', this.msg)
  }
})

,那么下一步查看_init方法,

// 只截取关键代码
export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // a flag to avoid this being observed
    vm._isVue = true
    // merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }

    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}
  • 首先
    vm=this,而this指向vue实例,uid是一个自增id,初始化时刻自增。
    vm.isVue一个属性,不知道做啥用
    然后是
vm.$options = mergeOptions(
  resolveConstructorOptions(vm.constructor),
  options || {},
  vm
)

字面意思就知道,是合并option属性的,mergeOption函数接受3个参数,第一个是父,第二个是子,第三个是vue对象本身。

export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  if (process.env.NODE_ENV !== 'production') {
    checkComponents(child)
  }

  if (typeof child === 'function') {
    child = child.options
  }

  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)
  const extendsFrom = child.extends
  if (extendsFrom) {
    parent = mergeOptions(parent, extendsFrom, vm)
  }
  if (child.mixins) {
    for (let i = 0, l = child.mixins.length; i < l; i++) {
      parent = mergeOptions(parent, child.mixins[i], vm)
    }
  }
  const options = {}
  let key
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

image

上面这段函数可以看出,他会合并父子属性,而后合并子属性的宏。
image

之后就是核心部分,

    vm._self = vm                         // 初始化的时候,自己就是自己
    initLifecycle(vm)                    // 执行初始化生命周期
    initEvents(vm)                          // 初始化事件
    initRender(vm)                       // 初始化渲染
    callHook(vm, 'beforeCreate')                  // 初始化  执行  beoforeCreate   钩子
    initInjections(vm)               // resolve injections before data/props
    initState(vm)                   // 初始化状态
    initProvide(vm) // resolve provide after data/props   初始化提供???
    callHook(vm, 'created')                      // 调用created钩子

首先看 initLifeCycle 函数

  vm.$parent = parent      // 将传入参数的parent赋值到vm实例的parent上面,parent是对外暴露属性。
  vm.$root = parent ? parent.$root : vm// 根节点如果是parent,则是它的根节点,否则根节点就是我本身
  vm.$children = []    // 子节点是数组
  vm.$refs = {}     // 用于访问子组件 中 定义了ref=“name”,
  vm._watcher = null       // 观察者
  vm._inactive = null       // 活动的
  vm._directInactive = false       // 啥意思,草
  vm._isMounted = false        // 是否挂载
  vm._isDestroyed = false      // 是否被销毁
  vm._isBeingDestroyed = false     // 是否正在被销毁

明显就是为vue构造对象,初始化一些实例,

接下来是initEvents函数

export function initEvents (vm: Component) {
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  // init parent attached events
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}

貌似是父子组件在通讯的时候用到,this.emit('234')方法;

接下来是initRender函数

export function initRender (vm: Component) {
  vm._vnode = null // the root of the child tree
  vm._staticTrees = null // v-once cached trees
  const options = vm.$options
  const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
  const renderContext = parentVnode && parentVnode.context
  vm.$slots = resolveSlots(options._renderChildren, renderContext)
  vm.$scopedSlots = emptyObject
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)

  const parentData = parentVnode && parentVnode.data

  /* istanbul ignore else */
  if (process.env.NODE_ENV !== 'production') {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
      !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
    }, true)
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
      !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
    }, true)
  } else {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
  }
}

初始化渲染函数,查看其中的$createElement,这个属性用于创建虚拟节点,
image

vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)    // 第一个参数默认是 vm
vm.$createElement('p','12321')          // 会输出一个虚拟的<p>123</p> 节点

下面查看createElement方法

export function createElement (
  context: Component,                 // vm实例
  tag: any,        // 标签名  eg:   p,div,span
  data: any,         // 值
  children: any,      // 子节点,类似于domcument.body.children // []
  normalizationType: any,     // 他应该是某个数字吧。
  alwaysNormalize: boolean      // 是否需要初始化
): VNode | Array<VNode> {
  if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children
    children = data
    data = undefined
  }
  if (isTrue(alwaysNormalize)) {
    normalizationType = ALWAYS_NORMALIZE
  }
  return _createElement(context, tag, data, children, normalizationType)
}

callHook函数

export function callHook (vm: Component, hook: string) {
  // #7573 disable dep collection when invoking lifecycle hooks
  pushTarget()
  const handlers = vm.$options[hook]
  if (handlers) {
    for (let i = 0, j = handlers.length; i < j; i++) { // beforeCreate, 遍历他所有的方法
      try {
        handlers[i].call(vm)     // 为每一个方法调用的时候绑定this。方法内部的this就是vm,前提是你用的普通函数,才有this,箭头函数是没有this的。
      } catch (e) {
        handleError(e, vm, `${hook} hook`)  // 否则报错
      }
    }
  }
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook)     // 如果有钩子事件,就发送给他的父组件。
  }
  popTarget()
}

接下来是initInjections函数,

export function initInjections (vm: Component) {
  const result = resolveInject(vm.$options.inject, vm)  // 另一个函数,传入vm实例,
  if (result) {
    toggleObserving(false)
    Object.keys(result).forEach(key => {
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== 'production') {
        defineReactive(vm, key, result[key], () => {
          warn(
            `Avoid mutating an injected value directly since the changes will be ` +
            `overwritten whenever the provided component re-renders. ` +
            `injection being mutated: "${key}"`,
            vm
          )
        })
      } else {
        defineReactive(vm, key, result[key])
      }
    })
    toggleObserving(true)
  }
}

暂时看不懂,下一个

initState函数

export function initState (vm: Component) {    // 用于初始化一些状态
  vm._watchers = []    // 设置一个要被观察的空对象,
  const opts = vm.$options      //  opts就是vm的传入的参数
  initProps(vm, opts.props)    //  初始化prop,也就是父组件传入的参数
  initMethods(vm, opts.methods)    //  初始化一些可以在模板中,使用的自定义函数
  initData(vm)    // 初始化data,如果没有,就设置成根节点 传入`{}`
  initComputed(vm, opts.computed)    // 初始化计算属性 computed
  initWatch(vm, opts.watch)   // 初始化要观察的对象
}

挑一两个来说吧,

  • 首先说initdata
    用于初始化数据模型,下面是删减过的函数initdata
function initData (vm: Component) {
  let data = vm.$options.data    // 拿到data函数返回的一个新的对象(数据模型Model)
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  // proxy data on instance
  const keys = Object.keys(data)    // 枚举所有属性的键值
  const props = vm.$options.props    // 检查data是否和prop重复要用到
  const methods = vm.$options.methods   // 检查data是否和methods中的属性是否重,要用到
  let i = keys.length
  while (i--) {     // 遍历每一个data属性,
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {   // 如果  methods有 和 data的属性 重复就报错
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    if (props && hasOwn(props, key)) {    // 如果  prop 有 和 data的属性 重复就报错
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(key)) {
      proxy(vm, `_data`, key)   // 如果都可以就代理vm对象  中的所有data属性
    }
  }
   // observe data   ,data 和 prop对象不能有重复的属性,
  observe(data, true /* asRootData */)    // 最主要的函数,观察data对象,
}

proxy函数

const sharedPropertyDefinition = {
  enumerable: true,    // 可枚举
  configurable: true,    // 可设置
  get: noop,      // function(){} 空函数
  set: noop       // function(){}  空函数
}
export function proxy (target: Object, sourceKey: string, key: string) {
  sharedPropertyDefinition.get = function proxyGetter () {   // 空函数的get返回这个属性的值,代理优化了值,this._data.msg = this.msg
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {  // 空函数的set设置函数的值,代理优化了值,this._data.msg = this.msg
    this[sourceKey][key] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

observe 可以说是vue的核心,下面就是obserber函数。

// observe 函数
export function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

这个没啥好看,虽然我也看不太懂,但是关键在于Observe对象

// Observe
export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that has this object as root $data
  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
      this.observeArray(value)    // 如果是数组
    } else {
      this.walk(value)     // 如果是对象
    }
  }
  /**
   * Walk through each property and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk (obj: Object) {      // 如果是对象,用object.keys 来遍历对象所有可枚举属性,然后调用defineReactive函数,并传入对象,和他的键值。
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }
  /**
   * Observe a list of Array items.
   */
  observeArray (items: Array<any>) {      // 如果是数组,遍历所有数组,重新调用该方法,直到它全部是对象位置,真的很巧妙。
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])     // 如果是数组就递归自身。
    }
  }
}
/**
 * 设置响应式函数
 */
export function defineReactive (
  obj: Object,    // 对象
  key: string,     // 键值
  val: any,       // 值
  customSetter?: ?Function,     // set函数?
  shallow?: boolean    // 浅??
) {
  const dep = new Dep()   // 构造dep对象
  const property = Object.getOwnPropertyDescriptor(obj, key)    // 用于获取obj对象的描述,也就是获取传入对象的一些自定义属性。
  if (property && property.configurable === false) {
    return
  }
  // cater for pre-defined getter/setters
  const getter = property && property.get    // 获取传入的data原本的get属性,
  const setter = property && property.set    // 获取传入的data原本的set属性,
  if ((!getter || setter) && arguments.length === 2) {      
    val = obj[key]
  }
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}

image

Reference

图解HTTP

作为一个大前端,了解HTTP是很必要的。

joma

概述阻塞和不阻塞

概述阻塞和不阻塞

这个概述了nodejs阻塞和非阻塞的区别。这个概述将会参考事件循环和libuv,但不需要实现了解这些主题。读者被假设有如下基础,明白js语言以及nodejs回调模式

  • I/O主要指与libuv支持的系统磁盘和lubuv支持的网络之间的交互。

阻塞

阻塞是当执行添加js在nodejs处理必须等待未阻塞的js操作完成。这发生是因为事件循环是不能继续执行js,当一个阻塞的操作被占用。
在Node.js,js是展示低效因为CPU密集型而不是等待非JavaScript操作,例如I/O,这个不是一般的参照去阻塞。同步方法在nodejs一般的库,用libuv,是大多数一般的被用来阻塞操作。本地模块也一样有阻塞的方法。
所有I/O方法在Node.js一般库提供异步版本,这个是不阻塞的,并且接受回调函数。一些方法也有阻塞对方,他们的名字以Sync结尾。

对比代码

阻塞方法执行同步和不阻塞方法执行异步
使用文件系统模块作为一个例子,这个是一个同步文件读取:

const fs = require('fs');
const data = fa.readFileSync('/file.md');    // blocks here until file is read

下面是一个等价的异步方法:

const fs = require('fs');
fs.readFile('./README.md', 'utf-8', (err, data) => {
  if (err) throw err;
  console.log(data);
});

第一个例子,看起来比第二个例子简单,但是一个坏处就算第二行阻塞了执行任何读取文件之后的代码,知道文件成功读取。注意:同步的版本如果发生错误,他将需要捕捉错误,或者让进程奔溃。在一部版本,可以自由决定如何报错。
让我们扩展这个例子

// 嗯这段同步的代码和上一个例子没有区别
const data = fs.readFileSync('./README.md', 'utf-8');
console.log(data);
console.log('finished');

然后,这里有一个相似的,但是不等价的异步函数代码,你会发现,finished会被在data之前打印出来

fs.readFile('./README.md', 'utf-8', (err, data) => {
  if (err) throw err;
  console.log(data);
});
console.log('finished');

上面第一个例子,打印将会被宰morework之前调用,在第二个例子,fs.readfile不阻塞js代码继续执行,所以会这样。在不等文件读取完成的情况下运行moreWork()的能力是允许更高吞吐量的关键设计的选择。

并发和吞吐量

js编译器在nodejs是单线程,所以并发指的是事件循环的容量来执行js回调函数,在完成其他工作之前。所有代码被预期以并发的形式运行必须允许事件循环继续允许作为没有js操作的像I/O形式继续运行。

作为一个例子,让我们考虑下面这个例子,每个请求对于web服务器花费50ms来完成并且其中的45ms是数据I/O,他们可以是异步的完成。选择不阻塞异步操作可以释放每个请求45ms来处理其他请求。这是一个通过选择使用异步的方法来操纵,这有着明显的容量差异。
事件循环是不一样的模型,对比很多其他语言,哪里可以创建额外的线程来处理并发工作。

混合阻塞和非阻塞代码是危险的

这里有一些模式,应该避免,当处理I/O。让我们看下面的例子:
image

const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
  if (err) throw err;
  console.log(data);
});
fs.unlinkSync('/file.md');

上述例子,fs.unlinkSync()像是会跑在fs.readFile()前面,他会删file.md文件,在他真正读取该文件之前,一个更好的方式,去写这个代码,是不阻塞的完成,并且保证去执行正确的顺序:

const fs = require('fs');
fs.readFile('/file.md', (readFileErr, data) => {
  if (readFileErr) throw readFileErr;
  console.log(data);
  fs.unlink('/file.md', (unlinkErr) => {
    if (unlinkErr) throw unlinkErr;
  });
});

上面位置不阻塞的调用fs.unlink()在回调fs.readFile()之内,这保证的正确的执行操作顺序。

reference

结论:nodejs好像真的就是一些特别有钱的人闲着没事玩出来的一个产物。但是请回头看看tensorflow.js这个目前在github上比较火的JavaScript 机器学习库。JavaScript正在发展,JavaScript不是玩具。

两点钟我睡不着

每天都在1~2点睡,7点半闹铃,8点上班

(6|9)点下班,在STARBUCKS浪到10点半,回到宿舍11点,想想儿童时期这个时候已睡着,(那时候看到大人总是不睡觉)。我还是继续摊开书学习,要么写博客,要么翻译经典文章,顺便偶尔玩一两把王者荣耀,顺便看一两部电影,按这效率1一周看一本简单的薄的书,厚的(js红宝书)难的(js算法,js函数式编程)肯定看不了,但想想现状(技术能力)已经比一年前好很多(学react也是照的官网翻译基础教程学的,又是webpack,又到koa2/异步编程踩坑,顺便又玩一下量化交易赚了十几块,入了租服务器这个坑,100rmb/mon,翻墙)

晚上。听起了许美静的《都是夜归人.mp3
到了12点, 想想自己又没女票又没钱,加班多工资少,房子租的,车子共享的。。。根本睡不着,我继续写博客。
到了 1点 , 想想自己的技术渣,沟通能力差,别人的话要听几次才能理解,也或许这辈子都成不了大牛,用了1年多的js ,居然连koa.js的源码也看不懂,在代码界也不会有大发展,睡不着!不如先去洗个澡。
ps:最好的美好,就是跟昨天比,进步了,就算。

JavaScript 在浏览器中的事件循环

关于js事件

你应该看看image

什么是call stack(调用栈)

  • queues: 先入先出(js和settimeout和gui和时间等线程按照队列形式执行,即先入先出顺序)
  • stack: 后入先出(而调用栈则按照后入先出的顺序依次执行,明白这个道理对后续的异步函数学习很有帮助)

所有被调用的js函数都会被放入栈,按照后入先出的顺序依次执行。理解这个对于理解js异步函数非常重要。JavaScript(引擎)是单线程的,Event loop并不属于JavaScript本身,但JavaScript的运行环境是多线程/多进程的,运行环境实现了Event loop。,js的单线程执行完美避开了GUI(css图形界面渲染)冲突,但同时js的运行环境又是多线程的,常见的线程有js主线程,GUI图形界面绘制进程,点击事件等进程,setTimeout/setInterVal时间进程,这么多进程共同协作,才构成了js灵活多变的浏览器操作。这样的设计异常巧妙。而html5又加入了web worker,但它是另一个进程,和我们所讨论的互不干涉,es6又加入了promise.then,但是promise真的不属于多线程,promise内部原理我是理解不了。
但是请看下图,

  • 1.最先被调用的是正常的js闭包只执行代码。
  • 2.排第二的是nodejs里面的process.nextTick()。
  • 3.排第三的则是promise().then()返回的代码,你可以将他理解做另一个线程,但他一定排在setTimeout线程前面。
  • 4.再就是js标准里面的setTimeout了,他就是另一个线程。
  • 5.再就是nodejs里面的setImmediate,他被放在了最后。

image

再次科普一下promise setTimeout

  • 宏任务主要包含:script(整体代码)、setTimeout、setInterval、I/O、UI交互事件、setImmediate(Node.js 环境)
  • 微任务主要包含:Promise、MutaionObserver、process.nextTick(Node.js 环境)

我们的JavaScript的执行过程是单线程的,所有的任务可以看做存放在两个队列中——执行队列和事件队列。

执行队列里面是所有同步代码的任务,事件队列里面是所有异步代码的宏任务,而我们的微任务,是处在两个队列之间。

当JavaScript执行时,优先执行完所有同步代码,遇到对应的异步代码,就会根据其任务类型存到对应队列(宏任务放入事件队列,微任务放入执行队列之后,事件队列之前);当执行完同步代码之后,就会执行位于执行队列和事件队列之间的微任务,然后再执行事件队列中的宏任务。

为了再次深入了解promise 和 settimeout和setImmediate,请查看如下代码

image
😴醉了,根本不是线程的问题,promise内部是保证一定完成,才进行下一步,所以必须先将promise内部函数执行完毕,才进行then后的函数,而then则属于微任务,所以它不是setTimeout和setImmediate这种宏任务的的对手,setTimeout线程位于setImmediate线程后面,所以then里面setImmediate先执行,setTimeout后执行

再看一个例子

我将promise命名一个字面量,输出的结果却又不一样了。真令人困惑。promise被分叉成两个.then
image
同样的函数我执行5次,如下图,居然发现输出结果不一致,为什么呢???
image
发现规律就是1永远排在最前面,246为一组,35为一组,这两组不定期排序。。本身是不是说明了事情就是promise的这两个单独的then让promise分叉了,任意时间返回结果。。。但是35是setTimeout线程,而246则是setImmediate线程,settimeout线程不是永远应该排在setImmediate后面么。。。

从 Error看 调用栈 call stack

如图,请认真查看这个报错的stack trace栈追踪,事件执行顺序依然是 foo -> bar -> baz -> object,而这条追踪对应的是error发生的时候记录的栈追踪,非常有意思。
image
当我用node去调用他的时候,没有发生任何变化。
image
浏览器的报错栈树,最外层的anonymous则是最外层我们调用js本身的main.js,这属于浏览器的栈追踪。
image

从调用自身查看stack调用

老司机应该经常看到这种报错,超过最大限量的栈调用
image

Reference:

js 数组 的一些笔记

js 数组笔记

浅复制

const arr = [1, 3, 4, 5, 6, 7, 8];
const newArr = arr;

console.log(`arr: ${arr}`);                   // arr: 1,3,4,5,6,7,8
console.log(`newArr: ${newArr}`);             // arr: 1,3,4,5,6,7,8

// 原数组改变,新数组也会跟着改变,因为这里的是浅复制,仅仅是point指向arr
arr.length = 4;

console.log(`arr: ${arr}`);                   // arr: 1,3,4,5
console.log(`newArr: ${newArr}`);             // newArr: 1,3,4,5

深复制

const arr = [1, 3, 4, 5, 6, 7, 8];
const newArr = arr.map(arr=>arr);             // map 会输出一个新的数组
// const newArr = [...arr];                   // 用es6的spread  传播的扩展数组方式形成一个新数组也是一样的
console.log(`arr: ${arr}`);                   // arr: 1,3,4,5,6,7,8
console.log(`newArr: ${newArr}`);             // newArr: 1,3,4,5,6,7,8

// 原数组改变,新数组不变
arr.length = 4;

console.log(`arr: ${arr}`);                   // arr: 1,3,4,5
console.log(`newArr: ${newArr}`);             // newArr: 1,3,4,5,6,7,8

数组shift()方法的实用性

假如数组没有 Array.prototype.shift()这种方法,我们如何[1,2,3,4,5,6] => [2,3,4,5,6]?那就会变得非常麻烦.

var arr  =[1,2,3,4,5,6];
var newArr = [];
for(var i=0;i<arr.length;i++){
    newArr[i]=arr[i+1]
}
newArr=[2,3,4,5,6,]     // arr 最后一个数组为null

数组排序, 根据id排序

const Arr = [
  { id: 1, name: 'peng' },
  { id: 0, name: 'zhou' },
  { id: 2, name: 'huang' },
];
// 运用es6的方法轻易做到
const newArr = Arr
  .map(e => e.id)
  .sort()
  .map(id => Arr.find(arr => arr.id === id));
console.log(newArr);

第二种方法

const by = (name, minor) => (o, p) => {
  const a = o[name];
  const b = p[name];

  if (a === b) {
    minor(o, p);
  }
  return a < b ? -1 : 1;
};
// 测试数据
$.ajax({
  url: 'https://chat.pipk.top/graphql',
  type: 'POST',
  dataType: 'json',
  data: {
    query: `{
      search(query:"yinxin630" , type:USER,first:1){
        edges{
          node{
            ... on User{
              repositories(first:100){
                nodes {
                  forkCount
                  createdAt
                  updatedAt
                  name
                }
              }
            }
          }
        }
      }
    }`,
  },
  success(json) {
    const Arr = json.data.search.edges[0].node.repositories.nodes;
    // 根据createAt排序,如果createAt相同,则根据forkCount排序
    const newArr = Arr.sort(by('createdAt', by('forkCount')));
    console.log(newArr);
  },
});

数组去重

const Arr = [1, 2, 3, 4, 23, 5, 5, 5, 6, 6, 4];
const newArr = new Set();
Arr.forEach(arr => newArr.add(arr));
console.log(newArr);

网络是怎样连接的

前言

这可以作为一条学习前端知识的主线

输入url

这个步骤主要是解析url,

https:443//pipk.top/article?page=1&pageSize=15

url是uri的子集

上面url包括了以下信息。

  • http协议+ssl安全加密层
  • 443端口
  • pipk.top 服务器名字
  • /article服务器内部文件路径或者前台/后台路由
  • ?page=1&pageSize=15 在url中拼接的参数,当然你也可以在http主体中写入信息,毕竟url长度是有限制的,超过某个长度返回 状态码:414 Request-URI Too Large

dns解析

上面拿到服务器名字pipk.top就要去拿服务器名字对应的ip了,其中如果浏览器中没有dns缓存,去系统hosts文件拿对应ip映射,因为在window系统中,hosts文件级别较高,如果hosts文件还没有找到,那就看你有没有手动设置DNS服务器ip,如果没有,那就去离你最近的dns服务器拿映射ip了。
image

image

全世界DNS供应商大接力。

由于dns供应商会缓存ip映射,比如baidu这种,google这种,别人拿过了,就会缓存起来,如果理你最近的DNS有对应服务器缓存,那就直接拿到。
image

如果离你最近的DNS供应商没有dns缓存,那就要解析你的服务器名字了,比如:www.pipk.top,www是三级dns供应商名字,pipk是二级dns供应商名字。www是一级dns供应商的名字,而还有一个根级dns供应商,他就是 . ,他就只有13个子dns服务器,com,cn,top之类的个人都可以用的,还有org,这种政府机关专用服务器名字。

建立管道连接

拿到ip之后,就会去和服务器建立管道连接关系,客户端通过http协议传递信息给服务器,浏览器是无法建立连接的,只能委托系统建立连接,在http协议外层套一层tcp套接层,步骤分为以下4个步骤:

  • 创建套接字(创建套接字阶段)
  • 将管道连接到服务器的套接字上(连接阶段)
  • 收发数据(通讯阶段)
  • 断开管道并删除套接字(断开连接)

什么是连接

这就只是,客户端服务端双方都有保存对方的信息,这样就叫建立了连接,

关于TCP/UCP协议层

之前一直是TCP/IP,后来ip和TCP分开2层,通过命令行netstat -ano,在linux可查看所有TCP套接层的信息
image

关于获取dns -- 关于通过ip连接服务器

这是两个不同的步骤:

  • 去dns供应商拿url对应ip的过程:浏览器通过http协议发起委托,委托UDP模块(和TCP模块同一个层次,相比TCP功能更少,速度更快,适合去拿DNS),然后UDP委托IP模块,IP模块又去委托网卡驱动程,网卡驱动进而委托网卡向路由器发送请求,路由器将请求发给最近的dns供应商,层层委托,记住浏览器是没有发送网络请求的能力的,只有网卡有这个能力。直至拿到真实的url对应的ip地址

  • 上面步骤拿到真实ip后,又重复之前步骤,只不过这次是TCP协议层,层层包裹,建立http协议进而委托TCP模块创建套接层,然后又委托ip模块,进而委托网卡驱动,然后是网卡(一个网卡对应一个ip,如果多个网卡则由多个ip,以网卡为单位而不是计算机),当然一个get请求,通常不会太长,但是也有例外,比如大文件传输,比如博客论文800字这么长的发表,这些通过一个网络包是无法完成的,通常TCP协议包括HTTP报文,包括主体信息,不能超过1500字节。这个时候知道了,网络信息并不一定是一次一个包就能发送完成的,可能需要多个包,考虑因素当然是体积<1500字节,还有时间,等太久也不会管了。但是这两个是相互矛盾的,如果为了填满缓冲区一次发送一个包,就会造成延迟,如果时间优先,那么确实减少了网络延迟,却又极大的减小了网络资源传输效率,这是相互矛盾的。因此取中间值。

关于数据拆分于数据组装

由于ajax表单提交数据较大的话,通常需要在客户端将数据拆分成1460字节以下进行串联发送,同时给其添加ack编号以便于其不同包在服务端可以组装,这就涉及到ack编号的问题了,为了加大ack编号被破解的难度,通常ack初始值>1。当然ACK值的作用远不止如此,为什么这么说呢?因为数据以包(1460字节)为单位在客户端进行切割,再传输,到了服务端在根据包上面的ACK编号,对分割的包进行组装,这是一种重要的错误补偿机制。如果其中某个包丢失了呢?那就回去要求客户端再次发送这个包就好了,每个包分别对应了相对的ACK编号,进而根据编号进行组装。当然了,如果你要传1g的文件呢?这又涉及到一个重要的问题,缓存区(用于存储之前接受的包,进而进行组装的某个内存区域)。缓冲区域的空间是有限的。可能对于大文件,需要先存储部分内容,清空缓冲区,再进行传输吧。。。。

三次握手,这个属于建立TCL套接层的范畴

  • 第一次握手:建立连接。客户端发送连接请求报文段,将SYN位置为1,Sequence Number为x;然后,客户端进入SYN_SEND状态,等待服务器的确认;
  • 第二次握手:服务器收到SYN报文段。服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,设置Acknowledgment Number为x+1(Sequence Number+1);同时,自己自己还要发送SYN请求信息,将SYN位置为1,Sequence Number为y;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN_RECV状态;
  • 第三次握手:客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为y+1,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。

完成了三次握手,客户端和服务器端就可以开始传送数据。以上就是TCP三次握手的总体介绍。
三次握手涉及到一个很重要的功能,如果我们丢失了某个包,怎么检测,其实利用ACK号,很容易查出来,并要求客户端重新发送这个包。这种错误补偿机制非常厉害。

四次分手

TCL套接层的4个步骤是,1.创建套接字(3次握手之后才能建立连接)。2.将管道套接在生成的套接字上面传输数据。3.传输数据。4.断开连接(4次分手后才能断开连接)。
4次分手(中断连接)可以由客户端或者服务端任意一方发起.。

传输层

接下来数据从路由器进入到了网络世界的传输再到下一个路由器,层层转发,其中的过程,非常复杂,有光纤传输,也有网线传输,电流是如何从高压线上传输的,网络数据同样参考这样的例子。网络通过路由器层层转发进行传输。
image

光纤和宽带传输对比

事实上光速和电场传播的速度相差无几吧(没学过物理),主要区别在于光纤损耗很小,而宽带损耗相对严重。所以光纤有优势。
而光纤是如何传递数据的,通常是数据(01010010101)转化为电信号高压/低压,电信号再转化为光信号,明/暗

防火墙

服务器不能裸奔吧,所以出现了防火墙,用于过滤掉其他软件发的数据,只留下特定程序同行,包括浏览器,app等。另外也可以像我一样租赁服务器,租腾讯云,阿里云,搬瓦工,vulrt等数据中心的服务器。用一句话来说就是:专业的事应该交给专业的人去做。防火墙的工作机制也就只能过滤不允许的程序包而已,并不能检查包的具体内容。无论加密与否。

多台服务器负载均衡

  • dns服务器轮询
    假设给http://pipk.top域名设置dns解析为3个IP地址,优点自然是成本低,简单容易设置,弊端就是dns缓存非常严重,浏览器缓存一次,dns供应商缓存一次,清掉缓存不知道猴年马月的事情了,还有是操作的连续性,比如支付步骤分为3个小页面,跳到下一步就要进行新的轮询到另一台负载均衡的服务器么?

image

  • 负载均衡器

下面是百度的多台服务器负载均衡的情况。
image

缓存

代理服务器

  • 正向代理
    正确地理解姿势,就是把他理解成防火墙就好了,你无权直接访问服务器,只能通过代理访问。

  • 反向代理
    与正向代理相反,对于客户而言, 代理服务器就像是真正地服务器,用于保护和隐藏原始资源。

  • 透明代理
    不懂。。。

路由器

电脑网卡转发数据包,就到路由器了,路由器提供的是转发功能,路由器中有一个ip协议表,通过这张表来查找下一个路由器,
image

DOM渲染机制

  1. html文件生成dom树。
    类似于BTS二叉树结构,父节点指向子节点,指针关系,先根据html构建基本数据结构,也就是dom tree的原型。

  2. javascript解析,css解析。
    不分先后,看谁写在前面,他们之间的任务队列互斥,都是单线程的,他们的工作 就是为dom.tree添加属性,css最简单,它根据他自己的selector规则,往styles里面添加属性就好了。而js比较复杂,可能会操纵dom元素,即修改dom tree属性,又或者指针关系,再有js代码里面添加事件监听,即为某个domtree,然而浏览器貌似没有开发关于事件绑定的api,所以我们也无从查看事件绑定内部代码,同时一旦你点击页面,也就会触发click,他会在新的队列里面推送一条新任务,同理ajax,以及setTimeout,setInterval

3.repaint重绘

4.reflow重排

最后

突然发现这题目的标准答案应该围绕http协议DOM渲染机制这两个的完整版来说

vue - 单文件结构

介绍

很多Vue项目中,我们使用Vue.components来定义全局组件,紧接着用new Vue({el : "#container"})在每个页面指定一个容器元素。如果你用js的方式来写会有以下缺点:

  • 全局定义,强制要求每个Component不能重复命名。
  • 字符串模板,用es6的模板字符串,缺乏语法高亮,在html多行的情况下,需要用到丑陋的\
  • 不支持css,意味着当css模块化的时候css被遗漏
  • 没有构建步骤,babel用不了。

这种情况下.vue文件孕育而生,下面例子,hello.vue例子,我们将获得完整的语法高亮,

<template>
	<h1>关于营销</h1>
</template>

<script>
module.exports = {
	data:{
		greeting: 'Hello'
	}
}
</script>

<style lang="less" scoped>
  h1 {
    padding: 0 0.5rem;
  }
</style>
  • 你会有高亮
  • CommonJS模块
  • 组件作用域的cssscoped

同时可使用预处理器,css-(scss | stylus | less), js-babel-typescript,如再搭配vue-loader使用,他能把css-modules当作一等公民使用。

怎样关注前后端分离呢

这里有一个重要事情比较值得注意。关注点分离不等于文件类型分离。在现代UI开发中,我们已经发现相比于把代码库分离成三个大的层次并相互交织,把他们划分为松散耦合的组件比较合理,然后组件在组合在一起。这使得组件更易于维护。

一个粒子时钟 动画

效果展示

前言

我就写写...

第一步 当然是新建一个JSON文件

Map.js,他就是一个类似地图的东西。

第二步 vue实例中的methods方法下面新建一个init方法

在mounted方法下面调用init这个方法。

调用方式this.init(),先说主思路,时钟作为底层canvas,它每秒更新一次,小球作为表层canvas,它50ms更新一次,事实上为什么是50ms呢,因为它比60ms要小其次50被整除是一个整数,方便计算。
嗯嗯,由于绘制时钟比较简单,我将drawClock方法中同时包括了绘制以及时刻调用自我,不断更新canvas。而更新小球这一图层,比较复杂。包括以下步骤:

  • 首先,提供一个数组给他储存一系列小球的相关数据,x,y,color,stepX轴速度,stepY轴速度,disY轴加速度。这个是小球的数据结构。这个就是updateBalls()方法要做的事情,时刻根据速度以及加速度,更新自己那个x,y坐标。
init(){
  //更新时钟
  setInterval(()=>{
    this.drawClock();
  },1000);
  //更新小球
  setInterval(()=>{
    //更新小球状态
    this.updateBalls();
    // //渲染
    this.renderBalls();
  },50);
},
  • 其次。渲染,绘制每个小球canvas
    具体方法也很简单,循环this.ball数组就好了,数组提供每一个arr就是单个小球的数据结构,具体如下代码。
this.ball.forEach(arr=>{
  this.ballCxt.beginPath();
  this.ballCxt.arc(arr.x,arr.y,this.R,0,2*Math.PI);
  this.ballCxt.fillStyle = arr.color;
  this.ballCxt.closePath();
  this.ballCxt.fill();                
})

第三步

好吧。我也好缭乱。
说说概念性质的东西,

  • this.ball储存的数组过长,会发生什么事情呢?
    答案是浏览器卡死,
    所以要做不定期清理this.ball的长度,我这种做法是通过观察this.ball的长度的规律进行的强行清理,没有做过精确计算。我觉得也没有必要。
this.cache = time.split('');
if(this.ball.length > 500) {
  this.ball.splice(0, 200)
}
  • 选择性push数组到this.ball里面
    首先,小时不是每时每刻都会动,分钟也不是,所以在push的时候,设置一个cache数组,他记录上一秒的时间,那么他们之间如何对比呢???通过split('')分裂字符串,对比你现在的某个数字,如果一致,则不会再push数组到ball里面,代码如下,简单来说就是做一个if判断而已。
if(letter !== this.cache[index]){
  map[letter].forEach((arr1,i)=>{
    arr1.forEach((arr2,j)=>{
      if(arr2 === 1){
        this.ball.push({
          x: (j*10 + index*100) + 100,
          y: i*10 + 300,
          stepX: Math.floor(Math.random() * 4 -2) + (Math.random() - 0.5)*5,
          stepY: -2*this.numArray[Math.floor(Math.random()*this.numArray.length)],
          color: this.colorArray[Math.floor(Math.random()* this.colorArray.length)],
          disY:1
        })
      }
    })
  })
}

这样下来,this.ball数组长度每次增加一般就是28个数组,不至于太长。
好吧,简单赘述了一下。
下面贴一下全部代码,

<template>
  <div class="content">
    <canvas width="1000px" height="700px" class="background"/>
    <canvas width="1000px" height="700px" class="animate"/>
  </div>
</template>

<script>
import axios from 'axios';
import store from '../../store';
import map from './map.js'

export default {
  store,
  data(){
    return {
      ball:[],
      cache: [],
      numArray: [1,2,3],
      colorArray:  ["#3BE","#09C","#A6C","#93C","#9C0","#690","#FB3","#F80","#F44","#C00"],
      R: 4,
      W: 700,
      H: 1000,
    }
  },
  created(){
    window.app = this;
  },
  mounted(e){
    this.init();
  },
  methods:{
    renderBalls(){
      this.ballCxt.clearRect( 0, 0 ,1000, 700);
      //
      this.ball.forEach(arr=>{
        this.ballCxt.beginPath();
        this.ballCxt.arc(arr.x,arr.y,this.R,0,2*Math.PI);
        this.ballCxt.fillStyle = arr.color;
        this.ballCxt.closePath();
        this.ballCxt.fill();                
      })
    },
    updateBalls(){
      // console.log(this.ball);
      const canvas = document.querySelector('canvas.animate');
      this.ballCxt = canvas.getContext('2d');
      const canvasBound = canvas.getBoundingClientRect();
      this.ball.forEach(arr=>{
        if(arr.y> 650){
          arr.stepY = -arr.stepY*0.7
          arr.y += arr.stepY - 10;
        }
        arr.stepY += arr.disY;
        arr.x += arr.stepX;
        arr.y += arr.stepY;
      })
    },

    init(){
      //更新时钟
      setInterval(()=>{
        this.drawClock();
      },1000);
      //更新小球
      setInterval(()=>{
        //更新小球状态
        this.updateBalls();
        // //渲染
        this.renderBalls();
      },50);
    },

    drawClock(){
      const canvas = document.querySelector('canvas.background');
      const ctx = canvas.getContext('2d');
      this.ctx = ctx;
      const canvasBound = canvas.getBoundingClientRect();
      const time = Intl.DateTimeFormat('en-US',{
        hour: 'numeric', minute: 'numeric', second: 'numeric',
        hour12: false
      }).format()

      this.ctx.clearRect( 0, 0 ,canvasBound.width, canvasBound.height);
      this.ctx.fillStyle = '#123';
      this.ctx.fillRect( 0, 0 ,canvasBound.width, canvasBound.height);

      this.drawTime(time);
      // ctx.fillText(time,500,350 );
      this.ctx.stroke();
    },

    drawTime(time){
      time.split('').forEach((letter,index)=>{
        // if(this.cache[index]!== letter){
          this.drawWord({
            letter,
            index
          })
        // }
      })
      this.cache = time.split('');
      if(this.ball.length > 500) {
        this.ball.splice(0, 200)
      }
    },
    drawWord({
      letter,index
    }){
      map[letter].forEach((arr1,i)=>{
        arr1.forEach((arr2,j)=>{
          if(arr2 === 1){
            this.drawBall({
              x: (j*10 + index*100) + 100,
              y: i*10 + 300
            })
          }
        })
      })
      if(letter !== this.cache[index]){
        map[letter].forEach((arr1,i)=>{
          arr1.forEach((arr2,j)=>{
            if(arr2 === 1){
              this.ball.push({
                x: (j*10 + index*100) + 100,
                y: i*10 + 300,
                stepX: Math.floor(Math.random() * 4 -2) + (Math.random() - 0.5)*5,
                stepY: -2*this.numArray[Math.floor(Math.random()*this.numArray.length)],
                color: this.colorArray[Math.floor(Math.random()* this.colorArray.length)],
                disY:1
              })
            }
          })
        })
      }
    },
    drawBall({x,y}){
      this.ctx.beginPath();
      this.ctx.arc(x,y,3,0,2*Math.PI);
      this.ctx.closePath();
      this.ctx.fillStyle="red";
      this.ctx.fill();
    },
  }
};
</script>

<style lang="scss" scoped>
canvas.background {
  position: absolute;
}
canvas.animate {
  position: absolute;
}
</style>

Reference:

Vue.js - 学习笔记

面试官: 你有系统性看vue官方教程?
我:没有。
猝。
今天面试又被问倒了(第二次),所以打算边写文章,一边一字一句的去学习vue。

vue是什么

vue是一套渐进式框架,与其他框架不同的是,vue被设计可以自底层向上逐层应用。vue的核心库只关心视图层,不仅易于上手,同样便于和其他项目或者第三方库整合。

vue表达式

你可以在{{}}内部写任何js表达式。但不支持if这种流程控制,vue提供其他技术支持来写这种条件判断流程。

<template>
  <div>
    {{foo.split('').reverse().join('')}}
  </div>
</template>
<script>
export default {
  data(){
    return {
      foo:'foo'
    }
  }
}
</script>

但是在模板内部放入太多逻辑代码,事实上违背了逻辑和空组件之间的分离的原则。模板内部不应该放入太多逻辑代码,所以vue提供了另一种鬼畜的方式 getter(因为不看官方文档是绝不可能猜出他的用法 滴。)

<template>
  <div id="example">
    <p>Original message: "{{ message }}"</p>
    <p>Computed reversed message: "{{ reversedMessage }}"</p>
    {{now}}
  </div>
</template>
<script>
export default {
  data: {
    message: 'Hello'
  },
  computed: {
    // 计算属性的 getter
    reversedMessage: function () {
      // `this` 指向 vm 实例
      return this.message.split('').reverse().join('')
    },
    now: ()=>new Date()
  }
}
</script>

为了防止业务逻辑和模板代码过多耦合,computed应运而生。
同时可以看出now的代码从不变化,这说明他是缓存的变量,

设置中间状态

<template>
    <div id="watch-example">  
          <p>    Ask a yes/no question:    <input v-model="question">  </p> 
          <p>{{ answer }}</p>
    </div>
</template>
<script>
var watchExampleVM = new Vue({
  el: '#watch-example',
  data: {
    question: '',
    answer: 'I cannot give you an answer until you ask a question!'
  },
  watch: {
    // 如果 `question` 发生改变,这个函数就会运行
    question: function (newQuestion, oldQuestion) {
      this.answer = 'Waiting for you to stop typing...'
      this.getAnswer()
    }
  },
  methods: {
    // `_.debounce` 是一个通过 Lodash 限制操作频率的函数。
    // 在这个例子中,我们希望限制访问 yesno.wtf/api 的频率
    // AJAX 请求直到用户输入完毕才会发出。想要了解更多关于
    // `_.debounce` 函数 (及其近亲 `_.throttle`) 的知识,
    // 请参考:https://lodash.com/docs#debounce
    getAnswer: _.debounce(
      function () {
        if (this.question.indexOf('?') === -1) {
          this.answer = 'Questions usually contain a question mark. ;-)'
          return
        }
        this.answer = 'Thinking...'
        var vm = this
        axios.get('https://yesno.wtf/api')
          .then(function (response) {
            vm.answer = _.capitalize(response.data.answer)
          })
          .catch(function (error) {
            vm.answer = 'Error! Could not reach the API. ' + error
          })
      },
      // 这是我们为判定用户停止输入等待的毫秒数
      500
    )
  }
})
</script>

Reference: vue.js 官网教程

canvas初识~!

关于如何运用canvas绘制画板。。

canvas比svg具备更高动画性能,svg会导致document重绘,而canvas仅仅只是清空画板,仅仅只需要重绘canvas,个人觉得这个是canvas动画比svg更流畅的一个原因吧,但是canvas更加底层。

  • 初始化

    var canvas = document.querySelector('canvas');
    var ctx = canvas.getContent('2d');
  • canvas动画

    依靠canvas的clearRect()内置方法,清除canvas画板后重新绘制,达到动画效果

    ctx.clearRect(
         0,0,
         canvas.getboundingclientrect().width,
         canvas.getboundingclientrect().height
    )
  • canvas点击检测

    同样依靠canvas 内置isPointInPath()的方法,当然,这个方法很好用,却很坑爹,因为必须绘制一个有边界的stroke,才能通过isPointInPath()的方法判断是否点击,

  • canvas 坐标转换

    如果涉及到图片旋转动画的话这个是必备的,不然我还需要通过Math.sin() 来计算坐标,这个计算量是非常困难的, ctx.save()开始后,下面是坐标转换 translate代表平移,rotate代表旋转,scale代表缩放

    ctx.save()
    ctx.translate(
         canvas_props.width*block_props.x + x,
         canvas_props.height*block_props.y + y
    ) 
    ctx.scale(scale, scale)
    ctx.rotate(rad)	
    ctx.restore()

正则匹配

零宽断言(1.零宽度正回顾后发断言

先举个例子,我想匹配<p>标签里面的内容。

const str = `
  <p>Para 1.</p>
  <img src="smiply.jpg" />
  <p>Para 2.</p>
  <div>Div.</div>
`;
str.match(/(?<=<p>)\w*/g);

在这里,(?<=exp)表示不匹配<p>本身,匹配它后面的内容。

(2.零宽度正预测先行断言

用于匹配带*.js文件的名字

const str = `
  peng.jpg
  li.jpg
  heng.jpg
`;
str.match(/\b\w+(?=.jpg\b)/g);

回溯

先从贪婪匹配和惰性匹配说起,贪婪匹配从最多开始匹配,如果匹配不到,就开始回溯,惰性匹配从最少开始匹配,如果匹配不到,则回溯,
image

使用正则去掉头尾空白

l从使用正则去掉字符串头尾空白去学习正则是一个不错的方式,因为我们有很多方式去实现它。
\s表示空格,\S表示非空格,这个对应其他的都一样\w表示字母,\W表示非字母

\s*        // 0-∞个
\s+       // 1-∞个
\s{1,2}  // 1-2个
\s{5,}    // 5-∞个
  • 浏览器方法
'   23232   '.trim()    //尽管浏览器实现它很简单。这种方法浏览器对它经过了底层优化,所以它比所有方法都快
  • 方法一。用两个正则,从前后两端替换,用两次replace
'   hi yours   '.replace(/^\s+/,'').replace(/\s+$/,'')
  • 方法二,将两个正则合并, 通过|这个油管符号,以及全局匹配
'   hi yours   '.replace(/^\s+|\s+/g,'')
  • 方法三,匹配整个字符串,将中间的提取出来,([\s\S]*?)是惰性匹配
'   hi yours   '.replace(/^\s*([\s\S]*?)\s*$/,'$1');
  • 方法四,匹配整个字符串,将中间的提取出来,([\s\S]*?)是贪婪匹配
'   hi yours   '.replace(/^\s*([\s\S]*\S)?\s*$/,'$1')
  • 方法五,
'   hi yours   '.replace(/^\s*(\S*(\s+\S+)*)?\s*$/,'$1')

深入理解ES6 - Nicholas C.Zakas

第一章 块级作用域绑定

var 会有一个作用域提升的问题

console.log(a)    // undefined
var a = '123';

等同于

var a;
console.log(a)    // undefined
a = '123';

等同于

let a;
console.log(a);
a = '123';

变量a被提升到顶部,因为这种bug,let,const等块级作用域诞生了。
let/const 的声明周期和var不一样。

块级声明

块级作用域(同时被称为词法作用域)存在于

  • 函数内部
  • 块中 {}[]
var的变量提升,原本是为了方便防止报错,结果到现在反而成了一种bug😭。

let禁止重复声明,而var不存在这种xian'z限制。

image

const

他必须经过初始化才行

const a;     // Uncaught SyntaxError: Missing initializer in const declaration;

临时死区(Temporal Dead Zone)

非常有意思

console.log(a);
let a = '123';    // reference 引用错误。

对比

console.log(typeof a);
if(true) {
  let a = 1;
}

image

image

因此,a的值如果处于ley的块级作用域,并且在打印后才赋值,那么是引用错误,但是如果放到块级作用域外面,它是默认从window下面拿值的,因此let之前的块级作用域称之为 Temporal Dead Zone.换句话说,块级作用域内。

第二章 模板字符串

众所周知,``代表es6的模板字符串,默认支持换行,但据说,他真正厉害的是模板标签

function passthru (literals, ...substitutions) {
  let result = '';
  // 根据substitutions的数量来确定循环的执行次数
  for (let i = 0; i < substitutions.length; i++) {
    console.log(result);
    result += literals[i];
    result += substitutions[i];
  }
  result += literals[literals.length - 1];
  return result; 
}
let count = 10;
let price = 0.25;
let message = passthru`${count} items cost $${(count * price).toFixed(2)}.`;
console.log(message);

第三章 函数

函数参数默认值

function func (a=1){
    console.log(a);
}
func();   // 1;

由于a默认值等于b,而b命名在a之后,

function t (a = b,b) {
  return a + b;
}
console.log(t(1,2));    // 3
console.log(t(1));    // NaN

函数参数中的默认参数的 Temporal Dead Zone

非常有趣的函数默认参数命名
上面例子,函数参数命名过程

// t(1,2)
let a = 1;
let b = 2;

// t(undefined,1);
let a = b;  // 参数为undefined时,使用默认参数。
let b = 1;  // 在临死区,b 引用不到,而且在临死区不会去默认指向window对象的属性。

一切正如第一章所说,当a引用b的时候,b在临死区,所有的绑定行为都会报错。

所有函数参数都有自己的作用域和临死区,与其他函数体的作用域各自独立,,也就是说,函数体内部参数默认值同样不能访问函数内部声明的变量。

函数的name,

同样非常有意思,函数默认有一条属性,那就是name
image
image
打印函数,输出的是函数体,但(函数).name输出的确实他的函数名,或者函数声明。这同样涉及到函数背后所做的事情。

function a(){};
=====输入======
a.name
====等同于======
var temp = new Function();
temp.name    // a
temp = null    // 销毁

image

var a = (){};
console.log(a.bind().name)   // "bound dosomething";
console.log((new Function()).name)   // "anonymous";

构造函数

function P(name){
  this.name = name;
}
var person = new P('peng');
var noPerson = P('peng');

console.log(person)   // "[Object object]";
console.log(noPerson)   // "undefined";

一般来说,new让函数内部this指向新的对象,并且返回这个新的对象。
js函数有两个不同的内部方法,[[Call]]和[[Constructor]],这两个方法很有意思,当new关键字被调用的时候,执行的是[[Construct]]函数,,他会创作一个通常被称为实例的新对象,然后执行函数体,将this绑定到实例上,如果不通过new关键字调用的话,,就会执行[[Call]],从而直接执行代码中的函数体,,而具有[[Constructor]]方法的函数,被统称为构造函数,
切记,不是所有函数都有[[Construct]]方法,因此不是所有的函数都可以通过new来调用,例如后面本章所说的箭头函数就没有[[Construct]]方法,

如何判断 是否被当作构造函数来调用,

function Person (name='peng'){
  if(this instanceof Person){
    console.log('我被当作构造函数来用');
    this.name = name;
  } else {
    throw new Error('错误,前面要有new')
  }
}
new Person();
Person();

image

看上面例子,首先this会被指向新的对象,如果新的对象被指向的新对象,他的原型中又Person,那么说明你有通过构造函数构造拥有Person原型的对象;如果this的 原型是Person,即this是Person的实例,那么继续执行,如果不是,就抛出错误。由于[[Construct]]方法会创建一个Person的实例 ,并将this绑定到新的实例上面,通常,通过call也可以实现绑定。

function Person (name='peng'){
  if(this instanceof Person){
    console.log('我被当作构造函数来用');
    this.name = name;
  } else {
    throw new Error('错误,前面要有new')
  }
}
var person = new Person();
Person.call((new Person), 'micheal');

上面这种方法同样将this绑定到Person上面。Person.call()时将变量person作为第一个参数传入,相当于在Person函数里面将this设为了person实例。对于函数本身,无法通过区分是否是通过new调用的还是通过Person.call()来调用的。

通过 鉴别new.target可以判断是否是通过new来调用的

function Person (name='peng'){
  if(new.target !== undefined){
    console.log('我被当作构造函数来用',new.target);
    this.name = name;
  } else {
    throw new Error('错误,前面要有new')
  }
}
var person = new Person();
Person.call(person, 'micheal');   // throw error
function PPerson(){
  Person.call(this,name);
}
var pperson = new PPerson();   // throw error  

image

块级作用域 中的函数

if(true){
  // es5报错,es6不报错,摸棱两可的属性。
  function a(){}
}

所有的函数都会发生提升,但是问题来了,一旦if语句不执行这部分呢??

a('sdf');  // a is not defined
if(true){
  function a(){
    console.log('a');
  }
  a('sdf');  // successful;
}
a('sdf');  // a is not defined

神奇的是,函数只在块级作用域内部发生提升

typeof a;  // a is not defined
// a('sdf');  // a is not defined
if(true){
  a('sdf');  // a
  function a(){
    console.log('a');
  }
  a('sdf');  // a
  typeof a;  // a is not defined
}
// a('sdf');  // a is not defined
typeof a;  // a is not defined

但是在非严格模式下,块级函数会被提升到外围函数顶部。

箭头函数 (重点)

在ECMAScript6中,箭头函数是最有趣的的新特性。与传统函数的不同主要有以下

  • 没有this。super。arguments。和new.target绑定,箭头函数中的this,super,arguments以及new.target这些值由外围最近一层非箭头函数决定。
  • 不能通过new关键字调用, 箭头函数没有[[Construct]]方法,所以不能被当作构造函数,如果通过new调用,会报错
  • 没有原型。由于不可以通过new调用箭头函数,因而没有构建原型的需求,所以箭头函数不存在prototpye这个属性,
  • 不可以改变this的绑定,在函数内部,this在生命周期内保持一致,
  • 不支持arguments对象,箭头函数没有arguments绑定,所以你必须通过命名参数和不定参数这两种形式来访问参数。
  • 不支持重复的命名参数,无论严格模式还是非严格模式,箭头函数都不支持重复命名参数。

以上差异产生原因。内部this指向不明确,因而出现箭头函数。

箭头函数同样拥有name属性,
箭头函数的IIFE版本(立即执行函数)需要包一层小括号,而正常函数包不包小括号都可以执行。

let person = (function(name){
  return {
    getName: function(){
      return name;
    }
  };
})('name');

console.log(person.getName());    // "Nicholas";

箭头函数没有this绑定

箭头函数内的this绑定是JavaScript中最常出现错误的因素,函数体内的this值可以根据函数调用上下文而改变。

let PageHandler = {
  id: '123456',
  init: function (){
    document.addEventListener('click',function (e){
      console.log(this);  // documet 对象
    },false)
  },
  doSomething: function (type){
    console.log(`handling ${type} for`,this.id)     // handling undefined for 123456
  }
}
PageHandler.init();
PageHandler.doSomething();

上面代码来看,对象PageHandler涉及初衷就是用来处理页面上面的交互,通过init来配置交互,然而实际上普通函数,this指向谁引用的它。this绑定的是目标对象的引用,上面代码中init被调用的引用是document,自然无法执行下去,想要通过bind绑定this的值,是不可能的,箭头函数this指向其外部非箭头函数。

通常在过去,通过bind方法可以显示绑定到对象中,修正这个问题。

let PageHandler = {
  id: '123456',
  init: function (){
    console.log(this);     // 指向对象本身
    document.addEventListener('click',function (e){
      console.log(this);  // PageHandler 对象
    }.bind(this) , false)
  },
  doSomething: function (type){
    console.log(`handling ${type} for`,this.id)     // handling undefined for 123456
  }
}
PageHandler.init();
PageHandler.doSomething();     // 

但是仔细一看,上面的做法很奇怪,为什么呢?(function(){}).bind(this)创建了一个新的函数。他的this指向当前对象。为了避免创建额外的函数,下面使用箭头函数。

let PageHandler = {
  id: '123456',
  init: function (){
    console.log(this);     // 指向对象本身
    document.addEventListener('click', (e)=>{
      console.log(this);  // PageHandler 对象
    } , false)
  },
  doSomething: function (type){
    console.log(`handling ${type} for`,this.id)     // handling undefined for 123456
  }
}
PageHandler.init();
PageHandler.doSomething();     // 

这个时候,this已经成功指向PageHandling本身了,再改一下,外部函数改成箭头函数

let PageHandler = {
  id: '123456',
  init:  ()=>{
    console.log(this);     // 指向对象本身
    document.addEventListener('click', (e)=>{
      console.log(this);  // window 对象  ,严格模式指向undefined
    } , false)
  },
  doSomething: function (type){
    console.log(`handling ${type} for`,this.id)     // handling undefined for 123456
  }
}
PageHandler.init();
PageHandler.doSomething();     // 

是不是很神奇,箭头函数永远指向其外部最近的普通的function的this。如果外部没有普通function,那么this指向window,或者undefined。箭头函数缺少基本的prototype,也就是说箭头函数没有原型链,箭头函数的原则就是即用即弃。,
箭头函数不能使用new来构造,因为他没有[[Construct]]方法,同时也是因为如此,JavaScript引擎可以进一步优化其特定行为。
同时,箭头函数不能通过call(),apply(),bind(),来改变this值。

image

箭头函数和数组

不多解释,非常强大,

箭头函数没有arguments绑定。这样,无论写在哪,都能通过this访问父函数的arguments。

尾递归优化

同样也是灰常强大的功能, 为什么这么说呢,这可以有效防止栈溢出。

function tip(){
  return newFunc();      // 尾调用
}

在es5引擎中,尾调用的栈同样清晰,创建一个新的栈帧(stack frame),将未完成的函数调用推入栈。但是尾递归的问题就是,当调用栈太多的时候会造成程序性能问题,
在es6引擎中,尾调用被优化,换句话说,es6引擎较少了调用栈的最大长度,如果超出长度,调用栈会先停止,转而去处理栈帧。如果满足以下三个条件,可以被js引擎自动优化调用栈。

(尼马,这一段阮一峰说的不清不楚的。)
  • 尾调用不访问当前栈的变量。(换句话说,该函数不是一个闭包)
  • 在函数尾部,尾调用是最后一句。
  • 尾调用的函数作为返回值。

如何优化一个函数

事实上尾递归优化发生在引擎后面,递归函数优化最明显,

'use strict';
function t (n, p = 1){
  if(n <=1){
    return 1*p;
  }else{
    let result = n + p;

    // 优化后
    return t(n - 1, result);
  }
}
t(12135)

然而在浏览器中打印,依然是zhan'yi'chu栈溢出。目前尚处于审查阶段。,日后再安利一波。
image

第四章 扩展对象的新功能

几乎每一种类型的值都是对象,随着js的发展,对象使用率越来越高,因此提升对象使用效率就变得非常重要。
es6也对对象进行了优化。通过许多方式加强对象的使用,通过简单的语法扩展,提供更多操作对象交互的方法,本章详细讲解这些改进。

对象类别

es6对对象进行了分类,以下4个类别

  • 普通类别(Ordinary) 具有JavaScript对象所有默认的内部行为。
  • 特异对象(Exotic) 具有某些默认行为不符的内部行为。
  • 标准对象(Standard) es6规范中定义的对象,例如 Array,Data等,标准对象可以是普通对象,也可以是特意对象。
  • 内建对象 脚本开始执行时候就存在与JavaScript内部的对象,所有标准对象都是内建对象。

下面,我们将用这些属于来解释es6定义的各种对象。

对象新增方法

通过Object.is(),我们可以检测两个值是否全等。

console.log(-0 === +0)  // true
Object.is(-0,+0);    //false
Object.is(NaN,NaN);    //true

除了NaN 或者-0这种情况,其他情况Object.is()和===基本一样。

####Object.assign()方法

今天终于见到了Mixin这个单词,原来这就是混合,真鸡毛坑啊,中文翻译真的好坑。

混合(Mixin)是JavaScript中实现函数混合对象组合的最流行的一种方式,

function minxin(receiver, superlier){
  Object.keys(superlier).forEach(function (key){
    receiver[key] = superlier[key];
  });
  return receiver;
}

上面函数会将两个对象混合在一起。因此es6出现了Object.assign这种方法。它可以改变第一个参数那个对象的属性并混入第二个对象的属性。

var t = {}
Object.assign(t,{
  a:1
},{
  a:2,
  b:3
},{
  c:5,
  d:7,
})
console.log(t);    // {a: 2, b: 3, c: 5, d: 7}

Object.assign可以接受任意个数的参数,并且越靠后的权重越高,同时,由于对象是指针关系,为了避免改变第一个参数,造成混乱,我们可以将第一个参数传入{},这样Object.assign()返回的新对象,将不会影响任何对象。注意,get属性不能被复制。

image

var o = {
  get foo(){
    return 17;
  }
}
var b = {
  a:2
}
Object.assign(b,o)
var d = Object.getOwnPropertyDescriptor(b, 'foo');  // undefined
console.log(d.get);

Object.getOwnPropertyDescriptor

非常有意思的属性,它可以获取对象的某个属性全部的值,
image

简化原型访问的Super引用

正如之前说的,原型对于js非常重要,es许多改进,最终是为了让他更好用,es6引入了super,使用它可以更便捷的访问对象原型。举个例子,

let person = {
  getGreeting(){
    return 'Hello!';
  }
};

let dog = {
  getGreeting(){
    return 'Woof!';
  }
}
let friend = {
  getGreeting(){
    return Object.getPrototypeOf(this).getGreeting.call(this) + ", hi!";
  }
}
Object.setPrototypeOf(friend, person);
console.log(friend.getGreeting());
console.log(Object.getPrototypeOf(friend) === person);

Object.setPrototypeOf(friend, dog);
console.log(friend.getGreeting());
console.log(Object.getPrototypeOf(friend) === dog);

super则相当于对象的原型指针,上面例子即使super = Object.getPrototypeOf(this);
当然他必须在简写方式中使用,负责会抛出语法错误。
image

第五章 解构:使数据解构访问更加方便

没啥好说的。

第六章 Symbol 和 Symbol属性

私有属性,外界无法访问。所有的原始值,除了symbol以外,都有各自的字面形式,例如布尔值类型的ture或者类型值42,可以通过Symbol来创建全局一个Symbol,

let fir = Symbol();
let person = {}
person[fir] = 'Nicholas';
console.log(person[fir])    // nicholas

第七章 set和map集合

map 和 set的区别

首先安利一波mdn用法如下:第二个参数是可选参数,this,
image

下面这个对象,forEach的this,作为第二个参数传入,输出结果 1,2

let set = new Set([1,2]);
let processor = {
  output(val) {
    console.log(val);
  },
  process(dataSet) {
    dataSet.forEach(function(val) {
      return this.output(val)
    }, this)
  }
}
processor.process(set);

下面这个例子,箭头函数从外围的process()函数读取this,其实直接把this当作变量来看待,他就是一个默认存在不需要声明就默认存在的变量,箭头函数内部没有this,所以你在箭头函数里面拿this会直接拿到外部的this。这个解释可以啊,

let set = new Set([1,2]);

let processor = {
  output(val) {
    console.log(val);
  },
  process(dataSet) {
    dataSet.forEach(val => this.output(val))
  }
}
processor.process(set);

set和真数组array之间互相转换

new set(arr); // set
[...set(arr)]   // arr

谁都知道,对象是引用类型,那么当你new Set(arr)的时候,set被添加arr数组,当arr=null的时候,引用对象呗销毁,而new Set()这个值依然保留。换句话说,这是强行引用,

weakSet 弱引用

什么是弱引用,就是原来引用的对象被删除了,那么weakSet对象会同步到删除操作,然并卵。。
image
image

Map 集合

Map类型是一种储存许多键值对的有序列表,其中键名和对应的值支持所有类型

Weak Map 集合

Weak Set 是若引用Set集合,相对的,Weak Map 是弱引用的Map集合,也是用于储存对象的弱引用,weakMap集合的键名必须是一个对象,如果使用非对象键名回报错,,如果在弱引用之外,不存在其他强引用,,引擎的辣鸡回收机制,会自动回收这个对象,同时移除weak Map集合种的键值对。
weak map是一种储存许多键值对的无序列表,列表的键名必须是非null的对象,键名对应的值应该是可以是任意类型,weak map 接口与map非常相似,,通过set方法设置,通过get方法得到。

let map = new WeakMap();
let element = document.querySelector(".element");
map.set(element,'Original');
let value = map.get(element);
console.log(value);                // "Original"

第七章小结

set 集合是一种包含多个非重复性的无序列表,值与值之间的等价性是通过Object.is()的方法来判断,,若果相同,就过滤,5和’5‘不同,同时,set不是数组的子类,所以你不能通过随机访问集合中的值,只能通过has()方法,检测指定的值是否存在于Set集合中,或者通过size属性查看数量,
weak set集合是一个特殊的set集合,只支持存放弱引用,当其对象的其他强引用被清除的时候,弱引用自然会被清除,
map是多个无序键值对组成的集合,键名支持任意数据类型,与set集合类似,map也是通过Object.is()判断是否重复,他与set的区别是可以通过迭代器循环,
weak map是弱引用,造轮子应该非常适用这种东西。

我已经深刻相信,再自学下去也搞不出个什么鬼了。还是看书吧。系统性学习真的很重要。

vue - 组件

前述

vue以单个组件为模型,构建整个vue工程项目,那么下面从一个基本的单个vue组件 TODO例子,来讲述vue组件。再结合 #35 我写的vue组件生命周期来看,这就是一个vue单个组件的全部了。
闲话不多说,单组件代码如下:

<template>
  <div>
    <input
      type="text"
      class="input"
      @keyup.enter="add"
    >
    <ul v-if="todos.length">
      <li v-for="(todo,i) in todos" :key="i">
        {{todo.text}}
        <button @click="remove" :id="todo.id">x</button>
      </li>
    </ul>
    <p v-else>
      Nothing left in the list. Add a new todo in the input above.
    </p>
  </div>
</template>

<script>
import BaseInputText from "./BaseInputText.vue";
import TodoListItem from "./TodoListItem.vue";

let nextTodoId = 1;

export default {
  components: {
    BaseInputText,
    TodoListItem
  },
  data() {
    return {
      newTodoText: "",
      todos: [
        {
          id: nextTodoId++,
          text: "Learn Vue"
        },
        {
          id: nextTodoId++,
          text: "Learn about single-file components"
        },
        {
          id: nextTodoId++,
          text: "Fall in love"
        }
      ]
    };
  },
  methods: {
    add(e) {
      this.todos.push({
        id: this.todos[this.todos.length - 1].id + 1,
        text: e.target.value
      });
      e.target.value = "";
    },
    remove(e) {
      this.todos = this.todos.filter(a => a.id != e.target.id);
    }
  }
};
</script>

上述代码便是一个完整的单个组件,todo的代码,简单描述,data里面的数据都是响应式的,换句话说你在methods里面任意定义的方法,去修该this.todos,template的代码便会响应式的做出反应,这个是非常方便的,这个方法同样适用于去修改store.state.todos,他会响应式修改全局所有组件的数据,除非你的数据存在computed里面缓存了。涉及3方面:

  • @keyup.enter="add" 为模板内的dom元素做事件绑定。
  • v-if v-else条件语句流程判断
  • methods 里面的两个方法 addremove

多个组件协作流程

下面我会将两个组件提取出来

  • TodoInput 组件
  • TodoList 组件

Todo.vue文件

<template>
  <div>
    <TodoInput
      @add="add"
      :value="newTodoText"
    />
    <ul v-if="todos.length">
      <todo-list-item 
        v-for="(todo,i) in todos" 
        :key="i"
        :todo="todo"
        @remove="remove"
     />
    </ul>
    <p v-else>
      Nothing left in the list. Add a new todo in the input above.
    </p>
  </div>
</template>
<script>
import TodoInput from "./TodoInput.vue";
import TodoListItem from "./TodoListItem.vue";
let nextTodoId = 1;
export default {
  components: {
    TodoInput,
    TodoListItem
  },
  data() {
    return {
      newTodoText: "testa ",
      todos: [
        {
          id: nextTodoId++,
          text: "Learn Vue"
        },
        {
          id: nextTodoId++,
          text: "Learn about single-file components"
        },
        {
          id: nextTodoId++,
          text: "Fall in love"
        }
      ]
    };
  },
  methods: {
    add(e) {
      this.todos.push({
        id: this.todos[this.todos.length - 1].id + 1,
        text: e
      });
    },
    remove(e) {
      this.todos = this.todos.filter(a => a.id != e);
		},
  }
};
</script>

TodoInput.vue文件

<template>
  <input
    type="text"
    :value="value"
    @keypress.enter="add"
  >
</template>

<script>
export default {
  props: {
    value: {
      type: String,
      default: ''
    }
  },
  methods:{
    add(e) {
      this.$emit('add',e.target.value)
      e.target.value = "";
    }
  }
}
</script>

TodoListItem.vue文件

<template>
  <li>
    {{todo.text}}
    <button @click="$emit('remove',todo.id)">x</button>
  </li>
</template>
<script>
export default {
  props: {
    todo:{
      type: Object,
      require: true,
    }
  }
}
</script>

image

父组件通过props给子组件传递讯息,子组件通过emit调用父组件的方法,同时传递参数,但是这种情况下 TodoInput 组件和TodoList 组件两个兄弟组件通讯就异常复杂,必须得依靠父组件这个中间人来传递讯息,因此,vuex用上把。

五点钟去天光墟

五點鐘去天光墟 

詞曲 林阿p
編 林阿p 阿科 阿賢 Nicole OuJian
唱 林阿p Nicole OuJian
專輯:《適婚的年齡》 (2014)
電影:Claude Chabrol, Les Cousins (1959)

我最近有一個夢想
係同朋友五點鐘去天光墟
我經已好耐無同朋友出去
而且去一個未去過嘅地區

但我點可能五點就起身出去?
除非我十點鐘開始入睡
但香港有無人可以十點鐘入睡?
除非喺屋企無端端飲醉

Terence、阿雪、Peter同阿Cza
你哋會陪我去嗎?
之後再去飲早茶
你哋都應該會陪我啩

但我最大嘅一個夢想
其實已一早失去
香港都正在死去
我都經已不再唏噓

這個世界叫人類堅持落去
世界叫窮人奮鬥落去
但我淨係可以繼續喺度
無聊落去

我有一隻公仔
兩年嚟朝夕相對
我有段日子唔想出街
主要都因為佢

但我最近發現
佢唔再講嘢食嘢同飲水
而我亦都開始瞓唔著
就算飲到好醉

do do do do do

我諗緊好唔好等到五點鐘
然後就去天光墟
我諗緊瞓唔著係咪因為
以前做過一啲事太衰

我起返身開返部機聽鄧麗君
聽佢唱到呢幾句:
「不知天上宮闕 今夕是何年
我欲乘風歸去‥‥‥」

J’ai un rêve, depuis récemment
C’est d’aller faire les “marchés de l’aube”* à 5h du matin avec des copains
Ca fait déjà bien longtemps que je ne suis pas sorti avec des amis
Ca serait bien d’aller dans un de ces endroits où je ne suis encore jamais allé

*marchés de l’aube : des types sans le sous qui vendent le peu qu’ils ont à même le trottoir très tôt le matin afin d’éviter les patrouilles du Food & Environmental Hygiene Department de Hong Kong.

Mais comment vais-je faire pour me réveiller à 5h ?
Sauf à m’endormir à 22h la veille
Mais qui s’endort à 22h à Hong Kong ?
Sauf à se saouler à la maison…

Terence, Ah Suet, Peter et Ah Cza
M’accompagnerez-vous ?
On ira ensuite « yam cha » *
Vous avez intérêt à m’accompagner

  • "yam cha" : littérallement, "boire le thé" ; renvoie en fait aux petits déjeuners ou goûters cantonais, autour d'un plateau de "dim sum" ou plats traditionnels.

Et j’ai un rêve, depuis récemment
A vrai dire, il s’est envolé il y a bien longtemps
Hong Kong est en train de mourir
J’ai déjà arrêté de pousser des soupirs

Ce monde force l’humanité à insister
Le monde intime aux pauvres de continuer à lutter
Et, moi, je ne peux que rester là
A vivoter sans direction

J’ai une poupée
Depuis deux ans, on se voit nuit et jour
Il y a un moment où je ne voulais plus sortir
C’était principalement à cause d’elle

Mais récemment, j’ai remarqué
Qu’elle a arrêté de parler, de manger et même de boire
Et depuis, je n’arrive plus à dormir
Peut-être que je suis déjà saoûl…

Je me demande s’il faut attendre jusqu’à 5h
Pour ensuite aller aux marchés de l’aube
Je me demande si mon insomnie n’est pas due
Aux choses que j’ai pu rater dans le passé

Je me retourne et met un CD de Teresa Teng
Je l’entends chanter ces quelques phrases
« Je ne sais pas quelle est l’année, dans le palais céleste
Je souhaite repartir, porté par le vent… »

image

大家都在忙自己的事情

Uploading image.png…

image
image

image
image

写一个jQuery

用法我就不多说了,跟jquery差不多,而且目前也就我一个人用我不用jQuery。

class Query {
  constructor(selector) {
    this.elements = document.querySelectorAll(selector);
    this.author = 'pengliheng';
    this.version = '0.0.1';
  }
  // 写方法
  css(key, val) {
    this.elements.forEach((dom) => { dom.style[key] = val; });
    return this;
  }
  // 选择第i个元素
  eq(i) {
    this.elements = [this.elements[i]];
    return this;
  }
  // find,查找///
  find(selector) {
    let newNode = [];
    this.elements.forEach((dom) => {
      newNode = [
        ...newNode,
        ...dom.querySelectorAll(selector),
      ];
    });
    this.elements = newNode;
    return this;
  }
  attr(attr, val) {
    this.elements.forEach((dom) => {
      if (attr.match(/data-/)) {
        dom.dataset[attr.match(/(?<=-)\w*/g)] = val;
      } else {
        dom[attr] = val;
      }
    });
    return this;
  }
  click(func) {
    this.elements.forEach(dom=>{
      dom.addEventListener('click',e=>func(e))
    })
    return this
  }
  each(func){
    this.elements.forEach(dom=>{
      func(dom)
    })
    return this;
  }
}
const $ = selector => new Query(selector);
// 用于写属性
$.ajax = ({
  type, url, dataType, success, error, data,
}) => {
  fetch(url, {
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
    },
    method: type,
    body: JSON.stringify(data),
    // credentials: 'include'
  })
    .then(res => res[dataType]())
    .then(suc => success(suc))
    .catch(err => error(err));
};
window.$ = $;

usage

$('li').eq(9).css('background','red').css('font-size',"25px").css('font-size',"25px")
$.ajax({
  type: "POST",
  url: `https://chat.pipk.top/graphql`,
  dataType: "json",
  data:{
    query: `{
      search(query: "${name||'pengliheng'}", type: USER, first: 1) {    
        edges {
          node {
            ... on User {
              login
            }
          }
        }
      }
    }`,
  },
  success: function (data) {
    console.log(data);
  },
  error: function (error) {
    console.log(error);
  }
});
$('li').find('#checkbox').attr('data-id','rgfhgfhgfo');
$('li').find('#checkbox').attr('id','privaty');
$('svg').css('background', 'red').click((e) => {
  $('li').each((e) => {
    console.log(e);
  });
});

JavaScript 面向对象精要 - Nicholas C.Zakas

Nicholas C.Zakas又一新著,好书不常见,认真看下去吧。
一开始以为JavaScript是一门基于继承的原型链语言,后来发现它更大的是,JavaScript是一门基于事件驱动的语言。看一本书的好坏,第一也是最浅显的是看他对于面向对象以及JavaScript底层引擎所做的一些事的描述。第二就比较深,JavaScript是一门基于事件驱动的语言,那么看书中对于事件驱动的描述。究竟是一笔带过,只描述用法,还是能够做到由浅入深,解析浏览器背后所做的一些事情。
但是从另一个角度来说,不正是因为丰富的事件交互,才构成了一个个精彩的页面?
如果你有玩过nodejs的话,你会发现,nodejs的事件循环。这个才是nodejs的真正核心技术。
好像2本书 <ES6入门基础>-阮一峰 <深入了解ES6> - Nicholas C.Zakas,真的得两本都去看看,一本讲用法,一本讲这一新属性的缘由。顺便吐槽,二者水平根本不在同一水平线上。

第一章,原始类型和引用类型

可能你想说js有七种数据类型(boolean,object,Number,Symbol,Function,null,NaN,undefined),但是我想说的不是这个啊,

  • 原始类型,看下面代码,b虽然等于a,但是b是一个新的数据,他不是指针指向a,所以a改变,b还是原来的数值。
var a = '13123';
var b = a;
a = '1231212323';
console.log(b);    // '13123'
  • 引用类型,这个就是指针了俗称**Point**,b如果等于a,a改变,b也会改变,因为它是指针指向。
{} === {}  // false
var a = {}
var b = a;
a.t = 't';
console.log(b)  // {t:"t"};

为什么字符串会拥有方法?

原因依旧很简单,可能你想说:字符串的方法都是继承过来的。但其实它本身就有方法。

'234'.match(/\d/)   // '2'

image

1.2 原始类型

原始类型包括:

类型名字 定义
boolean 布尔类型,值为ture,false
number 数字,值为任何整数或者浮点数
string 字符串,值为单引号或者双引号括出的单个字符和连续字符串(JavaScript不区分字符串类型)
null 空类型,该原始类型值只有一个:null
undefined 未定义,该原始类型只有一个值:undefined

前三种类型boolean,string,number表现行为类似,而后两个null,undefined则有一点区别,后面讨论,所有原始类型都有字面形式,字面形式是不被保存在变量中的值,如硬编码的姓名或价格,下面举例子:

// string
var name = 'NUisdc';
var selection = 'aa';

// number 
var b = 25;
var cost = 1.25;

// boolean
var found = true;

// null
var object = null;

// undefined
var flat = undefined;
var ref;   // 未定义即 undefined

JavaScript的原始类型值直接保存,而对象则是指针。

强制转换 == ,两个等号会强制转换 而 === 则不会

'25' == 25;  // true

原始类型拥有方法,但他们不是对象。

引用类型

创建对象

  • 方法一:new Object();使用new操作符和构造函数,构造函数就是通过new来创建对象的函数---任何函数都可以是构造函数。但是根据命名规范,构造函数首字母必须是大写,用来和普通函数区分。
var object = new Object();

上面代码,由于引用类型只是point,所以object并不包括对象的实例,他只是一个指针,指向内存中,实际对象所在位置,而原始值是直接保存在变量中。

13,2 对象引用的解除

JavaScript有辣鸡回收机制,因此无需担心内存分配,但最好在不使用对象时候将其引用解除,object=null;

1.4类型的实例化

以下类型都可以实例化

代码 定义
Array 数组类型,以数字为索引的一组值的有序列表
Date 日期和时间类型
Error 错误类型,还有一些更加特别的错误子类型
Function 函数类型
Object 通用对象类型
RegExp 正则类型

1.8 原始类型的封装

JavaScript共3种原始类型,String,Number,Boolean,
当你读取一个原始类型的时候,例如读取字符串,数字,布尔值的时候,原始封装类型自动创建如下:

var name = 'Peng';
var firstChar = name.charAt(0);
console.log(firstChar);                    // g

但是这仅仅只是表面现象,真实的事件发生过程如下

// what the javascript engine does
var name = 'Peng';
var temp = new String(name);
var firstChar = temp.charAt(0);
temp = null;
console.log(firstChar);

由于第二行把字符串当作对象使用,JavaScript引擎创建了一个字符串的实例,让charAt(0)可以工作,字符串对象的存在,仅仅用于该语句之后就被销毁(一种自动打包过程)。为了测试这一点,试着给字符串添加一个属性试一下/???如果是一个对象,那么他的属性一直被保留,如果是一个字符串,那么他的属性不会被保留,因为在使用过后,就会被销毁。

var name = 'peng';
name.test = 'test';
name.test   //  undefined
var obj = new Object();
obj.test = 'test';
obj.test      //  'test'

下面是JavaScript引擎在背后所做的一些工作

// what the javascript engine does
// 第一次  var name = 'peng',  并且,name.last = 'last'的时候,背后的一些事情
var name = 'Nicholas';
var temp = new String(name);
temp.last = 'last';
temp = null;

// 第二次  打印变量的时候,看到没有,用完之后,实例化String对象会被销毁。
var temp = new String(name);
console.log(temp.last)   // undefined
temp = null

同样的你也可以手动创建一个对象类型,但是副作用就是typeof 无法鉴别

var name = new String('peng')
console.log(typeof name)  // object

第二章 函数

前面说过 函数就是对象,而使函数不同于对象的地方在于[[call]]内部属性,本章讨论函数的行为和其他语言的不同,以function为例子,

2.1声明还是表达

声明function a (){}; 这个会有一个函数提升
表达var a = function(){}; 这个没有提升

因为声明的函数,JavaScript 引擎提前就知道了,

2.2 函数就是值

这个是函数式基础
var str = () => ''str"
考虑一下下面例子:

function hi(){
  console.log('hi');
}
hi();
var sayHi = hi;
sayHi();   // output 'hi'

为了便于理解,用Function构造函数写上面的例子

var hi = new Function('console.log("hi");');
hi()   // output 'hi'
var sayHi = hi;
sayHi();  // output 'hi';

构造函数更能清晰看出,函数就是对象,所以他才能这样的被传来传去。

2.3 参数

你可以给函数传递任意数量的参数,而不报错,那是因为参数实际被保存在一个被称为arguments的类似于数组里面的对象中,

2.4 重载

大多数面向对象语言支持函数重载,他能让一个函数既有多个签名,但是js只能声明一次

function js (val){
  console.log(val)
}
function js (){
  console.log('default value');
}
js('hi')    // out put 'default value'

上面代码其实可以被改写成下面代码

var js = new Function("val","console.log('val');");
js = new Function("console.log('default value');");
js('hi')    // out put 'default value'

js 函数被重新指针了一次嘛。

2.5.1 this对象

你可能注意到了函数的一些奇怪之处。sayName()方法直接引用了person.name,在方法和对象之间耦合严重,如果你要改变变量名字呢?这个时候使用this吧,this代表全局对象。

function sayName(){
    console.log(this.name);
}

var person1 = {
  name: 'peng',
  sayname: sayName
};
var person2 = {
  name: 'gray',
  sayname: sayName
};
var name = 'Micheal';
person1.sayName();   // 'peng'
person2.sayName();   // 'gray'
sayName();           // 'Micheal'

本例子定义函数sayName,然后以字面量形式创建两个对象以sayName作为sayName的方法。当person1调用sayName()时,输出'Peng',person2输出Gray,那是因为this在调用时候才被设置,所以this.name是对的。
最后,本例定义的全局变量。

改变this

在JavaScript中,使用和操作函数中的this的能力是良好地面向对象编程的关键,函数会在各种不同上下文中被使用,他必须到哪都能工作,一般this自动设置,但是你可以改变他的值来完成不同目标。有3种方法

  • 1.call方法
    第一个用于操作this的方法,就是call,它以指定this值和参数来决定执行函数,call()的第一个参数指定了函数执行this的值,其后所有的参数都是需要被传入函数的值。假设你更新sayName让他接受一个参数。
const name = 'global name';
function sayName(label) {
  console.log(this);
  console.log(`${label}: ${this}`);
}

const person1 = {
  name: 'name1',
};
const person2 = {
  name: 'name2',
};

sayName.call(this, 'global');
sayName.call(person1, 'person1');
sayName.call(person2, 'person2');

上面的例子,sayName()接受一个label的参数,输出,然后这个函数被调用了3次。注意调用函数的时候没有括号,因为它被调用作为对象访问而不是被执行的代码,第一次调用使用全局this,严格模式下全局this为undefined,之后两次分别是person1,和person2。由于调用了call()方法,你不需要将函数加入每个对象,你显样指定了每个this的值而不是让JavaScript引擎自动指定。

    1. apply()方法
      apply()是你可以用来操作this的第二个函数方法。apply()的工作方式和call一样,但是它只接受2个参数:1.this,2.一个数组或者类似数组的对象。,也就是说你可以把arguments作为第二个数组参数。你不需要像使用call()那样一个个指定参数,而是可以轻松传递整个数组给apply()。除此之外,call()和apply()完全一样,下面例子演示了apply()的使用方法。
const name = 'global name';
function sayName(label, label1) {
  console.log(this);
  console.log(label1);
  console.log(`${label}: ${this}`);
}
const person1 = {
  name: 'name1',
};
const person2 = {
  name: 'name2',
};
sayName.apply(this, ['global']);
sayName.apply(person1, ['person1', 'person1111234']);
sayName.apply(person2, ['person2']);

这段代码使用了数组替代之前的字符串。
一般来说如果你的参数是数组,那么用apply,如果你的参数是单个的一个,用call。

  • 3.bind()方法
    改变this的三个函数方法是bind()。es5中新增的这个方法和之前略有不同,bind的参数第一个是要传给this的值。其他所有参数都要被永久设置成新函数的命名参数,你可以在之后继续设置任何非永久性参数。
const name = 'global name';
function sayName(label) {
  console.log(this);
  console.log(`${label}: ${this}`);
}

const person1 = {
  name: 'name1',
};
const person2 = {
  name: 'name2',
};

// create a function just for person1
const sayNameBindP1 = sayName.bind(person1);
sayNameBindP1('person1');

// create a function just for person2
const sayNameBindP2 = sayName.bind(person2);
sayNameBindP2('person2');

// attaching a method to an object doesn't change this
person2.sayName = sayNameBindP1;
person2.sayName('person2222');

sayNameForPerson1()没有绑定参数,所以你仍然需要传入label参数用于输出。sayNameForP1()不仅绑定this为person2,同时也绑定了第一个参数为person2。这意味着你可以调用sayNameBindP2()而不传入任何额外参数。例子最后将sayNameBindP1()设置为person2的sayName方法。由于其this的值已经绑定,所以虽然sayNameBindP1现在是person2的方法,他仍然输出perosn1.name的值.

第三章:理解对象

尽管JavaScript本身就有很多对象如:Array,Boolean,Function,等对象,但是你还是会创建一些原生对象。,JavaScript中的对象是动态的,你可以在代码执行的任何时刻改变。
JavaScript编程的一大重点就是管理哪些对象,这就是为什么理解对象如何运作时这个重要,后面章节详细讨论。

3.1 定义属性

前面讲过,两种创建对象的方式。

var person1 = {
  name: 'Nicholas'
};
var person2 = new Object();
person2.name = 'Nicholas';

person1.age = '12';
person2.age = '12';

person1.name = 'Greg';
person2.name = 'Michael';

person1 和person2 都具有name属性。
那么说说底层JavaScript引擎对它的实现机制吧。当一个属性第一次被添加给对象时,JavaScript在对象上调用一个名为[[Put]]的内部方法。[[Put]]方法会在对象上面创建一个新节点来保存属性,,就像第一次在哈希表上面添加一个键值一样,这个操作不仅仅指定了初始的值,也定义了属性的一些特性。所以在方法中,当前属性name和age第一次被定义的时候都会调用[[Put]].
调用[[Put]]的结果是在对象上创建了一个自由属性,一个自有属性表明仅仅该指定的对象实例拥有该属性。该属性被直接保存在实例内,对该属性的所有操作都必须通过该对象进行。

自有属性有别于原型属性,后面慢慢讨论。

当一个已有属性被赋予新的值的时候,调用的是一个名为[[Set]]的方法,为它赋予新的值。上例为name设置第二个属性的时候调用[[Set]]方法,

3.2 属性是否存在

最常见的方法,包括我

if(a.name) console.log('exist')

问题是如果a.name = 0 的时候呢??雪崩,bug无处不在
a.name = false/0/null/undefined/Nan的时候都会雪崩
正确的方法是使用in 操作符,

if('name' in a){console.log('exist')}

但是如果属性是继承的呢,这个时候需要hasOwnProperty()的方法来鉴别,in 可以获取所有的方法,,而hasOwnProperty不能获取继承的方法。

3.3 删除属性

正如属性可以在任何时候被添加到对象上,他们也可以在任何时候被移除,但设置一个属性的值为null,并不能从对象中移除,只是调用[[Set]]将null值替换了该属性原来的值而已。这点前面说过。你只能使用delete彻底删除一个属性,
delete操作符指针对单个对象属性调用名为[[Delete]]的内部方法。你可以认为该值在hash里面移除了一个键值对,当delete成功的时候,返回true
image
删除后in操作符返回false
image

3.4 属性枚举

所有你添加的属性默认可枚举,也就是说你可以用for...in枚举他们,可枚举的属性的内部特征[[Enumerable]]都被设为true,同样Object.keys()同样可以得到所有可枚举属性,一般自定义属性可枚举,原生方法不可枚举。

const person = {
  name: 'Nicholas',
};
console.log('name' in person); // true
console.log(person.propertyIsEnumerable('name')); // true
let prop = Object.keys(person);
console.log('length' in prop);  // true
console.log(prop.propertyIsEnumerable('length'));  // false

这里,属性name是可枚举的,因为他是你定义的,而length是原生属性,不可枚举。

3.5 属性类型

属性也分为2种,

  • 数据属性,包含一个值,如{name:'peng'}
  • 访问器属性. 例如getter和setter
const person = {
  _name: 'peng',
  get name() {
    console.log('you are  get name');
    return this._name;
  },
  set name(val) {
    console.log('you r setting name to %s', val);
    this._name = val;
  },
};
person.name;
person.name = 'pppp';

getter和setter很像函数但是没有function关键字。特殊关键字set 和 get 被用在访问属性名字的前面,后面跟着小括号和函数体。getter被期望返回一个值,而setter则接受一个需要被赋值的属性,本例子使用_name来保存属性数据,

3.4通用特征

两个属性特征是所有访问器属性都有的,一个是[[Enumerable]],它决定了该属性是否可枚举,另一个[[Configurable]],他决定该属性是否可配置,同时你定义的所有属性都是可枚举可配置。
如果你想定义可改变属性特征,使用Object.defineProperty()方法,他接受3个参数,

const person = {
  name: 'peng',
  age: 15,
};

Object.defineProperty(person, 'name', {
  enumerable: false,
});
Object.defineProperty(person, 'age', {
  configurable: false,
});

console.log(person);
console.log('name' in person); //
delete person.age;
console.log(person);

image

数据属性

多了一个[[Value]]还有一个[[Writable]]。所有属性的值都会被保存在[[Value]]中,哪怕是一个函数。

第四章 构造函数 和 原型对象

构造函数和类可以给javascript对象带来类似类的功能,本章详细解释JavaScript如何使用构造函数和原型对象创建对象。

4.1构造函数就是你用new对象创建对象时候调用的函数。目前为止,你已经见过几次内建JavaScript构造函数了。例如 new Object() new Array() new Function(),好处是他们具有相同属性方法。

构造函数唯一区别就是首字母大写。

var person1 =  new Person();
var person2 =  new Person();

等价于

var person1 =  new Person;
var person2 =  new Person;

即使Person不返回任东西,person1都是一个新的Person类的实例。

person1 instanceof Person;   // true

可以看出 instance就是例的意思,instance of 即实例化
image
同样的每个类都在创建的时候就拥有一个构造函数的属性
image
image
image

其中包含了一个指针指向其构造函数的引用,哪些通过字面量形式或者Object构造函数创建出来的泛用对象,其构造函数属性指向Object;

person1.constructor === Person;
person2.constructor === Person;

虽然存在这样的指向关系,但是还是建议你使用 instanceof来监察对象类型。这是因为构造函数属性可以被覆盖,不确定。
当然,一个空的构造函数接受一个命名参数name,并将他赋值给this对象name属性,同时,构造函数还给对象添加了一个sayName()方法,,new会自动创建this对象,其类型就是构造函数的类型。

function Person(name){
    this.name = name;
    this.sayName = function(){
        console.log(this.name);
    }
}

上面例子中,构造函数本身不需要返回一个值,new 操作符会帮你返回。

我本身世间的一颗因子,如何冲击我都可以。

之后可以使用Person构造函数来创建具有初始name属性的对象了。

var person1 = new Person('Nicholas');
console.log(person1.name);      // Nicholas

你可以在构造函数中显示的调用return,如果返回的值是一个对象,他就会代替新的对象实例返回。但是如果返回的值是一个原始类型,那么他就会被忽略,新建的对象实例会被返回。

function Person(name){
     Object.defineProperty(this, 'name', {
        get: function(){
            return name;
        },
        set: function(){
            name = newName;
        },
        enumerable: true,
        configurable: true
    });
    
    this.sayName = function (){
        console.log(this.name);
    }
}

这个版本中的Person函数中,name属性是一个访问者属性,利用name参数来存取世纪的值。之所以能这样,是因为命名参数就相当于一个本地变量。
始终保持用new调用构造函数,否则构造函数内的this就会指向window,或者undefined,就会改变全局变量。

var  person1 = Person('Nicholas');
console.log('person1 instanceof');   // false;
console.log(typeof person1);     // undefined;
console.log(name);             //  "Nicholas";

image
然而如今已经不允许不用new实例化一个构造函数了。其实还是可以猜出,当你不用new的时候,this指向全局对象,。由于Person构造函数依靠new提供返回值,person1变量为undefined。没有new,Person只不过是一个没有返回函数的函数。
image
而对于this.name,则会创建一个全局变量来保持。严格模式下,this指向undefined,所以一切给undefined赋值都会报错。

这个的时候你不妨猜测一下,new到底做了什么。目前为止显而易见的是:
  • new 生成一个新的构造函数并返回。
  • 新的构造函数原型全部指向被实例化的那个对象
  • 将this内部变量全部指向构造函数本身

image

image

构造函数本身并没有冗余。每个实例化对象都有自己的sayName方法,这意味着,如果你有100个实例化对象,他们就有一百个对象做同样的事情。只是使用的数据不一样。
如果同一个对象共享一个方法更有效率,该方法使用this访问到正确的数据,这就需要原型对象。

如果你有看前面的原始类型和引用类型,就会知道,原型只不过就是一个指针。它指向原始对象的方法及属性。这样的创建其实不会过多的创建对象。节省内存。

4.2 原型对象

可以把原型看做是一个对象的基类,几乎所有的函数(除了一些内建函数)都有一个名为prototype的属性,该属性是一个原型对象用来创建新的对象实例。所有创建的对象的实例共享该原型对象。例如hasOwnProperty()方法被定义在泛用对象Object原型中,但却可以被任何对象当作自己的属性使用。

var book = {
    title: 'The Principles of Object-Oriented Javascript'
};
console.log('title' in book);   // true
console.log(book.hasOwnProperty('title'));               // true;
console.log('hasOwnProperty' in book);                   // true
console.log(book.hasOwnProperty('hasOwnProperty'));               // false
console.log(book.prototype.hasOwnProperty('hasOwnProperty'));               // true

可以看出,即使book没有hasOwnProperty()方法的定义,仍然可以通过,book.hasOwnProperty()来访问这个方法, ,因为这个方法存在prototype内部,而in可以无论原型还是自有属性都返回true。

鉴别一个原型属性
你可以这样用,一个函数去鉴别一个属性是否属于原型。

function hasPrototype(object, name){
  return name in object && !object.hasOwnPrototype(name);
}
console.log(hasPrototype(book, 'title'))    // false
console.log(hasPrototype(book, 'hasOwnProperty'))    // false

如果一个属性 in 返回 true ,hasOwnProperty() 返回false,那么这个属性就是原型属性。

4.2.1 [[Prototype]]属性

一个对象实例通过内部属性[[Prototype]]跟踪其原型对象。该属性是一个指向该实例使用原型指针的对象。当你用new创建一个新的对象是,构造函数的原型对象,事实上,原型就是proptotype指向实例化的对象,你可以用

Object.getPrototypeOf({})  ;    // 得到{}空对象的原型

大多数对象都有一个__proto__属性。该属性使得你可以直接读写[[Prototype]]属性,Firefox,safari,ndoejs都应该支持,
你也可以用isPrototypeOf方法检查某个对象是否是另一个对象的原型,该方法被包含在所有对象中

var obj = {}
console.log(Object.prototype.isPrototypeOf(obj));; // true

因为obj本身是一个泛对象。他的原型是Object.prototype,意味着本例子中isPrototype应该返回ture.
当我们读取一个对象属性,JavaScript引擎首先应该现在对象自有属性中查找,如果自有属性不包含名字,则JavaScript会搜索 [[Prototype]]中的对象。如果找到就返回,找不到则返回undefined。
同时,你不可能给一个对象的原型赋值,为什么呢,因为你的赋值会被添加到自有方法里面。同时你永远无法删除原型属性,delete仅仅只对自由属性起作用。

在构造函数中使用原型对象

原型的对象的共享机制使得他们成为一次性为所有对象定义方法的理想手段。因为一个方法对所有对象实例做相同的事情。没理由每个实例都要有自己的方法。
image

function Person (name){this.name = name};
Person.prototype.sayName = function(){console.log(this.name)};
var p = new Person('peng')
console.log(p);

在这个版本的Person构造函数中,sayName()被定义在原型对象上而不是构造函数中。创建出的对象和本章之前的例子中创建的无二,只不过sayName()现在在原型属性中,而不是自有属性。而person1和person2调用sayName()时,相对的this值被分别附上person1和person2.
也可以在原型上储存其他类型的数据,比如数组,但是原型上面的数组属于原型指针指向继承,所有实例化对象都共享,

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

Person.prototype.sayName = function(){
    console.log(this.name);
}
Person.prototype.favorites = [];

var person1 = new Person('Nicholas');
var person2 = new Person('Grey');

person1.favorites.push('234');
person2.favorites.push('34');

console.log(person1.favorites)    // ['234','34'];
console.log(person2.favorites)    // ['234','34'];

同样你可以用prototype 同时赋予多个属性

Person.prototype = {
   toString: function(){},
   sayName: function(){},
}

上面代码同样可以定义两个原型,但是要注意,person1的原型是一个对象,而不是Person了。为避免这一点,你需要手动添加constructor属性

Person.prototype = {
   constructor: Person,
   toString: function(){},
   sayName: function(){},
}

非常巧妙,这样你是否发现了new背后所做的不为人知的事情了吧,new 新建了一个构造函数并指向Person。
构造函数,原型对象和对象实例之间,最有趣的关系,就是对象实例和构造寒素没有直接关系。不过实例对象和原型对象以及原型和构造函数之间都是有关系的,

person1.prototype.constructor = Person

4.2.3 改变原型对象,

你可以改变原型对象,因为实例化对象的原型类指向原型的所有方法要继承,这一切只是指针关系。同样的道理实例化对象就是被冻结,他的原型同样可以被改变,。因为这对象和原型仅仅只是指针关系。

4.2.4 内建对象的原型扩展

给基本类型Array添加新属性,虽然方便,但是不可取,严禁这么做。

vue v-model,MVVM数据绑定模型

前言

众所周知,vue一定程度上遵循了MVVM的设计理念,根本上说就是 M(数据模型)V(视图)VM(vue实例)
为了更好的体现这一特性,v-model出现,它非常简便的同步了vue的数据模型视图区.
但其实v-model是语法糖,他的原理就是给data内部的数据添加set和get
image

image

<template>
  <div class="wrap">
    <input type="text" :value="message" @input="e=>message = e.target.value">
    {{message}}
  </div>
</template>
<script>
export default {
  data(){
    return {
      message:'23e',
    }
  },
  mounted(){
    window.app = this;
  }
}
</script>

相当于

<template>
  <div class="wrap">
    <input type="text" v-model="message">
    {{message}}
  </div>
</template>
<script>
export default {
  data() {
    return {
      message: "23e"
    };
  }
};
</script>

多个输入框的v-model

理解了v-model的基本原理后,下面是多个input.checkbox的实现,同步数据模型的v-model代码

<template>
  <div class="wrap">
    <label for="1">
      <input type="checkbox" id="1" v-model="message" value="1">
      1
    </label>
    <label for="2">
      <input type="checkbox" id="2" v-model="message" value="2">
      2
    </label>
    <label for="3">
      <input type="checkbox" id="3" v-model="message" value="3">
      3
    </label>
    {{message}}
	</div>
</template>
<script>
export default {
  data() {
    return {
      message: []
    };
  }
};
</script>

展示效果如下:
image
等同于以下代码

<template>
  <div class="wrap">
    <label for="1">
      <input type="checkbox" id="1" :checked="checkVal('1')" @change="update" value="1">1
    </label>
    <label for="2">
      <input type="checkbox" id="2" :checked="checkVal('2')" @change="update" value="2">2
    </label>
    <label for="3">
      <input type="checkbox" id="3" :checked="checkVal('3')" @change="update" value="3">3
    </label>
    {{vals}}
	</div>
</template>
<script>
export default {
  data:() => ({
    vals: ['1']
  }),
  methods:{
    checkVal(val){
      return this.vals.includes(val)
    },
    update(e) {
      const isChecked = e.target.checked
      const val = e.target.value
      if (isChecked) {
        this.vals.push(val)
      } else {
        this.vals.splice(this.vals.indexOf(val), 1)
      }
    }
  }
};
</script>

v-model 在自定义组件中的使用

// App.vue
<template>
  <div class="wrap">
    <v-input id="s1" v-model="selected"/>
    <v-input id="s2" v-model="selected"/>
    <v-input id="s3" v-model="selected"/>
    <v-input id="s4" v-model="selected"/>
    {{selected}}
	</div>
</template>
<script>
import Input from './Input.vue';
export default {
  components: {Input},
  data:()=>({
    selected: []
  })
}
</script>
// Input.vue 组件文件 
<template id='input'>
  <label :for="id">
    <input type="checkbox" :checked="checkVal()" :id='id' @input="updateValue">
    <slot />
  </label>
</template>
<script>
export default {
  name:'v-input',  
  model: {
    prop: 'selected',
    event: 'input'
  },
  props: {
    id: {
      type: String,
      required: true
      // default: '需要一个独一无二的id'+ ~~(Math.random()*1000) + 999
    },
    selected: {},
  },
  methods: {
    checkVal(){
      return this.selected.includes(this.id)
    },
   updateValue(e) {
      let newVal = [...this.selected]
      if (e.target.checked) {
        newVal.push(this.id)
      } else {
        newVal.splice(newVal.indexOf(this.id), 1)
      }
      this.$emit('input', newVal)
    }
  }
};
</script>

展示效果如图
image

Reference:

vue - 生命周期钩子

前言

单个vue组件都有自己的生命周期,包括不限于组件的创建(created),mounted挂载,更新updated,destroyed销毁,完全了解组件有助于进行vue的进一步学习。组件是vue框架的基石。

书写vue组件的生命周期钩子的时候经常有的疑问,我到底应该写简便的箭头函数,还是写function这种,由于无法指定vue实例本身,this孕育而出,默认this执行vue实例,但是问题是箭头函数和普通函数不一样,换句话说,箭头函数根本没有自己的构造器?(还是说他没有自己的this?我忘了),所以箭头函数内部this全部指向函数本身,这和vue核心**是相悖论的。

created: () => console.log(this)   // Uncaught TypeError: Cannot read property of undefined

完整生命周期示意图

这个没啥好说的
image

大致流程

  • beforeCreate : 创建之前
  • created :创建完成
  • beforeMount : 挂载之前
  • mounted :挂在之后
  • beforeUpdate :更新之前 只有在虚拟dom重新渲染时候才触发
  • updated : 更新完成 只有在虚拟dom重新渲染时候才触发
  • beforeDestory:销毁之前
  • destory:销毁之前
<template>
  <div class="nav-bar">
    {{test}}
    <input type="text" v-model="test">
  </div>
</template>
<script>
export default {
  data(){
    return {
      test: 'ttt',
    }
  },
  beforeCreate(){
    console.log('beforeCreate');
  },
  created(){
    console.log('created');
  },
  beforeMount (){
    console.log('beforeMount ');
  },
  mounted (){
    console.log('mounted ');
  },
  beforeUpdate (e){
    console.log('beforeUpdate ',e);
  },
  updated (e){
    console.log('updated ',e);
  },
  beforeDestory (){
    console.log('beforeDestory ');
  },
  destory (){
    console.log('destory ');
  },
}
</script>

只有当你键入字符的时候触beforeUpdate updated
image

函数式编程

说到函数式编程第一印象应该是函数

那么既然是函数,我们应该如何表示普通变量呢??我从下面这样一个个式子中受到启发。面向对象适合表示一个人,而函数适合处理数据。

let yourself = e => e
let compose = x => (x,y) =>x(y(z))

那么作为一个函数式编程的初学者,觉得优点如下

  • 可以将代码用函数包裹起来,达到复用的目的,
  • 函数同级化,避免出现嵌套层次过深的函数
  • 和async/await结合起来感觉很方便,

当然下面介绍一个比lodash更加适合函数式编程的函数库ramda,为什么说它比lodash更加适合呢?一个重要的概念是,lodash将要处理的数据放在了第一个位置例子,而ramda将要处理的数据放在最后一个位置,至于为什么这样更加方便,我不清楚,具体去买一本函数式编程动物书去看看,会略懂。

函数是一等公民

  let number = num => num;
  let str = str => str;
  let forty = ()=>num(40);
  console.log(43+forty())   // 83

这种声明式高阶函数和普通数字相加同样可以得出结果。

vue - 过度动画(未完待续)

前言

过渡动画是必须的,显得网页有逼格(增加用户体验)/。
主要依靠这个标签同时配合if 。。。else流程来完成这一个过成。

通过配合vue-router,为每一个路由切换都添加过度动画,非常炫,

<transition name="fade1">
  <router-view></router-view>
</transition>
.fade1-enter-active, .fade1-leave-active {
  transition: opacity 0.2s;
}
.fade1-enter , .fade1-leave-to {
  opacity: 0;
}
好吧,明天就要上班了。问题也没解决,感觉这几天学vue,还是没到精髓,下面准备解析vue的双向绑定,以及深入理解es6,这本书写的很深很棒,但是好像我欠缺基础知识,没法看懂。即便是前面看了阮一峰的es6入门基础做铺垫。

不要阻塞事件循环(或者工作池)

你应该阅读这个指引?

若果你在写一些比一个简短的命令行脚本更复杂的,阅读这个应该能帮你写高效,安全的应用。
这个文章是为节点服务器编写的,但是这个概念通用复杂node app,

TL;DR

nodejs运行js核心在事件循环(初始化和回调),并且提供一个事件池子来处理昂贵的任务,像I/O。nodejs节点很好扩展,一些更好的像apache,Nodejs便于扩展的秘密是在于它使用少量的线程来处理许多客户端。如果ndoe能处理一些线程,那么它可以将更多的系统时间和内存花费在客户端上,线程的时间开销(内存,上下文切换)。但是因为node只有少量线程,你必须构建你的线程明智的使用他们。
下面是保持节点服务器速度的一个很好的经验法则:在任何给定时间与每个客户端关联的工作“很小”时,节点速度很快。
这适用于事件循环回调和工作池中的任务。

为什么我应该避免组设事件循环和他的事件池。

node用少量线程处理许多客户端,在nodejs这里有两类线程,一个是事件线程(主轮询,主线程。事件线程等待。。)和一个池。
如果一个线程持有长时间来执行回调(事件循环)或者一个任务(worker),我们叫他‘blocked’,当一个线程在客户端阻塞时候,他不能处理请求来自其他用户。这为事件轮询和工作池提供两个动机:

  • 1.性能:如果你认识到表现的太重,这个吞吐量对于你的服务器已经沦陷。
  • 2.保密:如果这可能,输入一个线程,他可能会阻塞,恶意用户可能会提交恶意提交。使你的线程阻塞,将他们保持在他们的其他客户端,这将会是一个防御措施。

一个node快速复查

node用事件驱动成就。它有一个用于协调的事件循环和一个用于昂贵任务的工作者池。

什么样的代码在事件循环中运行。

当循环开始的时候,node 应用第一次完成一个初始化阶段,正在require模块和注册回调事件,通过执行适当的回调来回应客户端请求。这个回调同步执行,并且也许会异步注册请求,来在他完成后继续处理。这个回调为了这些异步请求将会被执行在事件循环。

事件循环也将满足其回调(网络 I/O)发出的不阻塞异步请求

总之,这个事件循环为事件执行js回调注册,并且它也是为了满足不阻塞异步请求(网络I/O)的响应.

代码运行在事件池发生了什么。

node的工作池是在libuv实现的,这个其中公开了一般任务提交API。
node用事件池来处理昂贵的任务。这个包括了I/O为每一次系统操作,但不提供一个不阻塞版本,以及特别需要CPU密集型任务的I / O。

这有node模块API,这些使用事件池

  • 1.I/O集合
    • 1.DNS:dns.loopup(),
      dns.loopupService()
    • 2.文件系统:所有的我呢见系统API除了fs.FSWatcher()和这些明确同步的使用libuv线程池。
  • 2.CPU集合
    • 1.Crypto: crypto.pbkdf2(),
      crypto.randomBytes(),
      crypto.randomFill().
    • 2.Zlib: 所有的zlib除了这些显示同步的 APIs 以外,他们都是用libuv线程池。

在许多node应用,这些APIs是一个位移资源任务,为了线程池。应用和模块使用C++,所以可以提交其他任务到任务池。
为了完整性,我们提示这些,当你调用这中一个APIS来自一个回调的事件循环的时候,

Node是如何决定哪一段代码接下来运行。

抽象来说,事件循环,和事件池主要队列。

事实上,事件循环不是真实的住队列。相反,它有一个手机文件描述,它要求操作系统去描述。

这对应用设计意味着什么

在一个单线程每一个客户端系统,像Apache,每一个挂起的客户端都被分配了自己的线程。如果一个线程处理一个一个客户端阻塞,这个操作系统将要打断它并给另一个客户端一个回合。这个操作系统因此确认,客户端,需要一个小的工作。
因为node处理许多客户端线程,如果一个线程阻塞处理一个客户请求,

不要阻塞事件循环

这个事件循环警示每个客户端连接和编排,这意味着,如果事件循环花了太多,所有当前和新的客户端不会的到回应。
你也应该确认你永不阻塞事件循环,换一句话说,每个你js调用应该快速完成。当然这也应用了你等待。
一个好的方式去确认这是一个理由,关于这个计算复杂,关于回调,如果你调用的回调无论其参数如何都需要采取一定数量的步骤,如果你调用带了一些不一样的数字,部署,取决于他的参数,那么你应该思考,关于多久参数给到你,
例子1:一个恒定的回调。

app.get('/constant-time',(req,res)=>{
  res.sendStatus(200);
});

例子2:一个O(n)回调,这个回调将会快速运行,n小运行快,大n运行慢.

app.get('/countToN', (req, res) => {
  const n = req.query.n;

  // n iterations before giving someone else a turn
  for (let i = 0; i < n; i += 1) {
    console.log(`Iter ${i}`);
  }
  res.sendStatus(200);
});

例子3:为了一个2层嵌套O(n^2)回调。这个回调将会运行的更快,对于一个小n,但为一个大的n,他将会运行的更慢,相对于前一个O(n)....ps:这看起来不太可能。

app.get('/countToN2', (req, res) => {
  const n = req.query.n;
  // n iterations before giving someone else a turn
  console.time('cost time');
  for (let i = 0; i < n; i += 1) {
    for (let j = 0; j < n; j += 1) {
      console.log(`Iter ${i}.${j}`);
    }
  }
  console.timeEnd('cost time');
  res.sendStatus(200);
});

你应该如何小心???

node用google v8引擎来运行js,这对于很多一般操作,很快!但对于如下正则和JSON除外。下面讨论:
然而,对于复杂任务你应该考虑限制输入和拒绝太长的输入。这是应为,甚至你的回调有大的复杂的,,通过限制输入,你确保回调不能超过最长最快的情况。你可以评估坏损花费的回调,并且在不在你的接受范围内。

阻塞事件循环:REDOS

一般方式来阻塞事件循环。
避免脆弱的正则。
一个正则表达式匹配一个输入字符串。我们一般考虑正则匹配作为请求一个单一输入长度,许多情况,一个单一输入只需要一次。不幸的是,在许多情况下正则匹配需要的时间呈指数上升。这会阻塞事件循环。
一个容易受到攻击的正则匹配式。

  • 1.避免嵌套量词,node正则引擎能处理一些更快(a+)*。但也容易受到攻击,
  • 2.避免OR’s,像(a|a)*,重复,这有些时候很快,
  • 3.避免使用反向引用,
  • 4.如果你在做一个简单的字符串匹配,用indexOf,或者本地当量。这会更便宜,

如果你不确认是否使用正则表达式。

一个重复操作的例子:
这是一个例子,一个容易受到攻击的正则暴露他的服务器给重复操作:

app.get('/redos-me', (req, res) => {
  const filePath = req.query.filePath;

  // REDOS
  if (filePath.match(/(\/.+)+$/)) {
    console.log('valid path');
  } else {
    console.log('invalid path');
  }
  res.sendStatus(200);
});

这个脆弱的正则在这个例子是不好的,方式去检测一个地址是否合法,在linux系统里面。它匹配字符串用/做分割。这是危险的,因为它违反规则一,这是一个双重嵌套。
如果一个客户端请求参数是/////..../\n由100个/跟随,这个事件循环将会一直循环,阻塞事件循环。这个客户端的重复操作攻击会导致其他客户端也得不到服务器响应。
因为这个原因,你应该保持怀疑态度使用复杂的正则表达式。

反-重复操作 资源

这里有许多工具来检测正则是否安全,像

  • safe-regex
  • rxxr2.然而,不会捕获所有的容易受到攻击的正则匹配。
    其他接近这个用的不同的正则引擎。你应该用node-re2模块。这个使用google的快速RE2正则表达式引擎。但要警告,这个检测不是100%兼容node正则,所以检测表达式,你用node-re2模块去处理正则,但是特别注意不是所有的正则都支持ndoe-re2.
    如果你正在尝试匹配一些明白你的,像url匹配,或者地址匹配,那么用一些正则库吧,像ip-regex,

阻塞事件循环,ndoejs核心模块。

  • Encryption
  • Compression
  • File system
  • Child process

这些api很贵,因为他们设计大量计算,这些api用于脚本很方便,但是不适用于服务器上下文。如果你执行他们。在事件循环。他们会花费更多事件,更阻塞。
在服务器,你应该不用如下同步API

阻塞事件循环:JSON DOS

JSON.parse和JSON.stringify是其他潜在的昂贵操作。如果你的服务器操纵JSON对象,尤其是来自客户端的对象,应该谨慎处理,字符串大小。

复杂计算不阻塞事件循环。

支持你想要做复杂的计算,用js来做。而不阻塞事件驯化吗。你有两个选择,区分和卸载。

区分:

你因该,区分你的计算,这样,每个运行事件循环,快速运行,以便于不阻塞其他事件,在JavaScript这很容以来保存状态,在正在运行的任务,在闭包中,如下例子:
简单的例子,支持你想要计算平均数字,从1~n。
例子1:不区分平均值,花费O(n)

for (let i = 0;  i< n;i++){
  sum += il;
  let avg = sum / n;
  console.log('avg: '+ avg);
}

卸载

如果你需要做一些复杂的不好的事。这是因为区分只用事件循环。并且你将不得益于多个核心。记住,这个事件循环应该编排客户请求。将事件循环工作迁移至工作池。

如何卸载

你有两个选项

  • 你可以使用node工作池,通过发展C++.在老版本的node,建立你的c++,使用NAN,并且在新的版本使用N-API,node
  • 你可以创建和管理你的工作池,大多数直接了当的方式是使用子进程和聚。

你不应该简单为每个客户端创建子进程.你应该接受客户端请求更快,比你能够创建和管理子进程,并且你的服务器能创建和管理儿子,并且你服务器也能成为,

卸载的下行

卸载方法的缺点是它会以通信成本的形式招致开销。只有事件循环允许来见命名空间。来自己的应用。来自工作者,你不能操作一个js对象,在事件循环中的命名空间,相反,你有一个必须序列化和反序列化。所有

不要阻塞工作池

Node 有一个工作池,如果你有用卸载范例讨论如下,你也许有一个分离的计算工作池适用相同的原则。在另一个例子,让我们假设,k是一个更小的客户,你也许能同时处理,这是个保持node单线程的。

为了避免这个,你应该尝试小变量。在一系列任务来提交工作池,当这个接近,你应该知道I/O请求的相对成本。并避免提交你期望的请求。
这两个例子应该说明任务时间可能的变化。
变体示例:长时间运行的文件系统读取
支持你的系统必须读取文件,为了处理一些客户请求。在咨询后,node的文件系统API,你选择使用fs.readFile(),为了简化。然而fs.readFile()是当前的,不区分,它提交单一的fs.read()任务跨越。如果你阅读短文件,为一些用户,并且长文件为其他的,fs.readFile()也能介绍信号变量在任务长度,来损害时间池吞吐量。
为一些坏脚本,支持一些攻击,方便你的服务器读取一些随意的文件,

nodejs事件循环,Timer和process.nextTick()

什么是node.js事件循环??

这个事件循环是一个允许nodejs表现的不阻塞的I/O操作 -- 尽管事实上JavaScript是一个单线程 -- 通过尽可能将操作系统写在到系统内核。
直至今日,大多数现代内核是多线程,他们可以在后台处理多个操作的执行。当一种一个完成,内核告诉nodejs,所以适当的回调可以将他重新添加到池队列中,并且最终执行。我们将要在这个主题的最后解释他。

event循环解释

当Node.js开始,它初始化事件循环,进程提供输入脚本,这让异步API调用,计划定时器。或者调用process.nextTick(),接着开始进程的事件循环
下面这个图形展示了一个简单的,大概的事件循环概述。

   ┌───────────────────────┐
┌─>        timers         
  └──────────┬────────────┘
  ┌──────────┴────────────┐
       I/O callbacks     
  └──────────┬────────────┘
  ┌──────────┴────────────┐
       idle, prepare     
  └──────────┬────────────┘      ┌───────────────┐
  ┌──────────┴────────────┐incoming:   
           poll          <─────┤  connections, 
  └──────────┬────────────┘         data, etc.  
  ┌──────────┴────────────┐      └───────────────┘
          check          
  └──────────┬────────────┘
  ┌──────────┴────────────┐
└──┤    close callbacks    
   └───────────────────────┘
注意,每个盒子都是事件循环的一个阶段。

每一个阶段都有一个执行回调的FIFO(first in first out),虽然每个阶段都有其特定的方式,但通常情况下,当事件循环进入给定阶段时,它将执行特定于该阶段的任何操作,然后在该阶段的队列中执行回调,直到队列耗尽或回调的最大数量已执行。当队列耗尽或达到回调限制时,事件循环将移至下一个阶段,依此类推

阶段描述

  • timers:这个阶段执行setTimeout(),setInterval().
  • I/O callbacks:执行几乎所有的回调函数,除了关闭回掉函数,timers预定的以及setImmediate();
  • idle,prepare:只在内部使用。
  • poll:检索新的I/O事件。node可能会在这里阻塞。
  • check:setImmediate()将会在这调用
  • 关闭调用:例如socket.on('close',...);

阶段详细说明

timers
一个定时器在之后可以执行提供的回调后,指定阙值。定时器将在指定的时间经过计划运行。然而,操作系统调整或者其他回调的运行可能延迟他们。
注意:从技术上说,轮询阶段控制何时执行定时器。
例如:假设你计划一个超过100ms阙值后执行超市,那么你的脚本开始异步都需一个文件需要95ms

const fs = require('fs');
function someAsyncOperaTion(callback) {
  // Assume this takes 95ms to complete
  fs.readFile('./README.md', callback);
}
const timeoutScheduled = Date.now();
setTimeout(() => {
  const delay = Date.now() - timeoutScheduled;
  console.log(`${delay}ms have passed since i was scheduled`);
}, 100);
// do someAsyncOperation which takes 95 ms to complete
someAsyncOperaTion(() => {
  const startCallback = Date.now();
  // do something that will take 10ms...
  while (Date.now() - startCallback < 10) {
    const delay = Date.now() - timeoutScheduled;
    // do nothing
    console.log(delay);
  }
});

每当事件循环进入轮询期,它有一个空的队列(fs.readFile())没有完成,所以它会等待剩余的ms数,知道达到最快的定时器阙值,当等待95ms时候通过。fs.readFile()完成读取文件并且他会调用一个花了10ms去完成,这个将会被加入队列池并执行,当这个调用完成,事件循环将会看到已经达到最快计时器的扩至,然后回到计时器阶段以执行计时器的回调,在这个例子,你将会看到总延迟(计时器开始计划的时候 - 他的回调被执行将会是105ms)。

注意:来阻止轮询期饥饿事件循环,libuv(c包,实现了nodejs事件循环和所有的异步行为的平台)也有一个困难的最大(系统依赖)再它停止轮询更多事件的时候

I/O调用

这个期间执行回调,为一些一同操作,例如TPC错误类型ECONNERFUSED,当试图连接,一些*nix系统,想要等待来报错,这将会队列的执行I/O调用时期。

poll(轮询)

这个轮询期有两个主要函数

  • 为阙值过期的定时器执行脚本。
  • 处理轮询队列中的事件

当事件循环进入轮询期并且这里没有定时器被计划,下面这两件事情要发生,

  • 如果轮询队列不为空,事件循环将要遍历其,通过他的队列被调用同步执行他们,直到队列执行完毕。或者达到硬件限制。
  • 如果轮询队列为空,这里有两件事会发生,
    • 如果降本被setImmediate计划的,这个事件循环将要结束轮询期,并继续检查执行这些预设脚本。
    • 如果脚本没有被setImmediate计划的,事件循环将要等待调用,来将他们添加到队列,并立即执行他们。

一旦轮询队列为空,事件循环将要检查计时器(时间阙值已经达到),如果一个或多个计时器被准备,这个时间循环将要被计时器回调。

check

这个阶段允许一个人再轮询阶段结束后,立即执行回调。如果轮询阶段变得空闲,被setImmediate()调用的脚本在队列中,这件循环也许会继续检查轮询而不是等待。

setImmediate()是一个真实的特殊计时器,它在事件循环单独一个阶段来运行。它使用一个libuv的API,这个被计划调用来执行,在轮询完成之后。

一般来说,作为一个被执行的代码,这个事件轮询终将会到达轮询阶段,这里它将要会等待一个增长连接,请求等...,然而,如果一个回调已经被计划setImmediate()并且这个投标阶段变得空闲,它将会结束并且继续去到检查阶段而不是等待轮询事件。

关闭调用

如果一个socket或者一个handle被关闭,(例如:sockety.destroy()),这个close事件将会发射在这个阶段,否则,它将会通过process.nextTick()发射.

setImmediate()vssetTimeout()

setImmediate和setTimeout是相似的,但是行为的不同取决于他们什么时候被调用。

  • setImmediate()是一个用于在当前轮询阶段完成后执行脚本。,所以他后面不用带时间参数,轮询阶段完成后立即执行
  • setTimeout计划脚本在经过最小阙值后运行,他的后面要带时间参数。

这个定时器被执行的顺序取决于他被调用的上下文。如果两个被调用来自于主模块内,这个定时器将会被执行顺序约束(可能会受到机器上运行的其他应用程序的影响)。
例如,如果我们运行如下脚本,他们不在I/O循环内(例如主模块),这两个定时器的执行顺序是非确定性的,他受性能的影响。(ps:翻译到这里,就非常有意思了。)

setTimeout(() => {
  console.log('timeout');
}, 0);
setImmediate(() => {
  console.log('immediate');
});

// 这个执行顺序取决于机器性能。他是随机的。

$ node timeout_vs_immediate.js
timeout
immediate
$ node timeout_vs_immediate.js
immediate
timeout

然而,如果你将这两个调用放在I/O循环内,immediate总是先执行,不管有几个定时器。

fs.readFile('./README.md', () => {
  setTimeout(() => {
    console.log('timeout');
  }, 0);
  setImmediate(() => {
    console.log('Immediate');
  });
});

process.nextTick()

理解 process.nextTick()

你也许注意过process.nextTick()没有出现在上面那个对话图中,甚至,他是异步API的一部分,这是因为``process.nextTick()在技术上不是循环事件的一部分(高级语法。。。技术地,真尼玛难翻译),相反,nextTickQueue`将在当前操作完成后处理,无论当前阶段是不是事件循环
回看上面我们画的那个对画图,任何时候你调用`process.nextTick()`在给定阶段。所有的调用传递给`process.nextTick()`·将会在事件循环被解决。这也能创造一样坏的情况,因为它允许你通过递归你的I/O事件.他和能创造一些坏的场景应为它允许你让你的I/O坏死,通过`process.nextTick()`的调用,,这阻止事件循环到达轮询阶段。

为什么这要被允许。

为什么一些像这样的被包括在nodejs里面,一部分是因为他是一个设计哲学,一个API应该异步,甚至他不需要这样,以下面代码片段为例子:

function apiCall(arg, callback) {
  if (typeof arg !== 'string') {
    return process.nextTick(
      callback,
      new TypeError('argument should be string'),
    );
  }
}

这个片段应该是一个参数检查,并且如果他是部队的,他会传递出error作为回调。最近更新的API允许将参数传递给process.nextTick(),以允许它将回调后传递的任何参数作为参数传播给回调函数,因此您不必嵌套函数。
我们正在做的是传递一个错误回去给用户,但仅仅在我们允许用户代码执行之后。通过使用process.nextTick(),我们保证apiCall()总是云心它调用在用户其他代码,并且在事件循环之前它被允许继续。为了实现它,这个js调用栈是被允许来放松,并立即执行提供的回调,这回调允许一个人递归调用process.nextTick()从而不用接受一个RangeError: Maximum call stack size exceeded from v8.

这个这里能够导致一些潜在的问题,理解下面这个片段。

let bar;
// 这里有一个异步签名,但是他是同步调用
function someAsyncApiCall(callback) {
  callback();
}
// 这个回调被调用在`someAsyncApiCall` 完成之前.
someAsyncApiCall(() => {
  // 直到someAsyncApiCall完成,bar都被有被定义任何值 
  console.log('bar', bar);  // undefined
});
bar = 1;

这个用户定义了someAsyncApiCall()来拥有一个异步调用,但是它实际上是同步才做的,当他被调用,这个回调被提供给someAsyncApiCall()在某些时期的事件循环的相同阶段,因为someAsyncApiCall(),因为someAsyncApiCall()没有真正的异步的做事。作为一个结果,这个调用触发bar,甚至它也许还没拥有变量在这个作用于,因为这个脚本还没有能够跑完。
通过替换调用在一个process.nextTick(),这个脚本仍然可以有能力完成,允许所有变量,函数等待。。。为了初始化,它还具有不允许事件循环继续的优点。在事件循环被允许继续之前,用户被告知错误可能是有用的。下面是之前那个例子用process.nextTick():

let bar;
function someAsyncApiCall(callback) {
  process.nextTick(callback);
}
someAsyncApiCall(() => {
  console.log('bar', bar); // 1
});
bar = 1;

这有另一个真实例子:

const server = net.createServer(() => {}).listen(8080);
server.on('listening', () => {});

只有当唯一的port传递过去之后,这个端口是立即绑定。所以,这个listening立即回调,这个问题是.on('listening')调用这个时候还没有被设置。
为了得到这个,这个listening事件队列在一个nextTick()来允许脚本运行完成,这个允许用户来设置一些事件

process.nextTick() vs setImmediate()

我们有两个调用,他们是相似的,名字不一样

  • process.nextTick()在同一个时期立即运行
  • setImmediate()运行在接下来的迭代,或者**事件循环

本质上,这个名字应该换,process.nextTick()运行更快,但这个是一个人造品,来传递,制造这个开关会打破大部分npm包。每天更多模块正在添加。这意味着我们等的每天,更多潜在破损在发生。当他们被拒绝的,这个名字自己将不会改变。
我们认识到开发者使用setImmediate()在所有的例子中,因为它更容易推理。

为什么使用process.nextTick()?

这有两个主要理由:

  • 允许用户处理错误,清理他们的不需要的资源,或者,在事件循环阶段再次尝试请求。
  • 有时需要在调用堆栈解除之后但事件循环继续之前允许回调运行。

一个例子是匹配用户意图。例如:

const server = net.createServer();
server.on('connection', (conn) => { });

server.listen(8080);
server.on('listening', () => { });

说这个listen()在运行在事件循环初始期,但这个监听回调被替代在setImmediate()。除非,一个主机名传递,绑定端口立即发生。 为了让事件循环继续,它必须伤害这个轮询阶段,这意味着,这里有一个非零的机会可以收到连接,他被允许在事件监听之前触发连接事件。

另一个例子是运行函数构造,继承来自EventEmitter并且它要调用一个事件在构造器内:

const EventEmitter = require('events');
const util = require('util');
function MyEmitter() {
  EventEmitter.call(this);
  this.emit('event');
}
util.inherits(MyEmitter, EventEmitter);
const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
  console.log('an evnet occurred!');
});

你不能立即发射一个事件来自构造器因为这个脚本将要没有处理这个点,这里这个用户分配一个回调给使劲按。所以,在构造器内部,你可以用process.nextTick()来设置一个回调来发射事件,在构造器被完成之后,这提供预期结果。

const EventEmitter = require('events');
const util = require('util');

function MyEmitter() {
  EventEmitter.call(this);

  // use nextTick to emit the event once a handler is assigned
  process.nextTick(() => {
    this.emit('event');
  });
}
util.inherits(MyEmitter, EventEmitter);

const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
  console.log('an event occurred!');
});

Reference:

现代前端技术解析

第一章 现代前端基础技术

前端解构的开发时期实现模式的前后经历,以及演变

  • 静态黄页
  • 服务器组装动态网页数据
  • 后端为主的MVC模式
  • 前后端分离
  • 纯前端MV*为主,中间层直出
  • 前端Virtual DOM,MV*前后端同构包括以next-react,nuxt-vue的后端渲染模式

我们在打开浏览器输入一个URL到页面展示这个过程中,浏览器和服务器都发生了什么?

  • 在接受url时候,对url进行判断,如果是HTTP就按照HTTP方式进行处理
  • 调用浏览器引擎中的对比方法,比如Webview中的loadUrl方法.
  • 通过DNS解析获取该网站地址对应IP地址,查询完成后连接同浏览器的Cookie,userAgent等信息向目标网站发出的GET请求。
  • 进行http协议会话,浏览器客户端向web服务器发送报文。
  • 进入网站后台的web服务器处理请求,如apache,Tomcat,nodejs等服务器。
  • 进入部署好的后端应用,如PHP,Java,Javascript,Python等后台程序。找到对应的请求处理逻辑,这期间可能会读取服务器缓存,或者查询数据库,
  • 服务器处理请求并返回响应报文,如果服务器上面有访问过该页面,缓存上有对应资源,会与服务器最后修改记录对比,一致就返回304,否则返回200和对应内容。
  • 浏览器开始下载对应HTML文档,200就下载,300就从缓存拿。
  • 浏览器根据下载接收到的HTML文件解析解构建立DOM文档树。并根据HTML中的标记请求下载指定的MIME文件类型,如css,html,js等,他同时设置缓存等内容。
  • 页面开始解析渲染DOM,css,根据规则解析,结合DOM文档树进行网页布局和绘制渲染,js根据DOM,api操作dom,读取浏览器缓存,执行绑定事件,整个页面展示过程完成。

通常认为浏览器一个组成部分:用户界面,网络,JavaScript引擎,渲染引擎,JavaScript执行引擎,UI后端,JavaScript解释器,和持久化数据储存。

  • 用户界面:url输入框,书签等
  • 浏览器引擎:可以在用户界面和渲染引擎之间传送指令,或者在客户端本地读取数据,是浏览器各部分之间通讯的核心。
  • 浏览器渲染引擎:可以将html解析以及css规则解析,并将内容排版到浏览器中显示有样式的界面中,也称之为排版引擎
  • 浏览器渲染引擎:通常所说的浏览器内核就是指的浏览器渲染引擎。
  • 网络功能模块:通常指的网络线程,开启网络线程,发送请求,下载资源,例如加载静态网页,拿到dom树,css后渲染解析。
  • UI后端:则用于绘制基本浏览器的控件,如button,input等元素
  • JavaScript解释器:用于浏览器解释执行js,如V8引擎。
  • 数据持久化:涉及cookie localStorage sessionStorage
    作为前端,可操作性比较少,对于线程完全不可操作,所以重点在于数据持久化渲染引擎,这两个灵活运用。

目前主流浏览器内核

  • Trident内核:Internet Explorer 。360.搜狗浏览器等。
  • Gecko内核:Firefox,seaMonkey等。
  • Presto内核:Opero<7。
  • Webkit内核:Safari,Chrome浏览器。
  • Blink内核:其实就是webkit的分支,添加新特性,例如跨进程的iframe,将DOM移入js,提高js对dom访问速度,目前移动端嵌入应用内嵌的浏览器内核开始渐渐使用Blink。

渲染引擎的工作流程,

  • 解析HTML构建DOM树,将他解析成树状解构。(可以想成BST二叉树,这个阶段有一个树状的对象。)

  • 构建渲染树,根据DOM树每个节点,顺序提取计算使用的css规则,并重新计算DOM树解构的样式图,并且生成一个带有样式表述的DOM树。(可以想成BST二叉树,这个阶段在每个节点加上样式,添加style属性,并更具css规则,添加style里面的规则。)
    image
    image

  • 渲染树布局阶段。当渲染树生成结束后,根据每个渲染树节点在页面的位置以及大小。

  • 绘制渲染树。将每个dom元素的背景以及颜色等样式信息渲染到节点上面。

这里要关注的是页面的布局阶段和渲染阶段。dom元素如果位置发生改变,就会发生重排=>重绘,如果位置不变,只是样式改变了,就只会发生重绘。重排消耗了巨大的浏览器性能,所以如果要改变位置,请添加position: absolute;.写成绝对定位,尽量减少重排的影响范围。

尼玛这个教程已经深入浅出的将渲染过程讲的很明白了,如果还有问题,建议你去了解BST二叉树结构,至于为什么要了解BTS呢,尼玛整个dom结构都是树状结构,你居然好意思连简单的2叉树都不懂?数据结构与算法这本书有讲。真的不想吐槽,之前看了网络上的劣质教程,说什么将dom树状结构和css样式文件揉成一团,真的被那些个劣质网络教程害了,我迟了半年才懂这个简单的html+css渲染过程。

另外不要再html内容里面的代码插入script,因为他常常会阻塞html解析

1.3搞笑开发流程

编辑器

  • sublime 轻, 没有debug 和断点功能
  • webStrome 集成面全,可扩展,可断点,
  • vscode 轻,支持typescript,可断点调试, 完整性若,
  • vim 骨灰级编辑器。用于linux较多

理想中编辑器应具备的能力

  • Format能力,规范代码。
  • 代码片段Snippet能力,高效开发,emmet
  • 自动检错能力,eslint,
  • debug,不过大多数浏览器有这个功能。
  • git版本控制能力
  • 自动文档工具,便于团队协作开发。以及记录,

浏览器调试工具

  • chrome
  • fiddler
    神配合。

第二章 前端与协议

协议无处不在,访问网页http协议,基于https的协议,以及web如何唤起原生app的协议,以及websock协议,与服务器交互Restful协议。协议无处不在,下面细说。

http( HyperText Transport Protocol )超文本协议。

http1.1
长连接 -- keep-alive等头域
http1.1的长连接通过keep-alive的头域信息来控制,而HTTP1.0中,如果要建立,请求信息中包含Connection: keep-alive头域信息返回,值得注意的是,长连接的请求机制并不会节省传输内容的网络开销。

缓存控制

在http1.1之前,浏览器缓存主要通过http1.0的expires头部来控制实现。我们直到expires只能通过绝对时间来刷新缓存,但是http1.1新加入的Cache-Control头域,可以支持相对时间。另外浏览器可以根据Etag和Last-Modified来判断浏览器是否从缓存中加载文件。浏览器发起请求时候,头部域字段的判断流程如下:

  • 浏览器会查下Cache-Control是否过期,如果过期如果Cache-Control和Expires同时设置,那么Cache-Control的优先级更高,他是相对过期时间,
  • 如果浏览器头部带有Etag,有就带上If-None-Match字段信号,服务器判断Last-Modified失效则返回200,有效就返回304
  • 如果Etag和Last-Modified都不存在,则直接向服务器请求内容。

下图就是Cache-Control 和 Etag和Last-Modified请求缓存的主要过程。

image

部分内容传输优化

部分内容传输优化

http报文内容大全

  • Accept 告诉服务器接受那种媒体类型比如:accept: application/json

  • Accept-Charset: 浏览器接受的内容的字符集:比如 utf-8

  • Accept-Encoding: 浏览器接受内容的编码方法:比如Accept-Encoding: gzip, deflate, br,是否支持压缩压缩方式

  • Accept-Language:浏览器接受的语言类型比如zh-CN

  • Accept-Range: Web服务器一般用于分段传输协议

  • Age: 一般当服务器用自己的缓存实体去响应的时候,可以从该头部表明实体从生产到现在经过了多长时间,比如Age:3600

  • Allow:该头部参数可以设置服务端接受哪些可用的HTTP请求方法。

  • Authorization:当客户端接受来自服务器的www-Authenticate响应的时候,后面可以用该头部来携带自己的身份证来给web服务器认证。

  • Cache-Control:用来声明服务器缓存

    • no-cache,不适用实体缓存,从服务器获取内容,
    • max-age:只接受Age值小于max-age值的内容,,
    • max-stale:可接受过去的对象,但是过期时间必须小于max-stale值。
    • min-fresh:接受生命周期大于当前Age跟min-fresh值之和的缓存对象
    • publish:可以利用Cache中内容回应任何客户。
    • Private:只能用于缓存内容,优先回应先前的内容具体用户。
    • no-cache:可以设置那些内容不被缓存
    • max-age:设置响应中包含对象的过期时间。
    • All:no-storre 不允许缓存
  • Connection:在请求头中,close告诉web服务器或者代理服务器,在完成本次请求后,保持连接,例如keep-alive例如:keep-alive:300。

  • Content-Encoding,与请求头中的accept-Encoding相对应,告诉浏览器web服务器使用哪种方法压缩,gzip,default,

  • Content-Language,告诉浏览器使用哪种语言解析。

  • Content-Length,web服务器告诉浏览器HTTP请求长度

  • Content-Range,响应的文件是哪一部分。

  • Content-type,响应的文件类型,例如:application/xml

  • Etag:对象的标志,一个对象文件如果被修改了,其Etag也被修改,

  • Expires :web服务器表明实体什么时候过期,对于过期对象,只有在web服务器验证了其有效性后,才响应客户请求。

  • Host:客户端指定自己访问的web服务器域名ip地址或者端口。

  • if-none-match,如果文件包含Etag信息,就会带上if-none-match,

  • if-modified-since:如果上次带有last-modified

  • Location:告诉服务器,新地址

  • Pramga:主要告诉服务器不缓存这个对象

  • Proxy-Authenticate:代理服务器响应浏览器,提供验证信息,

  • Range 要读取的对象

  • Referer 告诉服务器,自己从哪来的。

  • Server:服务器软件版本。

  • User-agent:浏览器代理名称

  • Transfer-Encoding:如何编码

image

HTTP2

下一个超文本传输协议,首先了解一下http2,spdy加速,他是google发起的,spdy协议要求必须使用https加密传输协议。所以之前http1.1以下的无法使用spdy加速,,因此最终http2决定以spdy为基础,进行开发。http2较之前http1.1有以下区别:

  • http2完全采用二进制传输数据,而非http1.x的默认文本格式来传输,二进制的单位一般为帧(一帧包括具有固定格式和长度的二进制数据包),多个帧就形成了网络传输数据流,所以我们理解的http2就是以流为传输数据格式的,,同时HTTP2协议是通过流式传输的,头部采用HPACK压缩传输,最大限度的节省了传输数据带宽,
  • http2采用TCP多路复用的方式降低网络请求连接建立和关闭的开销,,多个请求通过一个,相比于http1.x每次请求都会携带大量冗余信息,例如cookie,对此而言,http2就有很大优势了。

这里有必要安利一下我们的基础知识,TCP连接复用和HTTP1.1中的keep-alive连接复用的区别:TCP复用传输是发生在传输层的,而keep-alive控制的文件的连接复用是发生在应用层;keep-alive控制的文件的来连接复用是串行的,即一个文件传输完成后,下一个文件才能用这个连接,而TCP连接是帧的多路复用,这就是说,不同文件的传输帧可以在一个TCP连接中一起使用流式传输。

  • http2支持传输流的优先级和流量控制,HTTP2中每个文件传输流都有自己的传输优先级,并且可以通过服务器改变优先级,动态改变,,例如在未来的服务器,就可以优先保证css优先加载,再加载js文件。

  • 支持服务器推送。服务端能再特定条件下把资源主动推送给客户端,就像浏览器端的资源预加载,例如资源推送可以在HTML文档下载之前就让HTML的JavaScript和CSS文件现行下载,,从而大大缩短页面加载和渲染等待时间。

所以基于这些HTTP2的优势,有人提出推广HTTP2,但是目前来说,如果推广http2,那么之前很多网页针对http1.1优化规则就就无效了,而且支持http2的浏览器本身就少,但是http2终将到来,我们却不能立即享有。但是我真的很想说,这些web服务器早就想到了,例如nginx1.13.12,支持http协议在浏览器不支持http2的情况下自动降级。

2.2 web安全机制

web前端安全涵盖很多方面,比如

  • XXS(Cross Site Script, 跨站脚本攻击)
    通常由带有页面解析内容的数据未经过处理直接插入页面导致解析错误值得注意的是,XSS分类3种类型,主要区别就是攻击脚本引入位置

    • 储存型XSS
      攻击脚本常常出现在前端未经处理的提交数据提交保存在数据库,然后再从数据库读取出来后直接插入页面导致。
    • 反射型XSS
      可能是在网络上url中的url参数中注入了可解析内容的数据导致,如果直接获取URL中不合法插入页面导入
    • MXSS(也叫DOM XSS)
      渲染dom属性中,将攻击脚本插入dom属性中,被解析导致的,
  • SQL(Structured Query Language, 结构化查询语言)
    主要就是因为页面提交数据到服务端后,在服务端未进行数据验证就将数据直接坪街道SQL语句执行,主要方法措施就是在前端输入验证进行严格校验。

  • CSRF(Cross-site Request Forgery,跨站请求伪造)
    CSRF是指的非源站按照源站点的数据请求格式提交非法数据给源站服务器的一种攻击方式,非源站点在拿到用户登录验证信息的情况下,可以直接对源站点的某个数据接口进行提交。如果源站点对该提交请求的数据来源未经验证,该请求可能被成功执行,这其实不合理。通常比较安全的是通过token(令牌)提交验证的方式来验证请求是否为源站点页面提交的,来阻止跨站伪请求的发生。为了避免跨站请求伪造,通常我们进行token验证,其中一种形式就是将页面到后台验证Token与session临时保存的Token进行比较就可以实现了。

请求劫持与HTTPS

现在除了正常的前后端脚本安全问题,网络请求劫持的发生也越来越频繁。网络劫持一般指网站资源请求过程中因为人为的攻击导致没有加载到预期的资源内容,网络劫持分为2种:DNS劫持和HTTP劫持。

  • DNS劫持
    简单说就是修改了DNS服务器的关于URL与ip对应关系。
  • HTTP劫持
    在用户浏览器与访问的目的服务器之间建立的网络数据传输通道中,从网关或者防火墙,这个很诡异,修改了网关层或者防火墙层,监听特定数据,当满足条件时候,在正常数据包中插入或者修改为攻击者设计的网络数据包,目的是让用户浏览解析错误的数据,或者以弹出新框的形式,添加广告,这种预防方法就是使用https协议,来访问目标网站。

HTTPS协议通信过程

HTTPS协议就是通过加入SSL(Secure Sockets Layer)层来加密HTTP数据进行安全传输的HTTP协议,同时启动默认的443端口进行数据传输,那么使用HTTPS是怎样保证浏览器和服务器之间的数据安全呢?先说2个概念:公匙和私匙
他们之间是通过一种加密算法得到的密钥对,即一个公钥配一个密钥,公钥是公开部分,用来会话加密,验证数字签名或者加密可以用相应私钥解密的数据,通过这种算法得到的密钥对保证是唯一的,使用这个密钥的时候,如果其中一个密钥加密一段数据,则必须用另一个密钥解密,
例如三次握手,都是携带密钥进行访问的,下面是node的例子:

const http = require('http');
const https = require('https');
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx) => {
  ctx.body = '23';
});
http.createServer(app.callback()).listen(3000);
https.createServer(app.callback()).listen(3001);

RESTful 数据协议规范

path/v2/addBook

第三章,前端3层结构与应用

目前来说,html已经发展到HTML5了,但是在实际开发过程中,html5只有移动端才会用到,虽然出现很多组件开发模式,但是3层结构仍然是基础。

html结构

DOCTYPE,就是他的首部标签,

标签语义化

给予搜索引擎更好的理解,最主要的是杜绝全部由div标签嵌套,

<div class="ui-menu-list">
  <div class="menu-list-item"></div>
  <div class="menu-list-item"></div>
  <div class="menu-list-item"></div>
  <div class="menu-list-item"></div>
  <div class="menu-list-item"></div>
</div>

<ul class="ui-menu-list">
  <li class="menu-list-item"></li>
  <li class="menu-list-item"></li>
  <li class="menu-list-item"></li>
  <li class="menu-list-item"></li>
  <li class="menu-list-item"></li>
</ul>

推荐使用header+article+footer的套路

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <header>

  </header>
  
  <article>

  </article>

  <footer>
    
  </footer>
</body>
</html>

即使在没有html的情况下,仍然具备良好的阅读性,例如nav一般存放导航栏结构,article一般存放文章,

  • 行内元素:<a/>, <b/>, <span/>, <img/>,<input/>,<button>,<select>,<strong/>
  • 块级元素:<div/>,<ul/>,<li/>,<dl/>,h1~h6,<p><table>
  • 空元素:<br/>,<hr>,<link><area><base>,<col><command><embed><keygen>

但是这考虑到兼容性问题.

css样式统一化

  • reset 这种写法的缺点就是,你所有样式都得自己写
* {
  margin: 0;
  padding: 0;
}
  • normalize,相对于reset存在的问题,遵循某个规范规则,相对于reset而言,normalize,在某些公司前端样式设计规范确定的情况下,可以少写很多样式。
* {
  margin: 5px;
  padding: 5px;
}
  • neat,总的来说就是综合上面两个的优点缺点。
    就不多说了。

css预处理工具

SASS预处理主要包括模块引用,嵌套,父级选择器,嵌套属性,注释变量,数据类型,运算,圆括号,函数,攥写,变量默认值,规则指令,控制指令,mixin,继承extend,

表现层动画实现

常见动画方案,也就6种

  • JavaScript直接实现动画,
    容易造成页面重排重绘,,性能差,应该尽量避免使用

  • svg动画,
    比较复杂,自己去看mdn

  • CSS3transition动画
    css3新属性

  • CSS3animation
    然而移动端很卡,所以通常添加 transform: translate3D(0,0,0)或者transform: translateZ(0);来开启移动端动画cpu加速,让动画更加流畅。通常就是@Keyframes,想想,css动画脱离js,同时可以让浏览器开启cpu加速,实现复杂动画效果,

  • Canvas动画
    他也可以实现复杂动画。完全通过JavaScript来控制。

  • requestAnimationFrame
    这个不知道

总的来说,pc端,建议直接js动画,或者svg动画实现,移动端建议canvas,css3transition,css3 animation,canvas。

响应式网站

  • 方案一:
    可以在nodejs端判断访问pc还是移动,动态发布资源。

  • 方案二:
    例如bootstrap的媒介查询,但是这种方案有以下缺点

    • 加载了过多资源。
    • 想要做更多差异性功能就比较难了。
    • 兼容性问题难以处理。

解决以下问题就好:

  • 是否使用同一个站点域名避免跳转的问题,
  • 是否能保证移动端加载的资源内容最优。
  • 如何做移动端加载资源的最优。
  • 如何根据更多的信息进行更加灵活的判断,而不仅仅只是userAgent

如何在判断userAgent

let isMobile = navigator.userAgent.match(/Phone|iPod|Android|iPad/i)

常见优化图片资源,优化,可以选择背景图片的时候,选择不同图分辨率的资源,但是这个不利于seo(Search Engine Optimization )优化,也不能有alt,title等属性,也不能在图片加载失败的时候,显示错误图片,
以 border: 1px; 为例子,在高清屏幕下,会被渲染成2px,可以使用transform: scaleY(1/devicePixelRatio)来实现单方向缩放实现1个像素边框,对于字体,使用transform: scale(.5);来支持小文字。如果页面因为高清屏幕导致显示模糊,那么用 -webkit-font-smoothing: antialiased来修复。

JavaScript语言精粹阅读笔记

This 的4种调用模式

  • 方法调用模式
  • 函数调用模式
  • 构造器调用模式
  • apply调用模式

方法调用模式

var myObject = {
  value: 0,
  increase: function(inc) {          // 此处不能用 => 来声明函数
    console.log(this);               // myObject本身
    this.value += typeof inc==='number'?inc:1;
  }
}
console.log(myObject.value);    // 0
myObject.increase();
console.log(myObject.value);    // 1
myObject.increase(33);
console.log(myObject.value);    // 34

这种通过this调用上下文对象的方法称为公共方法。this和上下文之间的绑定发生在它调用的时候。这种超级延迟的绑定使得this可以高度复用。

函数调用

当一个函数并非一个对象的属性的时候,那么他就被当作一个函数来调用,此时

'use strict';
var myObject = {
  value: 0,
  increment: function (inc){
    this.value+= typeof inc==="number"?inc:1;
  }
}
let add = (a,b) => a+b;
myObject.double = function (){
  var that = this;                           //   此处this指向函数本身
  var helper = function (){
    console.log('myObject.double', thar);    //  此处this在非严格模式☞全局,严格模式undefined
    that.value = add(that.value, that.value)
  }
  helper()
}
myObject.double();
console.log(myObject);

构造器调用模式

js语言是一门基于原型继承的语言,可以从其他对象继承属性,该语言是无类型的。这和当前编程语言的主流风格不符合,但是如果你在函数调用之前加上new的话,那么背地里将会创造一个连接到函数prototype成员的新对象,同时this会绑定到那个新对象上面。同时new前缀也会改变return语句的行为。

'use strict';
var Quo = function (str){
  this.status = str;
}
Quo.prototype.get_status = function (){
  return this.status;
}
var myQuo = new Quo('confused');
console.log(myQuo.get_status());   // confused

一个函数创造的目的就是为了结合new前缀来调用,那么我们称之为构造函数,但是JavaScript语言精粹并不推荐这种构造方法。

Apply调用模式

因为js是一门函数式面向对象编程语言,所以函数拥有方法。
apply方法让我们构建一个参数数组传递给调用函数,他也允许我们选择this的值,apply接受2个参数,第一个是要绑定this的值,第二个就是一个参数数组。

doms = document.querySelectorAll('div');
Array.prototype.slice.apply(doms).map(e=> e.id);  // doms是没有map的方法,经过apply后拥有了map方法。

今天之前一直有个疑问,为什么大佬们这么喜欢用try--catch

var try_it = function (args){
  try {
    console.log(whatever);    //  ReferenceError  sdsd is not defined
  } catch (e) {
    console.log(e.name+" ",e.message);
  }
}
try_it();
console.log(console);

并不会因为报错就不执行之后的代码,这体验还是不错的。

用js写一个fade的褪色动画

为什么要这样呢,之前一直是用css的来写这种收缩动画,直到后来,css越来越大(压缩后200kb),我抑郁了。yes i promise you can use let instead of var in js everywhere.

const fade = (dom) => {
  let level = 1;
  const step = () => {
    dom.style.backgroundColor = `rgb(255,255,${level})`;
    if (level < 255) {
      level += 11;
      setTimeout(step, 100);
    } else {
      level = 1;
      setTimeout(step, 100);
    }
  };
  step();
};
fade(document.body);

the differnet between function and arrow function in javascript

  • 你没看错,箭头函数不能用arguments获得参数
'use strict';
const a = function () {
  console.log(arguments);
};
const b = () => {
  console.log(arguments);
};
a(1, 2, 3);
b(1, 2, 3);

image

  • 箭头函数不能使用new
  • 箭头函数 this 指向函数本身,而普通函数严格模式指向undefined,非严格模式指向global

模块模式

var serial_maker = function(){
  // 返回一个用来产生唯一字符串的对象
  // 位移字符串由:前缀+序列号
  // 这包括一个设置前缀的方法,一个设置序列号的方法
  // 和一个产生位移字符串的gensym的方法
  var prefix = ' ';
  var seq = 0;
  return {
    set_prifex: function(p) {
      prefix = String(p);
    },
    set_seq: function (s){
      seq = s;
    },
    gensym: function(){
      var result = prefix+seq;
      seq += 1;
      return result;
    }
  }
}
var seqer = serial_maker();
seqer.set_prifex("Q");
seqer.set_seq(1111)
var unique = seqer.gensym();
console.log(unique);  // Q1000

联级(jQuery)

返回this而不是undefined,这样就可以实现jquery的链式调用。这和函数式编程又有所不同。另一个返回的是data数组。用es6的class手写一个jQuery,实现链式调用,

class TQuery {
  constructor(tArg) {
    this.version = 0.1;
    this.author = 'pengliheng';
    this.DOM = [...document.querySelectorAll(tArg)]
  }
  css (attr, value) {
    this.DOM.forEach(dom=> dom.style[attr] = value )
    return this;
  }
}
window.$ = tArg => new TQuery(tArg);

实现一个最简单版本的jQuery链式调用,同时改变背景颜色,字体颜色,字体大小。

$('body').css('background','pink').css('color','red').css('font-size','50px');

链式调用的效率,由于jQuery返回的是整个this.所以他是低效的。

//耗时:104ms
for (let i = 0; i < 10000; i++) {
  $('div');
}
//耗时:4.7ms
for (let i = 0; i < 10000; i++) {
  document.querySelector('div');
}

原型与继承 (事实上,用浏览器打印出来就能对原型链继承看得一清二楚!)

const myMammal = {
  name: 'Herb the Mammal',
  getName() {
    return this.name;
  },
  says() {
    return this.saying || '';
  },
};
const myCat = Object.create(myMammal);
myCat.name = 'mimi';
myCat.saying = 'meow';
myCat.purr = (n) => {
  let i;
  let s = '';
  for (i = 0; i < n; i += 1) {
    if (s) {
      s += '-';
    }
    s += 'r';
  }
  return s;
};
myCat.getName = () => `${this.says} ${this.name} ${this.says}`;
console.log(myCat, myMammal);

image

函数化,

到目前为止,我们所看到的继承模式一个弱点就是没有隐私,因为所有属性都会被继承,即可见

识别变量的类型

首先想到的自然是typeof,但他的que缺陷也是显而易见的
image
细想一下,为何不用constructor,,看图,没错,识别了[]和{},但是null和NaN,undefined报错,
image
改良一下的最终解决方案式,唯一美中不足的是,不能识别undefined,请再配合isNaN一起使用,用来识别number和NaN的区别
image
顺便一提,callapply的效果是一样的

Object.prototype.toString.call(123)      // [object Number]
Object.prototype.toString.apply(123)     // [object Number]

JavaScript - 精华

  • 函数是顶级对象:函数是有词法作用域的闭包。
  • 基于原型继承的动态对象
    • 对象是无类别的。我们可以通过普通的赋值给任何一个对象新增一个成员的属性,同时一个对象可以通过从原型继承成员属性。
  • 对象字面量和数组字面量:创建一个新对象和数组是非常方便的表示法。

JavaScript - 糟粕

  • ==!=, 为了不出bug,==应该禁止使用
'' == '0'            // false
0 == ''              // true
0 == '0'            // true
  • with, JavaScript提供了一个with语句,本意是快捷访问对象的属性,不过结果不可预料,应该禁止使用。
    下面的语句
with (object) {
    a == b;
}

with 在这门语言的出现,本身影响了JavaScript处理器的速度,他阻断了变量名词法作用域的绑定,他的本意是好的。

  • eval
    你可能会这样用
eval("myValue = myObject."+a+";")
  • continue 语句
    continue 语句跳到语句的顶部,我发现一段代码通过重构后移除了continue语句之后,性能得到了改善。
  • ++,--语句,这个也禁止使用,建议用i+=1替换。
  • 位运算符
&       and 按位与
|       or   按位或
^       xor  按位异或
~       not  按位非
>>      带符号的右位移
>>>     无符号的(用0补足)右位移
<<      左位移  

通常用于执行硬件处理,但JavaScript不处理硬件,所以位运算符很慢

  • funtion语句对比function表达式
    function 语句会发生被提升,
    而var生命函数则不会。就我个人而言是建议使用var生命一个函数的。
function test(){}
var test = function(){}

一个语句不能以function开头,因为官方表示假定单词以function开头被认为是一个funciton语句。可以用如下做法避免他。

(function (){
  var hidden_val;
  // 这个函数对环境有一些影响,但不会引入新的全局变量
})
  • void 应该避免使用。

全书完。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.