GithubHelp home page GithubHelp logo

blog's Introduction

blog

this is my blog

所有博文均以 GitHub issue 的形式发布,点此阅读:

点我点我

博文标签

其他说明

该博客用来记录平常开发中遇到的小问题小知识点(即零碎知识)

关于前端其他知识系统的文章记录在以下地方

你可以在以下地方找到我

100 天前端进阶计划

每天一个知识点的原理解析+一道算法题或者一篇一类问题(算法)的总结。

总结篇

目录篇


blog's People

Contributors

funnycoderstar avatar gqbre 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  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  avatar  avatar

blog's Issues

富文本编辑器

原生编辑器

浏览器提供了两个原生属性

  • contenteditable
    contenteditable特性, 指定某一容器变成可编辑区域, 即用户可以在容器内直接输入内容, 或者删减处理
  • document.execCommand()
    对于选中的某一结构体, 执行一个命令, 譬如赋予黑体格式
  1. 浏览器兼容问题
  2. 只接受有限的command()

存在一下的问题:

  • 兼容性问题
  • 能力局限

命令行参数

node中可以通过process.argv来获取参数

  1. 新建一个shell.js
console.log(process.argv);

在终端输入 node shell.js

[ '/Users/wangyaxing/.nvm/versions/node/v10.7.0/bin/node',
  '/Users/wangyaxing/temp/vue-project/shell.js' ]

在终端输入 node shell.js abc

[ '/Users/wangyaxing/.nvm/versions/node/v10.7.0/bin/node',
  '/Users/wangyaxing/temp/vue-project/shell.js',
  'abc' ]

process.argv的用法是第一个是node文件, 第二个是脚本文件, 第三个是参数

npm scripts(npm脚本)发送命令行参数

什么是npm scripts

npm 允许在package.json文件里面,使用scripts字段定义脚本命令。

{
  // ...
  "scripts": {
    "build": "node build.js"
  }
}

上面代码是package.json文件的一个片段,里面的scripts字段是一个对象。它的每一个属性,对应一段脚本。比如,build命令对应的脚本是node build.js。

原理

执行npm run ,会自动创建一个shell, 在这个shell里面执行指定的脚本命令。
比较特别的是, 这个shell 会自动将当前目录下的node_modules/.bin子目录加入PATH,执行结束, 再将PATH变量恢复原样

通配符

由于 npm 脚本就是 Shell 脚本,因为可以使用 Shell 通配符。

"lint": "jshint *.js"
"lint": "jshint **/*.js"

上面代码中,*表示任意文件名,**表示任意一层子目录。
如果要将通配符传入原始命令,防止被 Shell 转义,要将星号转义。

"test": "tap test/\*.js"

传参

将命令行参数发送到npm脚本:

npm run [command] [-- <args>]

注意必要的--,需要将参数传递到npm命令本身,并将其传递给脚本。

使用webpack.DefinePlugin在打包时对文件中的变量进行替换

plugins: [
    new webpack.DefinePlugin({
      'domain':  process.argv[2]
    }),
}

参考

npm scripts 使用指南
如何向npm脚本发送命令行参数?
编译环境中的几种传参方法

关于this的指向问题

关于默认this

  • 浏览器默认的this为window
function test() {
    console.log(this);
}
test(); //window
  • node.js中

    • 全局环境默认this为{}
    console.log(this); // {}
    
    • 普通函数中默认this为global
    function test() {
        console.log(this);
    }
    test(); //global
    
    • 箭头函数中默认this为所在作用域的this
    var obj = {
    say: function () {
      setTimeout(() => {
        console.log(this)
      });
    }
    }
    obj.say(); // obj,此时this指的是定义他的obj
    

关于this指向

  • 普通函数的this指向函数的调用者
  • 箭头函数的this指向所在作用域的this
var obj = {
    name: 'objName',
    say() {
        console.log(this.name);
    }
    read: () => {
        console.log(this);
        console.log(this.name);
    }
}
obj.say() // objName
obj.read() // {}, undefined
window.val = 1;
var obj = {
    val: 2,
    dbl: function() {
        this.val *= 2;
        val *= 2;
        console.log(val);
        console.log(this.val);
    }
}
obj.dbl(); // 2 4
var func = obj.dbl;
func(); // 8 8

obj.dbl();执行这行代码时,this指的是obj,所以this.val === obj.val*=2,最后结果为4,val*=2 === window.val *= 2,最后结果是2

func(),执行这行代码时,func()没有任何前缀,this指的是window.func();所以此时this值得是window,this.val === window.val *= 2,此时window.val 为4,val*=2 === window.val *2,最后结果为8,最后console.log(this.val),与console.log(val),指的都是window.val,最后结果都是8

Tree-shaking

什么是tree-shaking

通过工具'摇'我么的JS文件,将其中用不到的代码'摇'掉;
具体来说, 在webpack项目中,有一个入口文件,相当于一棵树的主干,m入口文件有很多依赖模块,相当于树枝.实际情况中,虽然依赖了某个模块,但其实只使用其中的某些功能,通过tree-,shaking,将没有使用的模块摇掉,这样来达到删除无用代码的目的;
支持tree-shaking的构建工具

  • Rollup
  • Webpack2
  • Closure compiler

tree-shaking原理

Tree-shaking的本质是消除无用的js代码. 无用代码消除在广泛存在于传统的编程语言编辑器;编译器可以判断出哪些代码根本不影响输出,然后消除这些代码,这个称之为DCE(dead code elimination);

Tree-shaking是DCE的一种新的实现,JavaScript同传统的编译语言不同的是,javascript绝大多数情况需要通过网络进行加载,然后执行,加载的文件大小越小,整体执行时间更短,所以去除无用代码以减少文件体积,对JavaScript来说更有意义

Tree-shaking和传统的DCE方法又不太一样,传统的DCE消灭不可能执行的代码,而Tree-shaking更关注与消除没有用到的代码

编译型语言:程序执行之前,有个单独的编译过程,将程序翻译成机器语言,以后执行这个程序的时候,就不用再进行翻译了;
解释性语言: 是在运行的时候将程序翻译成机器语言,所以运行速度相对于编译型语言要慢
https://zhidao.baidu.com/question/193596037.html

(1)DCE消除大法

  • 代码不会被执行,不可到达
  • 代码执行的结果不会被用到
  • 代码只会影响死变量(只写不读)

传统编译型的语言中,都是由编译器将Dead Code从AST(抽象语法树)中删除,那Javascript中由谁来做DCE呢
首先肯定不是浏览器做DCE,因为当我们的代码送到浏览器,那还谈什么消除无法执行的代码来优化,所以肯定是送到浏览器之前的步骤进行优化

(2) Tree-shaking

主要关注与无用模块的消除
消除原理依赖ES6的模块特性
ES6 module特点

  • 只能作为模块顶层的语句出现
  • import的模块名只能是字符串变量
  • import binding是immutable的

ES6模块依赖关系是确定的,和运行时的状态无关,可以进行可靠度的静态分析,这是Tree-shaking的基础
所谓静态分析就是不执行代码,从字面量上对代码进行分析,ES6之前的模块化,如果动态require一个模块,只有执行后才知道引用的什么模块,不能通过静态分析去优化;

无用的类不能消除

  • rollup只处理函数和顶级的import/export变量,不能把没用到的类的方法消除掉
  • JavaScript动态语言的特性使得静态分析比较困难

参考

类型和语法

类型

使用复合条件检测null的类型

var a = null;
console.log(!a && typeof a === 'object');

函数是'可调用的对象', 是object的一个'子类型', 它有一个内部属性[[Call]], 该属性使其可以被调用
�函数对象的length属性是其声明的参数个数

function a(b, c) {

}
console.log(a.length); // 2

变量是没有类型的,只有值才有;typeof 得到的结果时该变量持有值得类型,因为JavaScript中变量没有类型

typeof 运算符总是会返回一个字符串

console.log( typeof typeof 42); // 'string'

undefined(已在作用域中声明但是还没赋值的变量): 变量在未持有值得时候
undeclared(还没有在作用域中声明过的变量):

var a;
console.log(a); // undefined
console.log(b); // ReferenceError: b is not defined
typeof a; // undefined
typeof b; // undefined  b是一个undeclared变量,没有报错,是因为typeof 有一个特殊的安全防范机制

undefinedis not defined 是两码事

使用typeof 的安全防范机制(阻止报错)来检查undeclared变量

// 这样做会抛出错误 ReferenceError: b is not defined
if(b) {

}
// 这样是安全的
if(typeof b !== 'undefined') {
    console.log(11);
}

数组

var a = [1, '2', [3]];
console.log(a.length, a); // 3 [ 1, '2', [ 3 ] ]
delete a[0];
console.log(a.length, a); // 3 [ <1 empty item>, '2', [ 3 ] ]

使用delete运算符可以将单元才能够数组中删除,但是删除后,数组的length不会发生变化 ?

'稀疏'数组(含有空白或空缺单元的数组)

var a = [];
a[0] = 1;
a[2] = [3];
console.log(a.length, a[1]); // 3 undefined

a[1]的值是undefined, 这将与将其显式赋值为undefined(a[1] = undefined) 还是有区别的 ?

类数组
有时候需要将类数组(一组通过数字索引的值)转化为真正的数字;
一般通过 数组工具函数(如indexOf(..), concat(..), forEach(..)等)来实现
工具函数slice(..)经常被用于这类转换

function foo() {
    var arr = Array.prototype.slice.call(arguments);
    arr.push('bam');
    console.log(arr); // [ 'bar', 'baz', 'bam' ]
};
foo('bar', 'baz')

es6的内置函数 Array.from(..)也能实现同样的功能

...
var arr = Array.from(arguments);
...

字符串

字符串经常被当做字符数组, 它们都是类数组,都有length属性以及 indexOf(..)和concat(..)方法;

JavaScript中字符串是不可变的,但是数组是可变的;
字符串不可变是指字符串的成员函数不会改变其原始值,而是创建并返回一个新的字符串.而数组的成员函数都是在其原始值上进行操作;

var a = 'foo';
c = a.toUpperCase();
console.log(a === c); // false
console.log(a, c); // foo FOO

许多数组函数用来处理字符串很方便,虽然字符串没有这些函数,但是可以通过'借用'数组的非变更方法来处理字符串

var a = 'foo';
var c = Array.prototype.join.call(a, '-');
console.log(c); // f-o-o
var d = Array.prototype.map.call(a, function(v) {
    return v.toUpperCase() + '.';
}).join('');
console.log(d); // F.O.O.

另一个不通电在于字符串反转,数组有一个字符串没有的可变更成员函数reverse();
我们无法'借用'数组的可变成员函数,因为字符串是不可变的
一个变通(破解)的方法是先将字符串转换为数组,待处理完后再讲结果转换为字符串

var c = a
       // 将a的值转换为字符数组
       .split('')
        // 将数组中的字符进行倒转
       .reverse()
       // 将数组中的字符拼接会字符串
       .join('');

数字

42.toFixed(2); // SyntaxError
下面的语法都有效
(42).toFixed(3);
0.42.toFixed(3);
42..toFixed(3);

42.toFixed(3)是无效语法,因为.被视为常量42.的一部分;42..toFixed(3)则没有问题,因为第一个.被视为number的一部分,第二个.是属性访问运算度;

较小的数值

整数安全范围

Number.MAX_SAFE_INTEGER
Number.MIN_SAFE_INTEGER

有时JavaScript程序需要处理一些比较大的数字,如数据库的64位ID等,由于JavaScript的数字类型无法精确呈现64位数值,所以必须将他们保存转换为字符串

整数检测

Number.isInteger();
Number.isSafeInteger();

特殊数值

null, undefined,
null是一个特殊关键字,不是标识符,我们不能讲其当做变量来使用和赋值.然而undefined却是一个标识符,非严格模式下可以被当做变量来使用和赋值

void运算符
undefined是一个内置标识符,它的值为undefined,通过viod运算符即可得到该值;
表达式viod没有返回值,因此返回结果是undefined,void并不改变表达式的结果,只是让表达式不返回值

var a = 42;
console.log(void a, a); // undefined 42

习惯上用void 0来获得undefined

function doSomething() {
    if(!App.ready) {
        // 不让表达式返回结果
        return void setTimeout(doSomething, 100);
    }
    var result;
    return result;
}
if(doSomething()) {
    // 
}

类似于

if(!App.ready) {
    setTimeout(doSomething, 100);
    return;
}

总之,如果要将代码中的值(如表达式的返回值)设为undefined,就可以使用void

特殊数字

1.不是数字的数字NaN

如果数学运算的操作数不是数字类型,就无法返回一个有效的数字,这种情况下返回值为NaN;
NaN是一个特殊值,它和自身不相等,是唯一一个非自反(自反, 即 x===x不成立)的值, NaN != NaN 为true
isNaN(..)判断一个值是否为NaN
但是有个验证缺陷, 检查方式过于死板,就是'检查参数是否不是NaN, 也不是数字';

var a = 2 / 'foo';
var b = 'foo';
console.log(a);
console.log(window.isNaN(a), window.isNaN(b)); // true true

es6提供了 Number.isNaN();

实际还有一个更简单的方法,即利用NaN不等于自身这个特点.NaN是JavaScript中唯一一个不等于自身的值;

if(!Number.isNaN) {
    Number.isNaN = function (n) {
        return n !== n;
    }
}

2.无穷数

var a = 1/0; // Infinity
var a = -1/0; // -Infinity
Infinity/Infinity; // NaN

javascript的运算结果有可能溢出, 此时的结果为 Infinity或-Infinity

3.零值

4.特殊等式
NaN和-0在相等比较时有些特别, NaN和自身不相等,使用Number.isNaN(..); -0等于0, 使用isNegZero(..)
Object.is(..)来判断两个值是否绝对相等;
能使用 == 或者 === 时就尽量不要使用Object.is(..);因为前者效率更高,更为通用,后者主要用来处理那些特殊的相等比较

值和引用

简单标量基本类型值(字符串和数字)通过值复制来赋值/传递,而复合值(对象等)通过引用复制来赋值/传递;JavaScript中的引用和其他语言中的引用/指针不同,他们不能指向别的变量/引用,只能指向值

function foo(x) {
    x.push(4); // x:[ 1, 2, 3, 4 ]
    x = [4, 5, 6];
    x.push(7);// x:[ 4, 5, 6, 7 ]
    
}
var a = [1, 2, 3];
foo(a); // a:[ 1, 2, 3, 4 ]
function foo(x) {
    x.push(4); // x:[ 1, 2, 3, 4 ]
    x = [4, 5, 6];
    x.length = 0;
    x.push(4, 5, 6, 7); //a:[ 4, 5, 6, 7 ] 
    
}
var a = [1, 2, 3];
foo(a); // a:[ 4, 5, 6, 7 ]

x.length = 0和x.push(4, 5, 6, 7)并没有创建一个新的数组,而是更改了当前的数组,所以a的值变成了[ 4, 5, 6, 7 ]

如果通过值复制的方式来传递复合值

foo(a.slice());

slice(..)不带参数会返回当前数组的一个浅复本,由于传递给函数的是指向该复本的引用, 所以foo(..)中的操作不会影响a指向的数组

原生函数

常用的原生函数

  • String()
  • Number()
  • Boolean()
  • Array()
  • Object()
  • Function()
  • RegExp()
  • Date()
  • Error()
  • Symbol()

内部属性[[Class]]

所有typeof返回值为'object'的对象(如数组)都包含一个内部属性[Class].这个属性无法直接访问,一般通过Object.prototype.toString(..)来查看

Object.prototype.toString.call([1, 2, 3]); // "[object Array]"

封装对象包装

var a = 'abc';
var b = new String(a);
var c = Object(a);
console.log(typeof a); // string
console.log(typeof b); // object
console.log(typeof c); // object
console.log(b instanceof String); // true
console.log(c instanceof String); // true
console.log(Object.prototype.toString.call(b)); // [object String]
console.log(Object.prototype.toString.call(c)); // [object String]

拆封

如果想要得到封装对象中的基本类型值,可以使用valueOf()函数

var a = new String('abc');
var b = new Number(42);
var c = new Boolean(true);

console.log(a.valueOf()); // abc
console.log(b.valueOf()); // 42
console.log(c.valueOf()); // true

隐式拆封(强制类型转换)

var a = new String('abc');
var b = a + '';
console.log(typeof a); // object
console.log(typeof b); // string

将原生函数当做构造函数

Array构造函数只带一个数字参数的时候,该参数就会被当做数组的预设长度(length), 而非只充当数组中的一个元素

var a = new Array(3);
var b = [undefined, undefined, undefined];
var c = [];
c.length = 3;
console.log(a.length, a); // 3 [ <3 empty items> ]
console.log(b); // [ undefined, undefined, undefined ]
console.log(c); // [ <3 empty items> ]
console.log(a.join('-')); // --
console.log(b.join('-')); // --
a.map(function(v, i) {
    console.log(i);
    return i;
});
b.map(function(v, i) {
    console.log(i);
    return i;
})

a.map(..)之所以执行失败,是因为数组中并不存在任何单元,所以map(..)无从遍历, 而join(..)却不一样,具体实现可参考下面代码:

function fakeJoin(arr, connector) {
    var str = '';
    for(var i = 0; i < arr.length; i++) {
        if(i > 0) {
            str += connector;
        }
        if(arr[i] !== undefined) {
            str += arr[i];
        }
    }
    return Storage;
}
var a = new Array(3);
console.log(fakeJoin(a, '-'));

join(..)首先假定数组不为空,然后通过length属性值来遍历其中的元素,而map(..)并不做这样的假定

var a = Array.apply(null, {length: 3});
console.log(a); // [ undefined, undefined, undefined ]

apply(..)是一个工具函数,适用于所有函数对象,它会以一种特殊的方式来调用传递给它的函数
第一个参数是this对象,第二个参数则必须是一个数组(或者类似数组的值, 也叫做类数组对象, array-like object), 其中的值被用作函数的参数;

Object(..), Function(..)和RegExp(..)

除非万不得已,尽量不要使用Object(..), Function(..)和RegExp(..)
建议使用常量形式来定义正则表达式, 这样不仅语法简洁,执行效率也更高;因为JavaScipt引擎在代码执行前会对它们进行预编译和缓存,与其他构造函数不同,RegExp(..)还是很有用的,比如动态定义正则表达式

Date(..)和Error(..);

Date.now();

if(!Date.now) {
    Date.now = function() {
        return (new Date()).getTime();
    }
}

Date()不带 new 关键字, 则会得到当前日期的字符串值

console.log(Date());
console.log(new Date());
 // Wed May 30 2018 16:48:20 GMT+0800 (CST)
 // 2018-05-30T08:48:20.089Z

symbol(..)

原生原型

其他构造函数的原型包含它们各自类型所持有的行为特征;比如Number#toFixed(..)(将数字转换为指定长度的整数字符串)和Array#concat(..)(合并数组), 所有的函数都可以调用Function.prototype中的apply(..), call(..)和bind(..)

小结

1.Javascript为基本数据类型提供了封装对象,称为原生函数(如 String, Number, Boolean等).它们为基本数据类型提供了该子类型所持有的方法和属性(如: String#trim() 和 Array#concat())

2.对于简单标量基本类型之,比如'abc', 如果要访问它的length属性或者String.prototype方法,JavaScript引擎会自动对该值进行封装(即使用相应类型的封装对象来包装它)来实现对这些属性和方法的访问;

强制类型转换

1.toString()
JSON字符串化
工具函数JSON.stringify(..)在将JSON对象序列化为字符串时也用到了toString();

console.log(JSON.stringify(undefined)); // undefined
console.log(JSON.stringify(function() {})); // undefined
console.log(JSON.stringify(
    [1, undefined, function(){}, 4] // [1,null,null,4]
));
console.log(JSON.stringify(
    {
        a: 2,
        b: function (){}, // {"a":2}
    }
));

2.toNumber()
true转换为1, false转换为0, undefined转换为NaN, null转换为0;
对象(包括数组)会被首先转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其强制转换为数字;

为了将转换为相应的基本类型值,抽象操作ToPrimitive会首先(通过内部操作DefaultValue)检查该值是否有valueOf()方法,如果有并且返回基本类型值,就使用该值进行强制类型转换,如果没有就使用toString()的返回值(如果存在)就进行轻质类型转换;
如果valueOf()和toString()均不返回基本类型值,会产生TypeError错误

使用Object.create(null)创建的toString()方法,因此无法进行轻质类型转换

3.toBoolean

以下都是假值

  • undefined
  • null
  • false
  • +0, -0和NaN
  • ""

假值列表之外的值就是真值

[], {}和function(){}, 都是真值;

字符串和数字之间的转换

String(..),遵循前面讲过的ToString规则,将值转换为字符串基本类型
Number(..)遵循前面讲过的ToNumber规则,将值转换为数字基本类型

var a = 42;
var b = a.toString();
var c = '3.14';
var d = +c;
b; // "42"
d; // 3.14

a.toString(), 也可以转为为字符串,但是其中涉及隐式转换.因为toString()对42这样的基本类型值不适用,所以JavaScript引擎会自动为42创建一个封装对象,然后对该对象调用toString().这里显式转换中含有隐式转换;
一元运算+被普遍认为是显示强制类型转换;
1.日期显式转换为数字
一元运算+另一个常见用途是将日期(Date)对象强制类型转换为数字,返回Unix时间戳,以微秒为单位(从1970年1月1日 00:00:00 UTC到当前时间);

var d = new Date('Wed May 30 2018 16:48:20 GMT+0800 (CST)');
+d;
// 常用下面的方法来获得当前的时间戳
var temestamp = +new Date();

奇特的~运算符 ?

~运算符(即字位操作符'非');
字位操作符只适用于32位整数,运算符会强制操作数使用32位格式,这是通过抽象操作ToInt32实现的
ToInt32首先会执行ToString强制类型转换,比如'123'先被转换为123,然后再执行ToInt32

字位运算符(如| 和~)个某些特殊数字一起使用会产生类似强制类型转换的效果,返回另外一个数字

console.log(0|-0); // 0
console.log(0|NaN); // 0
console.log(0|Infinity); // 0
console.log(0|-Infinity); // 0

以上这些特殊数字无法以32位格式呈现,因此toInt32返回0

~首先将值强制类型转换为32位数字,然后再执行字位操作'非'(对每一个字位进行反转)

字位截除
~~截除数字值的小数部分,

显式转换为布尔值

建议使用 Boolean(a) 和 !!a来进行显式强制类型转换

隐式强制类型转换

var a = [1, 2];
var b = [3, 4];
console.log(a+b); // 1,23,4

某个操作数如果是字符串或者能够通过以下步骤转换为字符串的话, +将进行拼接操作,如果其中一个操作数是对象(包括数组),则首先对其调用ToPromitive抽象操作,该抽象操作再调用[[DefaultValue]], 以数字作为上下文;
因为数组的valueOf()操作无法得到简单基本类型值,于是她转而调用toString().因此上例中的两个数组变成了'1, 2' 和'3, 4'。+将它们拼接后返回'1,23, 4'

var a = '3.14';
var b = a - 0;
console.log(b); // 3.14

-是数字减法运算符,因此a-0会将a强制类型转换为数字,也可以使用a*1和a/1,因为这两个运算符也只适用于数字

抽象相等

[] == ![]; // true
2 == [2]; // true
'' == [null]; // true

== 右边的值[2]和[null]会进行ToPromitive强制类型转换,以便能和左边的基本类型值(2和'')进行比较,因为数组的valueOf()返回数组本身,所以强制类型转换过程中数组会进行字符串化
[2]会转换成'2',然后通过ToNumber()转换为2;
[null]会直接转换为'';
所以最后的结果就是2 == 2和'' == ''

0 == '\n';//true

'', '\n'(或者' ' 等其他空格组合)等空字符串被toNumber强制转换为0;

'0' == false; // true
false == 0; //true
false == ''; // true
false == []; // true
'' == 0; // true
'' == []; // true
0 == []; // true

其中4中情况涉及 == false,之前我们说过应该避免,
现在剩下3种

'' == 0; // true
'' == []; // true
0 == []; // true

安全运用隐式强制类型转换

我们要对 == 两边的值认真推敲,以下两个原则可以让我们有效地避免出错

  • 如果两边的值中有 true或者false,千万不要用 ==
  • 如果两边的值中有 [], ''或者 0, 尽量不要使用 ==

抽象关系比较

1.比较双方首先会调用 ToPrimitive, 如果结果出现非字符串,就根据ToNumber规则将双方强制类型转为数字来比较
2.如果都是字符串,则按字母顺序来进行比较
3.<= 应该是 '小于或者等于', 实际上JavaScipt中 <= 是'不大于'的意思(!(a > b), 处理为!(b < a)).同理 a >= b 处理为 b <= a;

相等比较有严格相等,关系比较缺没有'严格关系比较', 如果要避免 a < b中发生隐式强制类型比较,我们只能确保 a和b为相同的类型,除此之外别无它法;
尽量使用 Number(a) < Number(b);

语法

关联

? : 是右关联
右关联不是指从右往左执行,而是指从右往左组合

a ? b : c ? d : e;
// 应该是以下组合顺序
a ? b : (c ? d : e);

某些情况, 返回的结果没有区别,但实际却有十分微妙的差别

var a = true, b = false, c = true, d = true, e = false;

a ? b : (c ? d : e); // false, 执行a和b
(a ? b : c) ? d : e; // false,执行a, b和e

另一个右关联的例子是 = 运算符

var a, b, c;
a = b = c = 42;

它首先执行 c = 42, 然后是b = .., 最后是a = .., 因为是右关联,所以它实际上是这样来处理的: a = (b = (c = 42));

来看一些复杂的例子

var a = 42;
var b = 'foo';
var c = false;
var d = a && b || c ? c || b ? a : c && b : a;
d; // 42
// 根据优先级和关联, 根据组合规则将上方代码分解如下

((a && b) || c) ? ((c || b) ? a : (c && b)) : a;

// 可以通过缩进显式让代码更容易理解
(
    (a && b) 
       || 
    c
) 
? 
(
    (c || b)
      ? 
      a 
       : 
    (c && b))
 : a;

(1) (a && b) 结果为 'foo';
(2) 'foo' || c 结果为 'foo';
(3) 第一个 ? 中 'foo'为真值;
(4)(c || b)结果为'foo'
(5)第二个 ?中, 'foo'为真值
(6) a的值为42
最后结果为42

最后: 如果运算符优先级/关联规则能够令代码更为简洁,就使用运算符优先级/关联规则,如果( )有助于提高代码可读性,就使用( );

自动分号

有时JavaScript会自动为代码补上缺失的分号,即自动分号插入(Automatic Semicolon Insertion ASI;

ASI只在换行符处起作用,而不会再代码行的中间插入分号;只有代码行末尾与韩航服之间除了空格和注释之外没有别的内容,它才会这样做

触摸事件和手势事件

TouchEvent(触摸事件)

TouchEvent是一类描述手指在触摸平面(触摸屏, 触摸板等)的状态变化事件.这类事件用于描述一个或多个触电, 使开发者可以检测触点的移动, 触点的增加和减少等
每个touch对象代表一个触点:每个触点都由位置, 大小, 形状, 压力大小和目录element描述.TouchList对象代表多个触点的一个列表

  • touchstart: 当手指触摸屏幕时触发;即使已经有一个手指放在了屏幕上也会触发
  • touchmove: 当手指在屏幕上滑动时连续的触发。在这个事件发生期间,调用preventDefault()可阻止滚动。
  • touchend: 当手指从屏幕上移开时触发。
  • touchcancel: 当系统停止跟踪触摸时触发。关于此事件的确切触发事件,文档中没有明确说明。

除了常见的DOM属性以外, 触摸事件还包含下列用于跟踪触摸的属性

  • touches: 表示当前跟踪的触摸操作的Touch对象的数组。
  • targetTouches: 特定于事件目标的Touch对象的数组。
  • changeTouches: 表示自上次触摸以来发生了什么改变的Touch对象的数组。

每个touch对象包含下列属性

  • screenX: 触摸点相对于屏幕的位置
  • screenY: 触摸点相对于屏幕的位置的位置(Y坐标)
  • clientX: 触摸点相对于浏览器窗口的位置(X坐标)
  • clientY: 触摸点相对于浏览器窗口的位置
  • pageX: 触摸点相对于页面的位置(X坐标)
  • pageY: 触摸点相对于页面的位置(Y坐标)
  • identifier: 表示触摸的唯一ID
  • target:

GestureEvent(手势事件)

非标准的事件;
当两个手指触摸屏幕时就会产生手势

  • gesturestart: 当一个手指已经按在屏幕上面另一个手指有触摸屏幕时触发。
  • gesturechange: 当触摸屏幕的任何一个手指的位置发生变化时触发。
  • gestureend: 当任何一个手指从屏幕上面移开时触发。

DragEvent(拖拽事件)

  • dragstart
  • drag
  • dragend

参考

开启gzip压缩

gzip压缩简介

1. 为什么要开启gzip压缩?

加快资源加载速度

2.开启gzip为什么会加快资源加载速度?

没有开启压缩的时候, 资源加载时间为: 获取资源时间 + 资源下载时间
开启压缩的时候, 资源加载时间为: 压缩资源时间 + 获取压缩后资源时间 + 下载压缩后的资源时间 + 解压缩资源时间
咋一看开启压缩后的时候比没开启压缩时间要多一个压缩和解压缩的时间, 举个例子, 获取一个js文件比如没开始压缩的时候, 所用时间为 5ms + 5ms = 10ms; 开启压缩的时间为 1ms + 2ms+ 2ms + 1ms = 6ms, 这样算下来, 开启压缩就会加快资源加载速度.

但是并不是什么资源开启压缩之后都会加快资源加载速度, 像图片资源这种就不需要开启压缩, 因为图片这种不会压缩很多, 因为它们已经内置了压缩

3.怎么在nginx开启gzip压缩

# 开启gzip
gzip on;
# 启用gzip压缩的最小文件,小于设置值的文件将不会压缩
gzip_min_length 1k;
# gzip 压缩级别,1-10,数字越大压缩的越好,也越占用CPU时间,后面会有详细说明
gzip_comp_level 2;
# 进行压缩的文件类型。javascript有多种形式。其中的值可以在 mime.types 文件中找到。
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript;

4.怎么看有没有开启gzip压缩??

在response header中看有没有Content-Encoding: gzip
image

参考

代码整洁之道

代码整洁之道

感触比较深的几点
1.要敢于修改代码,好的代码是要不断修改的,害怕修改之后带来bug,只会让代码更加难以维护
2.有单元测试和集成测试很重要,这样在重构代码的时候,才能降低对其他模块的影响
3.要不断学习,没人敢找很久不看病的医生,不写代码的架构师不是一个好的架构师
4.每天花几分钟强迫自己,比如:盲打, 一些快捷键,看英文文档

  1. TDD(测试驱动开发)的重要性, 代码需要修改的时候可以大胆的修改, 因为修改完成之后可以执行所有的单元测试来判断修改之后有没有产生新的bug
    6.心情不好的时候, 精神状态不好的时候不要写代码,经验会告诉你,这个时候写的代码通常都是需要重新写的
    7.关于会议, 不要参加跟自己当前无关的会议, 除非这个会会给你现在的工作带来实质性的影响;如果一个会议开发一半,发现背离了开会的目的,或者因为某个争论而打乱了会议的节奏,找理由赶紧离开。

nginx使用总结

现在有如下需求

  1. 在手机端输入pc端地址自动跳转到m站地址
  2. a.com/src/a.html => a.com/a

对于需求1,刚开始想的解决方法是直接在js中判断User-Agent, 如果是手机浏览器, 则直接通过window.location跳转到 m站的地址链接, 但是这样会有一个问题: 用户在手机上输入pc的地址。 首先会把C端所有的资源都请求下来, 之后才会进入User-Agent的js判断逻辑, 之后会重新下载M站的相关资源。所以这种方式会下载没有必要的pc站的资源
最好的是在nginx上判断User-Agent直接重定向到M站

 if ( $http_user_agent ~* "(Android|iPhone|Windows Phone|UC|Kindle)" ){
        rewrite  ^/(.*)$  http://a.com/m redirect;
    }

对于需求2, 可以理解为改动点有两个

  1. 去掉.html文件后缀
  2. 去掉一层目录

Array.prototype.slice.call()方法详解

slice方法

slice方法返回一个从开始到结束(不包括结束)选择的一部分浅拷贝到另一个新数组的对象,且原始数组不会被修改

[].slice.call()或者Array.prototype.slice.call()

  • [].slice.call()或者Array.prototype.slice.call(), 就是截取(更重要的是获取,slice是得到的子数组的)函数的参 数, 然后让arguments等伪数组也可以使用数组的各种方法;
  • ES6的Array.from()也是类似的用法;不单单可以转换arguments,元素集合,还可以转化类数组对象;
  • ES6新增的拓展运算符也可以实现相同的功能
// 按照新的ES6的行为方式实现
function foo(...args) {
    // args已经是一个真正的数组
    // 丢弃args中的第一个元素
    args.shift();
    // 将整个args作为参数传给console.log(..)
    console.log(...args);
}
// 按照前ES6的老派行为方式实现
function bar() {
    // 把arguments转换为一个真正的数组
    var args = Array.prototype.slice.call(arguments);
    // 在尾端添加几个元素
    args.push(4, 5);
    // 过滤掉奇数
    args = args.filter(function(v) {
        return v % 2 == 0;
    })
    // 把整个args作为参数传给foo(..)
    foo.apply(null, args);
}
bar(0, 1, 2, 3); // 2, 4

函数foo(..)声明的...args收集参数,console.log(..)调用中的...args将其展开;

参考

Array.prototype.slice.call()方法详解
Array.prototype.slice() MDN

微信小程序: ios数据无法正常显示,请求接口一直显示need login

1,ios数据无法正常显示,请求接口一直显示need login,

  • 登录逻辑: 1,微信授权 2,去请求get的login判断为true,时 3,获取header里的session存储起来,之后每次请求都得带上 4,去请求post的login接口
    所以先从判断授权开始,但是在真机上测试时,一开始并不弹出授权弹窗
  • 参考提升用户体验,微信小程序“授权失败”场景的优雅处理
    所以尝试不在登录之前判断微信授权,果然可以请求到数据;而在需要的时候去判断是否授权,具体参考上述文档

但是最后在onShow的时候也遇到一些问题
tabBar页面A navigatorTo 到页面B,然后B switchTab 到A,这里A会执行onShow();
但是我再从A跳到B再switchTab回来,A就不会再执行onShow()了,
去开发者社区询问,得知此问题在下个版本会修复

vue中scroll函数的用法

data () {
  return {
    scrolled: false
  };
},
methods: {
  handleScroll () {
    this.scrolled = window.scrollY > 0;
  }
},
created () {
  window.addEventListener('scroll', this.handleScroll);
},
destroyed () {
  window.removeEventListener('scroll', this.handleScroll);
}

nginx location 详解

nginx location 详解

语法

location [=|~|~*|^~] /uri/ {}
  • = 表示精确匹配; 要求字符串与uri严格匹配, 如果匹配成功,则停止向下搜索,并立即处理此请求
  • ~ 表示区分大小写的正则匹配
  • ~* 表示不区分大小写的正则匹配
  • ^~ 表示uri以某个常规字符串开头, 不是正则匹配
  • / 表示通用匹配, 如果没有其他匹配, 任何请求都会匹配到

匹配顺序
一个具体的请求path过来之后, nginx的具体匹配过程可以分为几步

关于location匹配结尾的斜杠

在location后面接的表达式中的斜杠, 可有可无, 并没有影响;
而对于URL中的 / 则是当有/时表示目录, 没有时则表示文件.当有 /是服务器会自动去对应 目录下找默认文件, 而如果没有 / 则会优先去匹配文件. 如果找不到文件才会重定向到目录, 查默认文件

root和alias的区别

root指令会将location中的部分附加到root定义的末尾形成一个完整的路径;
alias则不会包含location中定义的部分
比如:

location /static {
    root /var/www/app/static/;
    autoindex off;
}

那么当 Nginx 寻找路径时会是:

/var/www/app/static/static/

如果这个在 static 目录的 static 目录不存在则显而易见会产生 404 错误。这是因为 location 中的 static 部分被附加到了 root 指定的路径后面,因此正确的做法是:

location /static {
    root /var/www/app/;
    autoindex off;
}

而对于 alias 正确的做法则是:

location /static {
    alias /var/www/app/static/;
    autoindex off;
}

图解HTTP

了解Web及网络基础

网络基础 TCP/IP

TCP/IP的分层管理

TCP/IP协议族按层次分别为以下4层: 应用层, 传输层和数据链路层

应用层

决定了向用户提供应用服务时通信的活动;
TCP/IP协议族内预存了各类通用的应用服务.比如, FTP(File Transfer Protocol, 文件传输协议)和DNS(Domain Name System, 域名系统)服务就其中两类.
hTTP协议也处于该层;

传输层

传输层对上层应用层,提供处于网络连接中的两台计算机之间的数据传输;
在传输层中有两个性质不同的协议: TCP(Transmission Control Protocal, 传输控制协议)和UDP(User Data Protocal, 用户数据报协议);

网络层(又名网络互连层)

网络层用来处理网络上流动的数据包.数据包是网络传输的最小数据单位.该层规定了通过怎样的路径(所谓的传输路线)到达对方计算机,并把数据包传送给对方;
与对象计算机之间通过多台计算机或网络设备进行传输,网络层所起的作用就是在众多的选项中选择一条传输路线;

链路层(又名数据链路层,网络接口层)

用来处理连接网络的硬件部分.包括控制操作系统,硬件的设备驱动,NIC(Network Interface Card, 网络适配器,即网卡),及光纤等物理可见部分(还包括连接器等一切传输媒介).硬件上的范畴均在链路层的作用范围之内;

TCP/IP的通信传输流

img

img

与HTTP关系密切的协议: IP, TCP和DNS

IP协议位于网络层, 作用是把各种数据包传送给对方

TCP位于传输层,提供可靠的字节流服务; TCP协议为了更容易传送打数据才把数据分割,而且TCP协议能够确认数据最终是否送达到对方;
为了准确无误地将数据送达到目标处,TCP协议采用了三次握手策略;用TCP协议把数据包送出去后,TCP不会对传送后的情况置之不理,它一定会向对方确认是否成功送达.握手过程中使用TCP的标志: SYN和ACK;
发送端首先发送一个带SYN标志的数据包给对方.接受端收到后,回传一个带有SYN/ACK标志的数据包以示传达确认信息.最后,发送端再回传一个带ACK标志的数据包,代表'握手'结束;

DNS(Domain Name System)服务是和HTTP协议一样位于应用层的协议.它提供了域名到IP地址之间的解析服务;
DNS协议提供通过域名查找IP地址,或逆向从IP地址反查域名的服务;

URI和URL

img

简单的HTTP协议

请求报文和响应报文

持久链接节省通信流量

使用浏览器浏览一根 包含多张图片的HTML页面时,在发送请求访问HTML页面资源的同时,也会请求HTML页面里包含的其他资源,因此,每次的请求都会造成武威的TCP连接的建立和端来,增加通信量的开销;
持久连接的特点是: 只要任意一端没有明确提出断开连接,则保持TCP连接状态;
持久连接的好处在于减少了TCP连接的重复建立和断开所造成的额外开销,减轻了服务器端的负载.另外,减少开销的那部分时间,使HTTP请求和响应能够更早地技术,这样Web页面的显示速度也就响应提高了.

使用Cookie的状态管理

http协议是无状态的,增加了cookie来保存一些数据;
Cookie会根据从服务器端发送的响应报文内的一个叫做Set-Cookie的首部字段信息,通知客户端保存Cookie.在下次客户端再往改服务器发送请求时,客户端会自动在请求报文中加入Cookie值后发送出去;
服务器端发现客户端发送过来的Cookie后,会去检查究竟从哪一个客户端发来的连接请求,然后对比服务器上的记录,最后得到之前的状态信息;

返回结果的HTTP状态码

HTTP首部字段

响应首部字段

WWW-Authenticate
WWW-Authenticate: Basic realm="Usagidesign Auth"
用于HTTP访问认证.它会告诉客户端适用于访问请求URI所指定资源的认证方案(Basic或是Digest)和带参数提示的质询(challenge).状态码401 Unauthorized响应中,肯定带有首部字段WWW-Authenticate;
realm字段的字符串是为了辨别请求URI指定资源所受到的保护策略

确保Web安全的HTTPS

确认访问用户身份的认证

Web的攻击技术

因回话管理疏忽引发的安全漏洞

1.回话劫持
回话劫持(Session Hijack)是指攻击者通过某种手段拿到了用户的回话ID,并利用此回话ID伪装成用户,达到攻击的目的.
下面列举几种攻击者可获得回话ID的途径

  • 通过非正规的生成方法推测回话ID
  • 通过窃听或XSS攻击盗取回话ID
  • 通过回话固定攻击(Session Fixation)强行获取回话ID
    2.回话固定攻击
    3.跨站点请求伪造
    跨站点请求伪造(Cross-Site Request Forgeries, CSRF)攻击是指攻击者通过设置好的陷阱,强制对已完成认证的用户进行非预期的个人信息或设定信息等某些状态更新,属于被动攻击;
    可能造成以下影响
  • 利用已通过认证的用户权限更新设定信息等
  • 利用已通过认证的用户权限购买商品
  • 利用已通过认证的用户权限在留言板上发表言论

其他安全漏洞

1.密码破解攻击(Password Cracking)即算出密码,突破认证
有以下两种手段

  • 通过网络的密码试错
  • 对已加密密码的破解(指攻击者入侵系统,已获得加密或散列处理的密码数据的情况)
    除去突破认证的攻击手段,还有SQL注入攻击逃避认证,跨站脚本攻击窃取密码信息等方法
    2.点击劫持
    利用透明的按钮或链接做成陷阱,覆盖在Web页面之上,然后诱使用户在不知情的情况下,点击那个链接访问内容的一种攻击手段.这种行为又称为界面伪装;
    3.Dos攻击
    一种让运行中的服务呈停止状态的攻击.有时也叫服务器停止攻击或拒接服务攻击.Dos攻击的对象不仅限于Web网站,还包括网络设备及服务器等.
    主要有一下两种Dos攻击方式
  • 集中利用访问请求造成资源过载,资源用尽的同事,实际上服务也呈停止状态
  • 通过攻击安全漏洞使服务停止
    4.后门程序
    后门程序(Backdoor)是指开发设置的隐藏入口,可不按正常步骤使用受限功能.利用后门程序就能够使用原本受限制的功能
    主要分为以下3种类型
  • 开发阶段作为Debug调用的后门程序
  • 开发者为了自身利益植入的后门程序
  • 攻击者通过某种方法设置的后门程序

常遇到的css问题(一)

前言

我们写css的时候经常会遇到要清楚一些默认的样式,其实每次做项目中需要清除的样式就经常是那么几个,最常见的比如

  • 清除表单元素input,select,textarea的默认样式,
  • CSS3中隐藏滚动条但仍能继续滚动,
  • 多行文本溢出省略号显示等等,

所以就总结了一下,持续更新中...,也请大家多多贡献更多的常见的需要清除默认样式的方法😊

1.多行文本溢出省略号显示

  • 让文本只显示一行,然后溢出省略号显示
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  • 让文本显示两行,然后溢出部分省略号显示
    line-height: 1.3rem;
    max-height: 2.6rem;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;

2.滚动条的处理

CSS3中隐藏滚动条但仍能继续滚动

::-webkit-scrollbar {
    width: 0px;
    height: 0px;
}

清除浏览器默认的滚动条

::-webkit-scrollbar, ::-webkit-scrollbar-track, ::-webkit-scrollbar-thumb, ::-webkit-scrollbar-corner {
    display: none;
}

::-webkit-scrollbarMDN

3. 解决iPhone中overflow:scroll;滑动速度慢或者卡的问题

-webkit-overflow-scrolling : touch;

4.消除input,textarea等的默认样式

input, button, select, textarea {
    outline: none;
    -webkit-appearance: none;
    border-radius: 0;
    border:0;
}
textarea{
    resize:none;
}
  • outline: none;去掉chrome浏览器自带的点击input框出现边框情况
  • -webkit-appearance: button;使元素标签看起来像个按钮样式,意思定义了按钮样式
    -webkit-appearance: none;去掉按钮样式
  • border-radius: 0; 去掉圆角
    border:0; 去掉border
  • textarea{resize:none}取消chrome下textarea可拖动放大:

pureComponent vs Component vs Stateless Functional component

�pureComponent vs Component vs Stateless Functional component的区别

1,createClass,没有使用es6语法的时候,定义组件,就使用React.createClass来定义组件

var React = require("react");
var Greeting = React.createClass({
  
  propTypes: {
    name: React.PropTypes.string //属性校验
  },

  getDefaultProps: function() {
    return {
      name: 'Mary' //默认属性值
    };
  },
  
  getInitialState: function() {
    return {count: this.props.initialCount}; //初始化state
  },
  
  handleClick: function() {
    //用户点击事件的处理函数
  },

  render: function() {
    return <h1>Hello, {this.props.name}</h1>;
  }
});
module.exports = Greeting;
在createClass中,React对属性中的所有函数都进行了this绑定,也就是上面的handleClick其实相当于handleClick.bind(this);

2, component,因为es6对类和继承有语法级别的支持,所以用es6创建组件的方式更为优雅

import React from 'react';
class Greeting extends React.Component {

  constructor(props) {
    super(props); //调用父类的构造函数
    this.state = {count: props.initialCount};
    this.handleClick = this.handleClick.bind(this);
  }
  
  //static defaultProps = {
  //  name: 'Mary'  //定义defaultprops的另一种方式
  //}
  
  //static propTypes = {
    //name: React.PropTypes.string
  //}
  
  handleClick() {
    //点击事件的处理函数
  }
  
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

Greeting.propTypes = {
  name: React.PropTypes.string
};

Greeting.defaultProps = {
  name: 'Mary'
};
export default Greating;

3,pureComponent

我们知道,当组件的props或者state发生变化时,React就会对当前的props和state分别与nextProps和nextState进行比较,当发生变化时,就会对当前组件或子组件进行重新渲染,否则就不渲染,有时候为了避免租金进行不必要的重新渲染,我们通过shouldComponentUpdate来优化性能

class CounterButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (this.props.color !== nextProps.color) {
      return true;
    }
    if (this.state.count !== nextState.count) {
      return true;
    }
    return false;
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}

shouldComponentUpdate通过判断props.color和state.count是否发生变化来重新渲染组件,React提供了PureComponent来自动帮我们做这件事,

class CounterButton extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}

4,Stateless Functional Component

上面提到的是用来创建包含状态和用户交互的复杂组件,当组件本身只是用来展示,所有数据都是通过props传入的时候,我们便可以使用Stateless Functional Component来快速创建组件

import React from 'react';
const Button = ({
  day,
  increment
}) => {
  return (
    <div>
      <button onClick={increment}>Today is {day}</button>
    </div>
  )
}

Button.propTypes = {
  day: PropTypes.string.isRequired,
  increment: PropTypes.func.isRequired,
}

选哪个

createClass, 除非你确实对ES6的语法一窍不通,不然的话就不要再使用这种方式定义组件。

Stateless Functional Component, 对于不需要内部状态,且用不到生命周期函数的组件,我们可以使用这种方式定义组件,比如展示性的列表组件,可以将列表项定义为Stateless Functional Component。

PureComponent/Component,对于拥有内部state,使用生命周期的函数的组件,我们可以使用二者之一,但是大部分情况下,我更推荐使用PureComponent,因为它提供了更好的性能,同时强制你使用不可变的对象,保持良好的编程习惯。

为什么前端静态资源要上CDN?

为什么前端静态资源要上CDN?

大家都知道会很快, 但是具体为什么会快一些呢?

原因是把静态资源放到CDN服务器, CDN服务器会向全国各地(国际的CDN会向世界各地)的服务区分发资源, 当用户访问完整时, 会去请求离用户最近的服务器.这样时间会缩短很多.

但是有一点需要注意, html是不能放CDN的, 因为html会有缓存,而且缓存的时候会很长, 这意味着如果你更新页面, 可能会很久很久线上才会更新最新的代码.

但是你可能会问, 为什么静态资源文件, 比如css, js等可以放CDN. 难道它们不存在缓存么, 答案是它们也会存在缓存, 但是我们每次更新css和js文件的时候,文件路径都会带有不同的hash值, 有的是时间戳, 通过修改路径的不同强制更新资源.但是html文件不同, 你访问html文件, 它的路径一般是固定的, 比如网站的首页路径为, http://xxx.com/index.html, 这个路径是不会修改的, 所以html的会有缓存, 除非你在路径上加一个参数或者时间戳, 但是你总不能让用户每次访问网站的时候都在路径地址上加一个时间戳吧.

所以最好的办法是, 静态资源(css, js)放CDN, html文件放在服务器上

参考

如何提升看书效率

  1. 带着问题去阅读
    读每个章节之前, 先自己给自己提几个问题, 读完这个章节之后看有没有把这个问题给弄明白, 如果不明白,就反复阅读跟这个问题有关的部分.
    突然一下子理解了在学校的时候语文设置阅读理解这个部分的意义了

类和原型

混合对象'类'

类, 继承,实例化
举例:
'汽车'可以被看作'交通工具'的一种特例,后者是更广泛的类
Vehicle类, Car类
Vehicle可能包含: 推进器(如引擎),载人能力
定义Car类,只要声明它继承(或者扩展)了Vehicle的基础; Car定义就是对通用Vehicle定义的特殊化
隋坦Vehicle和Car会定义相同的方法,但是实例中的数据可能是不同的(如, 梅辆车都有独一无二的车牌号);

构造函数

类实例是由一个特殊的类方法构造的,这个方法名通常和类名相同,被称为构造函数.这个方法的任务就是初始化实例需要的所有信息

class Vehicle {

}

类的继承

先定义一个类,然后定义一个继承前者的类; 后者为'子类',前者这称为父类;
比喻: 父类和子类称为父类DNA和子类DNA,我们需要根据这些DNA来创建(或者说实例化)一个人, 然后才能和他进行沟通

在传统的面向类的语言中
super: '超类'(superclass), 表示当前类的父类/祖先类; 还有一个功能: 就是从子类的构造函数中通过super可以直接调用父类的构造函数;
因为对于真正的类来说,构造函数时属于类的,但是js中'类'是属于构造函数的(类似Foo.prototype..这样的类型引用), 由于js中父类和子类只存在于两者构造函数对象的.prototype对象中.

混入

用来模拟类的复制行为

function mixin(sourceObj, targetObj) {
    for(var key in sourceObj) {
        if(!(key in targetObj)) {
            targetObj[key] = sourceObj[key];
        }
    }
    return targetObj;
}

var Vehicle = {
    engines: 1,
    ignition: function() {
        console.log('Turning on my engine');
    },
    drive: function() {
        this.ignition();
        console.log('Steering and moving forward!');
    }
}

var Car = mixin(Vehicle, {
    wheels: 4,
    drive: function () {
        Vehicle.drive.call(this);
        console.log('Rolling on all' + this.wheels + 'wheels');
    }
})

原型

[[Prototype]]

js对象有一个特殊的[[Prototype]]内置属性, 其实就是对于其他对象的引用,几乎所有对象在创建时[[prototyp]]属性都会被赋予一个非空的值

var myObject = {
    a: 2,
}
console.log(myObject.a);

对于默认的[[Get]]操作来说,如果无法再对象本身找到需要的属性,就会继续访问对象的[[prototype]]链

var anotherObject = {
    a: 2,
}
var myObject = Object.create(anotherObject);
console.log(myObject.a);

Object.create(..), 会创建一个对象并把这个对象的[[prototype]]关联到指定的对象

myObject对象的[[Prototype]]关联到了anotherObject, 现在myObject.a并不存在,但是尽管如此,属性访问仍然成功地找到了值2
但是,如果anotherObject中也找不到a并且[[Prototype]]链不为空的话,就会继续查找下去
这个过程会持续到找到匹配的属性名或者查找完整条[[[Prototype]]]链,如果是后者的话, [[Get]] 操作的返回值是undefined

for...in 遍历对象的原理和查找[[Prototype]]链类似,任何可以通过原型来呢访问到(并且是enumerable)的属性都会被枚举;
in 操作符来检查属性在对象中是否存在时,同样会查找对象的整条原型链(无论属性是否可枚举);

当你通过各种语法进行属性查找时都会查找[[Prototype]]链,直到找到属性或者查找完整条原型链

Object.prototype

到哪里是[[Prototype]]的'尽头'呢?
所有普通的[[Prototype]]链最终都会指向内置的Object.prototype.
由于所欲的'普通'(内置,不是特定主机的拓展)对象都'源于'(或者说把[[Prototype]]链的顶端设置为)这个Object.prototype对象,所以它包含Javascript中许多通用的功能(比如.toString()和.valueOf(), .hasOwnProperty(..), isPrototypeOf(..))

属性设置和屏蔽

给一个对象设置属性并不仅仅是添加一个新属性或者修改已知的属性值,现在完整的讲解一个这个过程

myObject.foo = 'bar';

如果myObject对象中包含名为foo的普通数据访问属性,这条赋值语句只会修改已有的属性;
如果foo不是直接存在于myObject中, [[Prototype]]链就会被遍历, 类似[[Get]]操作,如果原型链上找不到foo,foo就会被直接添加到myObject上;
如果属性名即出现在myObject中也出现在myObject的[[Prototype]]链上层,那么就会发生屏蔽, myObject中包含的foo属性会屏蔽原型链上层的所有foo属性,因为myObject.foo总是会选择原型链中最底层的foo属性;
foo不直接存在于myObject中而不是存在于原型链上层时myObject.foo = 'bar'会出现的三种情况:
1.如果在[[ProtoType]]链上层存在名为foo的普通数据访问属性, 并且没有被标记为只读(writable: false), 那就会直接在myObject中添加一个名为foo的新属性,他是屏蔽属性;
2.如果在[[ProtoType]]链上层存在名为foo的普通数据访问属性, 但是它被标记为只读(writable: false), 那么无法修改已有属性或者在myObject上创建屏蔽属性,如果运行在严格模式下,代码会抛出一个错误,否则,这条赋值语句会被忽略,总之,不会发生屏蔽

只读属性会阻止[[Prototype]]链下层隐式创建(屏蔽)同名属性, myObject对象竟然会因为其他对象中有一个只读foo就不能包含foo属性,但是这个限制只存在=赋值中, 使用Object.defineProperty(..)并不会收到影响

3.如果在[[ProtoType]]链上层存在名为foo并且它是一个setter, 那就一定会调用这个setter, foo不会被添加到(或者说屏蔽于) myObject, 也不会重新定义foo这个setter ?

如果你希望在第二种和第三种情况下也屏蔽foo, 那就不能使用=操作符来赋值, 而是使用Object.defineProperty(..)来向myObject添加foo

一个隐式屏蔽的例子

var anotherObject = {
    a: 2,
}
var myObject = Object.create(anotherObject);
console.log(anotherObject.a); // 2
console.log(myObject.a); // 2
anotherObject.hasOwnProperty('a'); // true
myObject.hasOwnProperty('a'); // false

myObject.a++;
console.log(myObject.a); // 3
console.log(anotherObject.a); // 2

myObject.hasOwnProperty('a'); // true!

尽管myObject.a++ 看起来应该是(通过委托)查找并增加anotherObject.a属性,但是别忘了++操作相当与myObject.a = myobject.a + 1, ++操作首先会通过[[Ptototype]]查找属性a并从anotherObject.a获取当前属性值2,然后给这个值加1, 接着用 [[put]]将值3赋给myObject中新建的屏蔽属性a;天呐!

修改委托属性时一定要小心.如果想让anotherObject.a的值增加,唯一的办法是 anotherObject.a++;

'类'

为什么一个对象需要关联到另一个对象?这样做有什么好处?
javascript没有类,只有对象

'类函数'

模仿类的行为利用了函数的一种特殊特性: 所有函数more都会拥有一个名为prototype的公有并且不可枚举的属性,它会执行另一个对象

function Foo(){
    
};
console.log(Foo.prototype); // {}

这个对象通常被称为 Foo的原型,因为我们铜鼓Foo.prototype的属性引用来访问它

function Foo(){
    
};
var a = new Foo();
console.log(Object.getPrototypeOf(a) === Foo.prototype); // true
console.log(Foo.prototype); // {}

调用new Foo()时会创建a, 其中的一步就是给a一个内部的[[Prototype]]链接,关联到Foo.prototype指向的那个对象;

js中不能创建一个类的多个实例,只能创建多个对象,他们[[Prototype]]关联的是同一个对象,但是在默认情况下并不会进行复制,因此这些对象之前并不会完全失去联系,他们是相互关联的;

关于名称
继承意味着赋值操作,javascript(默认)并不会复制对象属性.相反,Javascript会在两个对象之前创建一个关联,这样一个对象就可以通过委托访问另一个对象的属性和函数, 委托更准确的描述了Javascript中对象的关联机制;

构造函数

function Foo(){
    
};
console.log(Foo.prototype.constructor === Foo); // true
var a = new Foo();
console.log(a.constructor === Foo); // true

Foo.prototype默认有一个公有并且不可枚举的属性.constructor,这个属性引用的是对象关联的函数;
通过'构造函数'调用 new Foo()创建的对象也有一个.constructor属性,指向'创建这个对象的函数';

实际上本身并没有.constructor属性, 而且,虽然a.constructor确实指向Foo函数,但是这个属性并不是表示 a由Foo构造;
实际上 .constructor被委托给Foo.prototype, 而Foo.prototype.constructor默认指向Foo;
.constructor属性指向Foo看作是a对象由Foo构造非常容易理解,但这只不过是一种虚假的安全感, a.contructor只是通过默认的[[Prototype]]委托指向Foo, 这和'构造'毫无关系;

Foo.prototype的.contructor属性只是Foo函数在声明时的默认属性.如果你创建了一个新对象替换了默认的.prototype对象引用,那么新对象并不会自动获得 .constructor属性

function Foo() {

};
Foo.prototype = {

};
var a1 = new Foo();
console.log(a1.constructor === Foo); // false
console.log(a1.constructor === Object); // true

Object(..)并没有构造a1, 看起来应该是Foo()构造了它; 如果真的是这样,那么a1.constructor应该是Foo, 但是不是Foo, 到底是怎么回事?

a1并没有.constructor属性,但是它委托[[Prototype]]链上的Foo.prototype, 但是这个对象也没有.constructor属性(不过默认的Foo.prototype对象有这个属性), 所以它会继续委托,这次会委托给委托链顶端的Object.prototype.这个对象有.constructor属性,指向内置的Object(..)函数

function Foo() {

};
Foo.prototype = {

};
Object.defineProperty(Foo.prototype, 'constructor', {
    enumerable: false,
    writable: true,
    configurable: true,
    value: Foo
})
var a1 = new Foo();
console.log(a1.constructor === Foo); // true
console.log(a1.constructor === Object); // false

.constructor属性是一个不可枚举,可写(可以被修改); 你可以给任意[[Prototype]]链中的任意对象添加一个名为constructor属性或者对其进行修改,你可以任意对其赋值;

a.constructor是一个非常不可靠并且不安全的引用,尽量避免;

原型继承

function Foo(name) {
    this.name = name;
};
Foo.prototype.myName = function() {
    return this.name;
}
function Bar(name, label){
    Foo.call(this, name);
    this.label = label;
}
// 创建一个新的Bar.prototype对象并关联到Foo.prototype
Bar.prototype = Object.create(Foo.prototype);

// 注意! 现在没有Bar.prototype.constructor了
// 如果你需要这个属性的话可能需要手动修复它

Bar.prototype.myLabel = function() {
    return this.label;
}
var a = new Bar('a', 'obj.a');
console.log(a.myName());
console.log(a.myLabel());

Bar.prototype = Object.create(Foo.prototype);调用 Object.create(..)会并空创建一个'新'对象并把新对象内部的[[prototype]]关联到你指定的对象, 直接把原始的关联对象抛弃掉;

es6的Object.setPrototypeOf(..)
对比一下两种把Bar.prototype关联到 Foo.prototype的方法

// es6之前需要抛弃默认的Bar.prototype
Bar.prototype = Object.create(Foo.prototype);

// es6开始可以直接修改现有的Bar.prototype
Object.setPrototypeOf( Bar.prototype, Foo.prototype)

检查'类'关系

function Foo() {

}
Foo.prototype.blah = 'aa';
var a = new Foo();
console.log(a instanceof Foo); // true

instanceof 操作符的左操作数是一个普通的对象,右操作数是一个函数,instanceof回答的问题是: 在a的整条[[prototype]]链中是否有指向 Foo.prototype的对象?
这个方法只能处理对象(a)和函数(带.prototype引用的Foo)之间的关系, 如果想判断两个对象(例如a和b)之间是否通过[[Prototype]]链关联,只用instanceof 无法实现

function isRelatedTo(o1, o2) {
    function F() {};
    F.prototype = o2;
    return o1 instanceof F;
}
var a = {};
var b = Object.create(a);
console.log(isRelatedTo(b, a)); // true

isPrototypeOf()

function Foo() {

}
Foo.prototype.blah = 'aa';
var a = new Foo();
console.log(Foo.prototype.isPrototypeOf(a)); // true

isPrototypeOf(..)回答的问题是: 在a的整条[[Prototype]]链中是否会出现Foo.prototype?
判断两个对象之前的关系

b.isPrototypeOf(c);

Object.getPrototypeOf(a), 直接获取一个对象的[[Prototype]]链

function Foo() {

}
Foo.prototype.blah = 'aa';
var a = new Foo();
console.log(Object.getPrototypeOf(a)); 
console.log(Object.getPrototypeOf(a) === Foo.prototype); // true
console.log(Foo.prototype.isPrototypeOf(a)); // true

绝大多数(不是所有!)浏览器也支持一种非标准的方法来访问内部[[Prototype]]属性

a.__proto__ === Foo.prototype; // true

.constructor, .__ptoto__实际并不存在你正在使用的对象中, 它和其他的常用函数(.toString(..), .isPrototypeOf(..), 等等)一样, 存在于内置的Object.prototype中(他们是不可枚举的)

Object.defineProperty(Object.prototype, '__proto__', {
    get: function() {
        return Object.getPrototypeOf(this);
    },
    set: function(o) {
        Object.getPrototypeOf(this, o);
        return 0;
    }
})

对象关联

[[Prototype]]机制就是存在于对象中的一个内部链接,它会引用其他对象;
这个链接作用是: 如果在对象上没有找到需要的属性或者方法引用,引擎就会继续在[[Prototype]]关联的对象上进行查找,同理,如果在后者中也没有找到需要的引用就会继续查找它的[[Prototype]], 以此类推,这一系列对象的链接被称为'原型链';

创建关联

[[Prototype]]机制的意义是什么?
Object.create(..)会创建一个新对象,并把它关联到我们制定的对象;这样就可以充分发挥[[Prototype]]机制的威力(委托),并且可以避免不必要的麻烦(比如使用new的构造函数调用会生成.prototype和.constructor引用)

Object.create(null)会创建一个空的(或者说null)[[Prototype]]链接的对象,这个对象无法进行委托,由于这个对象没有原型链,所有instanceof 操作符无法进行判断, 因此总是会返回 false,这些特殊的空[[Prototype]]对象通常被称作'字典', 它们完全不会受到原型链的干扰,因此非常适合用来存储数据;

Object.create()的polyfill代码

if(! Object.create) {
    Object.create = function (o) {
        function F() {};
        F.prototype = o;
        return new F();
    }
}

使用了一个一次性函数F,我们呢通过改写它的.prototype属性使其指向想要关联的对象,然后再使用 new F()来构造一个新对象进行关联;

内置的Object.create(...)函数还提供了一系列附加功能;

var anotherObject = {
    a:2,
}
var myObject = Object.create(anotherObject, {
    b: {
        enumerable: false,
        writable: true,
        configurable: false,
        value: 3,
    },
    c: {
        enumerable: true,
        writable: false,
        configurable: false,
        value: 4,
    },
})
console.log(myObject.hasOwnProperty('a')); // false
console.log(myObject.hasOwnProperty('b')); // true
console.log(myObject.hasOwnProperty('c')); // true
console.log(myObject.a); // 2
console.log(myObject.b); // 3
console.log(myObject.c); // 4

第二个参数指定了需要添加到新对象的属性名以及这些属性的属性描述符

ES6的类

class ColorPoint extends Point {
}

// 等同于
class ColorPoint extends Point {
  constructor(...args) {
    super(...args);
  }
}

如果子类没有constructor方法,这个方法会被默认添加,
在子类的构造函数中,只有调用了super之后,才可以使用this关键字,否则会报错;这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例

super关键字
super这个关键字,既可以当做函数使用,也可以当做对象使用

第一种情况, super作为函数调用时,代表父类的构造函数, es6要求,h子类的构造函数必须执行一次super函数

class A {}

class B extends A {
  constructor() {
    super();
  }
}

上面代码中,子类B的构造函数之中的super, 代表调用弗雷的构造函数,这是必须的,否则Javascrip引擎会报错
super虽然代表了父类的构造函数,但是返回的是子类B的实例,t即super内部的this指向的是B, 因此super()在这里相当于A.prototype.call(this);

作为函数时, super()只能用在子类的构造函数之中, 用在其他地方就会报错

算法图解笔记

算法简介

二分查找

大O表达法

递归

选择排序

递归

快速排序

散列表

DNS解析: 域名到IP的对应
用于缓存

性能

简单查找所需时间为线性时间o(n);
二分查找的速度更快,所需时间为对数时间 o(logn)
散列表中查找时间为常量时间 o(1)

1.散列表的填装因子
散列表包含的元素数/位置总数
填装因子越低,发生冲突的可能性越小,散列表的性能越高.一个不错的经验规则: 一旦填装因子大于0.7,就调整散列表的长度
2.散列函数
良好的散列函数让数组中的值程均匀分布
糟糕的散列函数让值扎堆,导致大量冲突

广度优先搜索

广度优先搜索让你能够找到两样东西之间的最短距离

  • 编写国际跳棋,计算最少走多少布就可获胜
  • 编写拼写检查器,计算最少编辑多少个地方就可将错拼的单丝改为正确的单词
  • 根据你的人际关系网络找到关系最近的医生
    最短路径问题,你经常要找出最短路径,这可能是前往朋友家的最短路径,也可能是国际象棋中把对方将死的最少部署,解决最短路径问题的算法被称为广度优先搜索

图是什么
图由节点和边组成.一个节点可能与众多节点直接相连,这些节点被称为邻居

可用于回答两类问题

1.从节点A出发,有前往节点B的路径吗?
2.从节点A出发,前往节点B的哪条路径最短?

2个步骤

1.使用图来建立问题模型
2.使用广度优先搜索解决问题

只有按照顺序查找时, 广度优先搜索不仅查找A到B的路径,而且找到的是最短路径;
有一个可以实现这种目的的数据结构,就是队列(quene);
队列是一种先进先出(First In First Out, FIFO)的数据结构,栈是一种先进后出(Last In First Out, LIFO)的数据结构;

运行时间

广度优先搜索的运行时间为O(人数+边数), O(V + E), 其中V(vertice)为顶点数, E为边数

狄克斯特拉算法

  • 继续图的讨论,介绍加权图-提高或降低某些边的权重
  • 介绍狄克斯特拉算法,让你能够找出加权图中前往X的最短路径
  • 介绍图中的环,它导致狄克斯特拉算法不管用

使用广度优先搜索,找出的是段数最少的路径;
要找出最快的路径,使用狄克斯特拉算法

4个步骤

1.找出'最便宜'的节点,即可在最短时间内到达的节点
2.更新该节点的邻居的开销
3.重复这个过程,直到对图中的每个节点都这样做了
4.计算最终路径

术语

狄克斯特拉算法用于每条边都有关联数字的饿图,这些数字称为权重;
带权重的图称为加权图;不带权重的图称为非加权图;
要计算非加权图的最短路径,可使用广度优先搜索,要计算加权图中的最短路径,可使用狄克斯特拉算法;
无向图意味着两个节点彼此指向对方,其实就是环!
在无向图中,每条边都是一个环;狄克斯特拉算法只适用于有向无环图(DAG)

负权重

不能将狄克斯特拉算法用于包含负权变得图;在包含负权边的图中,要找到最短路径,可使用另一种算法-贝尔曼-福德算法

实现

节点的开销指的是从起点出发前往该节点需要多长时间;

8.贪婪算法

集合覆盖问题

NP完全问题

你需要计算的所有解,并从中选出最小/最短的那个

9.动态规划

最长公共字符串

最长公共子序列

1.如果两个字母不同,就选择上方和左右邻居中较大的那个
2.如果两个字母相同,就将当前单元格的值设置为左上方单元格的值加1

实际应用

1.git diff, 指出两个文件的差异,也是使用动态规划实现的
2.生物学家根据最长公共序列来确定DNA链的相似性
3.字符串的相似程序,编辑距离指出了两个字符串的相似程度,也是通过动态规划计算得到的;编辑距离算法的用途很多,从拼写检查到判断用户上传的资料是否是盗版

10. K最近邻算法

本章内容

  • 学习使用K最近邻算法创建分类系统
  • 学习特征抽取
  • 学习回归,即预测值,如明天的股价或用户对某部电影的喜欢程度

创建推荐系统

机器学习简介

1.OCR(optical character recognition) 是光学子符识别,这以为着你可拍摄印刷页面的照片,计算机将自动识别出其中的文字

手写一个promise

理解一些基础概念

原型链,类,实例, 类属性,类方法, 实例属性,实例方法

class A {
    static b = '1'
    static classMethod() {
        return 'hello'
    }
}
const a = new A();
a.c = 5;
a.sayHello = function() {
    console.log('welcome')
}

A: 类
b: 类属性
classMethod: 类方法
a: 实例
a.c: 实例属性

ES6的类

static:静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。

实现一个promise

Promise简介

要想自己实现一个Promise,我们首先要对Promise的用法有所了解;

Promise 是一个构造函数,用来生成 Promise 实例,接受一个函数作为参数,这个函数有两个参数 resolve 和 reject,是两个函数。resolve 是异步操作成功时调用,并将异步操作的结果作为参数传递出去。reject 是异步操作失败时调用,并将异步操作报出的错误作为参数传递出去。

静态属性或方法:是存在类上的属性或方法,实例上无法访问,只用通过 Promise.xxx 访问

要实现 xxx,首先要明白这个函数是干什么的,那又从那个方面来分析么呢?

  • 接收哪些参数
  • 返回什么参数
  • 如果 xxx,怎么处理

Promise 的静态方法

  • Promise.resolve()
  • Promise.reject()

Promise.resolve()

  • 参数:分为以下两种情况
    • Promise 实例, 直接返回
    • 原始值,或者是一个不具有then方法的对象,返回一个新的 Promise 示例,状态为resolved。
  • 返回值:一个新的Promise实例

Promise.reject()

  • 参数:分为以下两种情况
    • Promise 实例, 直接返回
    • 原始值,或者是一个不具有then方法的对象,返回一个新的 Promise 示例,状态为rejected。
  • 返回值:一个新的Promise实例

以下方法都是接受数组作为参数,数组中的某项如果不是Promise实例,会先调用Promise.resolve方法,将参数转为 Promise实例,再做处理

  • Promise.all()
  • Promise.race()
  • Promise.allSettled()
  • Promise.any()

Promise.all()

  • 参数:数组,数组中的某项如果不是Promise实例,会先调用Promise.resolve方法,将参数转为 Promise实例,再做处理
  • 返回值:
    • 数组:都成功时,返回数组
    • 一个 promise 实例:只要有一个失败,返回值为第一个被reject的实例的返回值

Promise.race()

  • 参数:数组,数组中的某项如果不是Promise实例,会先调用Promise.resolve方法,将参数转为 Promise实例,再做处理
  • 返回值:
    • 一个 promise 实例:返回那个率先改变的 Promise 实例的返回值

Promise.allSettled()

  • 接收一组 Promise 实例作为参数,只有等到所有的参数实例都返回结果才会返回

  • 场景:不关心异步操作的结果,只关心是否都结束了。比如多张图片选择后一起上传时,需要判断多张图片上传的结果,成功了几张,失败了几张

  • 参数:数组,数组中的某项如果不是Promise实例,会先调用Promise.resolve方法,将参数转为 Promise实例,再做处理

  • 返回值:

    • 返回值:数组,数组的每一项都是一个对象具体的结构如下
      [
          {
              status: 'fulfilled',
              value: 1,
          },
          {
              status: 'rejected',
              reason: 1,
          },
      ];

Promise.any()

接收一组 Promise 实例作为参数,只要有一个成功就返回成功的结果,如果所有结果都是失败的,就返回所有失败的结果

  • 参数:数组,数组中的某项如果不是Promise实例,会先调用Promise.resolve方法,将参数转为 Promise实例,再做处理
  • 返回值:
    • 一个promise实例:只要有一个成功时返回一个 promise 实例的结果
    • 数组:所有都失败时,返回数组

代码实现

class Promise {
    constructor(fn) {
        /**
         *  三种状态 
         *  pending:进行中
         *  fulfilled:已成功
         *  rejected: 已失败
         */
        this.status = 'pending';
        this.resoveList = []; // 成功后回调函数
        this.rejectList = []; // 失败后的回调函数

        fn(this.resolve.bind(this), this.reject.bind(this));
    }
    then(scb, fcb) {
        if (scb) {
            this.resoveList.push(scb);
        }
        if(fcb) {
            this.rejectList.push(fcb);
        }
        return this;
    }
    catch(cb) {
        if (cb) {
            this.rejectList.push(cb);
        }
        return this;
    }
    resolve(data) {
        if (this.status !== 'pending') return;
        this.status = 'fulfilled';
        setTimeout(() => {
            this.resoveList.forEach(s => {
                data = s(data);
            })
        })
    }
    reject(err) {
        if (this.status !== 'pending') return;
        this.status = 'rejected';
        setTimeout(() => {
            this.rejectList.forEach(f => {
                err = f(err);
            })
        })
    }
    /**
     * 实现Promise.resolve
     * 1.参数是一个 Promise 实例, 那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
     * 2.如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。
    */ 
    static resolve(data) {
        if (data instanceof Promise) {
            return data;
        } else {
            return new Promise((resolve, reject) => {
                resolve(data);
            })
        }
    }
    // 实现Promise.reject
    static reject(err) {
        if (err instanceof Promise) {
            return err;
        } else {
            return new Promise((resolve, reject) => {
                reject(err);
            })
        }
    }
    /**
     * 实现Promise.all
     * 1. Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。
     * 2. 返回值组成一个数组
    */
    static all(promises) {
        return new Promise((resolve, reject) => {
            let promiseCount = 0;
            let promisesLength = promises.length;
            let result = [];
            for(let i = 0; i < promises.length; i++) {
                // promises[i]可能不是Promise类型,可能不存在then方法,中间如果出错,直接返回错误
                Promise.resolve(promises[i])
                    .then(res => {
                        promiseCount++;
                        // 注意这是赋值应该用下标去赋值而不是用push,因为毕竟是异步的,哪个promise先完成还不一定
                        result[i] = res;
                        if(promiseCount === promisesLength) {
                        return resolve(result);
                        }
                    },(err) => {
                        return reject(err);
                    }
                )
            }
        })
    }
    /**
     * 实现Promise.race
     * 1. Promise.race方法的参数与Promise.all方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。
     * 2. 返回那个率先改变的 Promise 实例的返回值
    */
    static race(promises) {
        return new Promise((resolve, reject) => {
            for(let i = 0; i < promises.length; i++) {
                Promise.resolve(promises[i])
                    .then(res => {
                        return resolve(res);
                    },(err) =>{
                        return reject(err);
                    }
                )
            }
        })
    }
    /**
    * Promise.any()
    * - 参数:数组,数组中的某项如果不是Promise实例,会先调用Promise.resolve方法,将参数转为 Promise实例,再做处理
      - 返回值:
        - 一个promise实例:只要有一个成功时返回一个 promise 实例的结果
        - 数组:所有都失败时,返回数组
    */
    static any(promises) {
        return new Promise((resolve, reject) => {
            const promisesLen = promises.length;
            let promiseCount = 0;
            let errorResult = [];
            for (let i = 0; i < promisesLen; i++) {
                Promise.resolve(promises[i])
                    .then((value) => {
                        return resolve(value);
                    },(err) => {
                        promiseCount++;
                        errorResult[i] = err;
                        if (promiseCount === promisesLen) {
                            return reject(errorResult);
                        }
                    },
                );
            }
        });
    }
    /**
    * Promise.allSettled()
    * - 参数:数组,数组中的某项如果不是Promise实例,会先调用Promise.resolve方法,将参数转为 Promise实例,再做处理
    * - 返回值:数组
    */
    static allSettled(promises) {
        return new Promise((resolve, reject) => {
            const promisesLen = promises.length;
            let promiseCount = 0;
            const result = [];
            for (let i = 0; i < promisesLen; i++) {
                Promise.resolve(promises[i])
                    .then((value) => {
                        promiseCount++;
                        result[i] = {
                            status: 'fulfilled',
                            value,
                        };
                        if (promiseCount === promisesLen) {
                            return resolve(result);
                        }
                    },(err) => {
                        promiseCount++;
                        result[i] = {
                            status: 'rejected',
                            reason: err,
                        };
                        if (promiseCount === promisesLen) {
                            return resolve(result);
                        }
                    },
                );
            }
        });
    }
}

测试用例

1. Promise.then

const p = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('resolve');
        resolve(222);
    }, 1000)
})

p.then(data => {
    setTimeout(() => {
        console.log('data', data);
    })
    return 3333;
}).then(data2 => {
    console.log('data2', data2);
}).catch(err => {
    console.error('err', err);
});

2. Promise.reject

const p1 = Promise.reject('出错了');
p1.then(null, function (s) {
    console.log(s); // 出错了
});

3. Promise.all && Promise.race && Promise.any && Promise.allSettled

const q1 = new Promise((resolve, reject) => {
    resolve('hello')
});

const q2 = new Promise((resolve, reject) => {
    reject('world')
});

Promise.all([q1, q2])
.then((res) => {
    console.log(res);
}, (err) => {
    console.log(err); // world
});

Promise.race([q1, q2]).then(res => {
    console.log(res); // hello
});

Promise.any([q1, q2]).then(res => {
    console.log(res); // hello
});

Promise.allSettled([q1, q2]).then(res => {
    console.log(res); 
    /**
      [
        { status: 'fulfilled', value: 'hello' },
        { status: 'rejected', reason: 'world' }
        ]
    */
});

react-redux

redux

redux的三个概念: action reducer store

  • action: 一个包含type的object, 通常把一个返回action对象的函数称为action函数, 直接简称为action

function action(data) {
    return {
        type: 'Action1',
        data,
    };
}


  • reducer: 一个根据action type来更新数据的函数
function reducer(state = { data: 1 }, action) {
    switch (action.type) {
        case 'action1': {
            const newState = JSON.parse(JSON.stringify(state));
            return newState;
        }
        case 'action2': {
             const newState = JSON.parse(JSON.stringify(state));
             return newState;
        }
        default: {
            return state;
        }
    }
}

  • store: 使用createStore从reducer函数创建的数据对象, 包含getState和dispatch方法
import { createStore } from 'redux';

let store = createStore(reducer);
console.log(store.getState())

store.dispatch(action(0))
console.log(store.getState())

注意! 更新数据需要dispatch对应的action

react-redux

用于将redux于react结合

  • Provider: 顶层组件, 注入store对象
import { Provider } from 'react-redux';

ReactDom.render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById('app'),
);

  • connect: 工具函数, 参数一为函数, 该函数接收state, 返回一个对象, 对象包含一系列数据. 参数二为函数, 该函数接收dispatch, 返回一个对象, 对象包含一系列方法
import { connect } from 'react-redux';  

class App extends Component {
    componentDidMount() {
        this.props.dispatch({
            type: 'action1',
            data: 1,
        });
    }

    render() {
        // this.props.func1();
        // this.props.func2();
        return (
            <div>
            </div >
        );
    }
}

export default connect(
    state => ({
        data: state.data,
        obj: state.obj,
    }),
    dispatch => ({ dispatch }),
)(App);

immutable.js

特殊的object(MAP)/array(LIST)

  • 更新数据: set setIn update updateIn
  • 读取数据: get getIn
  • 可以直接比较(深层比较)
  • 创建(fromJS), 还原(toJS)
上述的reducer可以用下面的代码优化

function reducer(state = immutable.fromJS({ data: 1, obj: { aa: 1 } }), action) {
    switch (action.type) {
        case 'action1': {
            return state.set('data', action.data * 10);
        }
        case 'action2': {
            return state.setIn(['obj', 'aa'], action.data);
        }
        default: {
            return state;
        }
    }
}

优点

  • 无需深复制, 更新数据自动返回一个新对象
  • 适合与react shouldComponentUpdate结合来优化性能

pure-render-decorator

自动创建shouldComponentUpdate的工具, 需要配置babel decorator特性才能使用

JavaScript

1.JavaScript简介

ECMAScript, DOM, BOM

2.在HTML中使用JavaScript

2.1<script> 标签, 6个属性

  • async
  • defer
  • charset
  • language
  • src
  • type

2.1.1 标签位置

如果放在<head>元素中,意味着必须等到全部JavaScript都被下载,解析和执行完成以后才呈现页面的内容(浏览器遇到<body标签时才开始呈现内容).这无疑会导致浏览器在呈现页面时出现明显的延迟,而延迟期间的浏览器窗口中将是一片空白;
为了避免这个问题,一般把全部JavaScript引用放在<body>中,放在页面的内容后面;这样,在解析包含JavaScript代码之前,页面的内容将完全呈现浏览器中.而用户也会因为浏览器显示空白页面的时间缩短而感到打开页面速度加快了;

2.1.2 延迟脚本

defer属性,脚本会被延迟到整个页面都解析完毕后再运行;
相当于告诉浏览器立即下载,但是立即执行

2.1.3 异步脚本

async 只适用于外部脚本文件,并告诉浏览器立即下载文件;但是并不保证按照它们的先后顺序执行;

3.基本概念

�标识符: 变量,函数,属性的名字和函数的参数
逗号操作符:用于声明多个变量

4.变量,作用域,内存问题

基本类型和引用类型的值
引用对象的值是保存在内存中的对象,JavaScript不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间.在操作对象时,实际上是在操作对象的引用而不是实际的对象;

JavaScript内存分为栈,堆,池, 栈存放变量,堆存放复杂对象,池存放常量

JS中的基础数据类型,这些值都有固定的大小,往往都保存在栈内存中(闭包除外),由系统自动分配存储空间。我们可以直接操作保存在栈内存空间的值,因此基础数据类型都是按值访问 数据在栈内存中的存储与使用方式类似于数据结构中的堆栈数据结构,遵循后进先出的原则

4.1.3 传递参数

JavaScript中所有函数的参数都是按值传递的;
在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量(即命名参数);

function setName(obj) {
    obj.name = 'Nicholas';
    obj = new Object();
    obj.name = 'Greg';
}
var person = new Object();
setName(person);
console.log(person.name); // Nicholas

实际上,当在函数内部重写obj时,这个变量引用的就是一个局部对象了,而这个局部对象会在函数执行完毕后立即被销毁;

4.2 执行环境和作用域

4.3 垃圾收集

JavaScript中,所需内存的分配以及无用内存的回收完全实现了自动管理;
垃圾收集机制原理: 找出那些不再继续使用的变量,然后自动释放其占有的内存.垃圾收集器按照固定的时间间隔(或代码执行中预定的收集时间),周期性地执行这一操作;

函数中局部变量的正常生命周期: 局部变量只在函数执行过程中存在;在这个过程中,会为局部变量在堆(或栈)内存上分配相应的空间,以便存储它们的值.然后再函数中使用这些变量,直至函数执行结束.此时,局部变量就没有存在的必要了,因此可以释放它们的内存以供将来使用;

两个策略

1.标记清除
JavaScript中最常用的垃圾收集方式是标记清除.
垃圾收集器在运行的时候会给储存在内存中的所有变量都加上标记(当然,可以使用任何标记方式).然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记.而在此之后再加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了.最后垃圾收集器完成内存清除工作,销毁哪些带标记的值并回收他们所占用的内存空间;

2.引用计数
另一种不太常见的垃圾收集策略叫做引用计数.引用计数的含义是跟踪记录每个值被引用的次数;
当声明了一个变量并将一个引用类型的值赋给该变量时,则这个值的引用次数就是1;如果同一个值又被赋给另一个变量,则该值得引用次数加1;相反,如果包含对这个值引用的变量又取得了另一个值,则这个值的引用次数减1;当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占有的内存空间回收回来;

但是遇到一个严重的问题: 循环引用; 循环引用指的是对象A中包含一个指向对象B的指针,而对象B中也包含了一个指向对象A的引用;

funtion problem() {
    var objectA = new Object();
    var objectB = new Object();
    objectA.someOtherObject = objectB;
    objectB.anotherObject = objectA;
}

objectA和ObjectB通过各自的属性相互引用;也就是说,这两个对象的引用次数都是2;在采用标记清除策略的实现中,由于函数执行之后,这两个对象都离开了作用域,因此这种问题相互引用不是个问题;但是在采用引用计数策略的实现中,当函数执行完毕后,objectA和objectB还将继续存在,因为它们的引用计数永远不会是0;加入这个函数被重复多次调用,就会导致大量内存得不到回收;

编写高质量的代码

高质量的HTML

使用语义化的标签
table布局的网页特点

  • 代码量大,结构混乱
  • 标签语义不明确,对搜索引擎不友好

为了保证网页去样式后的可读性,而且又符合Web标准,我们应注意一下几点:

  • 尽可能少地使用无语义标签div和span
  • 在语义不明显,即可以用p也可以用div的地方,尽量用p,因为p默认情况有上下间距,去样式后的可读性更好,对兼容特殊终端有利
  • 不要使用纯样式标签,例如b, font和u,改用CSS设置.语义上需要强调的文本可以包在strong或em标签里, Strong和em有"强调"的语义,其中strong的默认样式是加粗,而em的默认样式是斜体

高质量的CSS

如何组织CSS
base.css + common.css + page.css
base层:会被所有页面引用,是页面样式所依赖的最底层.这一层与具体的CSS reset功能和粒度最小的通用类-原子类.不同设计风格的网站可以使用同一个base层.
base层相对稳定,基本上部需要维护.

common层: 提供组件级的CSS类;common层是网站级的,不同的网站有不同的common层,同一个网站只有一个common层.common层是放在一个common.css里,还是按照功能划分放在诸如common_form.css,common_imagelist.css的多个文件里,需要根据网站规模来决定.
在团队合作中,common层最好由一个人负责,统一管理.

page层: 网站中高度重用的模块,我们把他们视为组件,放在common层;非高度重用的模块,可以把他们放在page层.page层是页面级的,每个页面都可能会有自己的page层CSS

position: absolute会完全脱离文档流,不在z-index:0层保留占位符,其left, top, right, bottom值是相对于自己最近的一个设置了position: relative或position: absolute的祖先元素的,如果祖先元素全部都没有设置position: relative或position: absolute,那么就相对于bosy元素;

float: 也能改变文档流,不同的是,float属性不会让元素'上浮'到另一个z-index层,它仍然让元素在z-index:0层排列,float不像position: relative和position:absolute那样,它不能通过left, top,right, bottom属性精确地控制元素的坐标,它只能通过float: left和float: right来控制元素在同层里'右浮'和'左浮'.float会改变正常的文档流排列,影响到周围元素

高质量的JavaScript

避免全局变量.使用命名空间

位置

CSS放在页头,JavaScript放在页尾.在载入HTML元素之前,先载入他们的样式,这样可以避免HTML出现无样式状态;将JavaScript放在页尾,先将网页呈现给用户,再来加载页面内的脚本,避免JavaScript阻塞网页的呈现,减少页面空白的时间

JavaScript分层

base, common, page

base分为以下三块

  • 操作DOM,包括获取DOM节点和设置DOM属性
  • 操作事件,包括访问event对象的属性和设置事件监听
  • 模仿其他语言提供原生JavaSript不提供的函数

common层
common层本身依赖base层提供的接口,所以要使用common层的组件,必须先加载base层的代码
common层和page层都是供page层调用的,二者而区别在于common层提供的不是简单的接口,而是相对更庞大的组件,例如cookie,原生JavaScript对设置和读取cookie的方法显的非常笨拙;
除了Cookie,common层常用的组件还有用于异步通信的Ajax, 用于拖曳的Drag,用于拖拉元素大小的Resize,用于动画的Animation, 用于标签切换的Tab,用于树型目录的Tree,用于模拟弹出窗口的Msg,用于拾色器的ColorPicker, 用于日历的Calendar, 用于富文本编辑器的RicnTextEditor等.
一个易用性,重用性和可扩展性都是非常好的组件,代码量往往偏高,所以common层的JS需要按功能分成一个个单独的文件,例如common_cookie.js, common_drag.js, common_tab.js等,按需加载

page层
base层和common层都属于框架级,page层是属于应用层的,它可以调用base层的接口和common层的组件

获取DOM节点的几种方法

  • document.getElementById();//id名,在实际开发中较少使用,选择器中多用class id一般只用在顶级层存在 不能太过依赖id
  • document.getElementsByTagName();//标签名
  • document.getElementsByClassName();//类名
  • document.getElementsByName();//name属性值,一般不用
  • document.querySelector();//css选择符模式,返回与该模式匹配的第一个元素,结果为一个元素;如果没找到匹配的元素,则返回null
  • document.querySelectorAll()//css选择符模式,返回与该模式匹配的所有元素,结果为一个类数组

将复杂类型的数据转换成字符串,称为数据的序列化,其逆操作叫做数据的反序列化;

面试题

面试题

HTML

  1. window.onload, document.ready, DOMContentLoaded的区别

document.ready: ready事件是在DOM结构绘制完成之后就会执行
DOMContentLoaded: 当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完成加载; 注意:DOMContentLoaded 事件必须等待其所属script之前的样式表加载解析完成才会触发。
window.onload: onload网页中所有内容(包括图片)全部加载完毕之后执行

资源加载和页面加载事件

  1. iframe,如何在页面中改变另一个iframe的样式
    document.getElementById('frame').contentWindow.document.getElementById('cc');

  2. 尽可能多的方法隐藏一个html元素
    display: none;
    visibility: hidden;
    opacity: 0;

CSS:

  1. 盒模型,IE盒模型和标准盒模型,如何改变
    两者的区别在于content的不同, IE盒模型的content包括 border和 padding
    box-sizing:border-box || content-box || inherit
    content-box: 页面将采用标准盒模型模式来解析计算, 同时改模式为默认模式
    border-box: 页面将采用怪异模式解析计算, 怪异模式也称为IE模式

  2. 选择器优先级(内联样式在何处)
    !important > 内联样式(权重1000) > id选择器(权重100) > 类(class)选择器(权重10) > 标签选择器

  3. less和scss的好处
    都是css的预编译处理语言, 它们引入了mixins, 参数, 嵌套规则, 运算, 颜色, 名字空间, 作用域, javascript赋值

JS

  1. 请为所有数组对象添加一个findDuplicate(n)方法,用于返回该数组中出现频率>=n的元素列表
[1,2,3,4,1,2,2,2].findDuplicate(2) => [1,2]
[1,2,3,4,1,2,2,2].findDuplicate(5) => []
[1,2,3,4,1,2,2,2].findDuplicate(-1) => [1,2,3,4]
if (!Array.prototype.findDuplicate) {
    Object.defineProperty(Array.prototype, 'findDuplicate', {
        value: function (n) {
            if (this === void 0 || this === null) {
                throw new TypeError('this is null or not defined');
            };
            var t = Object(this);
            var len = t.length >>> 0;
            var obj = {};
            var result = [];
            for(let i = 0; i < len; i++) {
                obj[t[i]] = (obj[t[i]] || 0)+1;
                if(obj[t[i]] == n) {
                    result.push(t[i]);
                }
            }
            return result;
        }
    });
}
  1. 箭头函数的好处
    简洁, 简化回调函数, 可以与变量解构结合使用

  2. 判断数组

// 1.使用 instanceof
[] instanceof Array;

// 2.使用constructor
var a = new Array;
a.constructor === Array;
// 或
function isArray(arr){ 
  return typeof arr === "object" && arr.constructor === Array; 
}

// 3.Object.ProtoType.toString()
Object.prototype.toString.call([]); // "[object Array]"
  1. typeof和instanceof的区别

typeof操作符

可能返回的值如下

  • undefined
  • boolean
  • string
  • number
  • object
  • function

注意: typeof 的能力有限,其对于null, Date、RegExp类型返回的都是"object"

typeof null // 'object'
typeof {}; // "object" 
typeof []; // "object" 
typeof new Date(); // "object"

使用场景:区分对象和原始类型,要区分一种对象类型和另一种对象类型,可以使用: instanceof运算符或对象contructor属性

instanceof运算符

用法: 左边的运算数是一个object,右边运算数是对象类的名字或者构造函数;返回true或false

[] instanceof Array; // true
[] instanceof Object; // true
[] instanceof RegExp; // false
new Date instanceof Date; // true

如果object是class或者构造函数的实例,则返回true,如果不是或者是null也返回false
instanceof运算符判断是否为数组类型

function isArray(arr){ 
  return arr instanceof Array; 
}
  1. new和instanceof的内部机制

  2. for...in迭代和for...of有什么区别?

for in 用来遍历对象
for of 用来遍历数组

  1. 原型链中prototype和__proto__分别指什么

  2. require和import的区别:
    require是CommonJS模块, import是Es6模块,CommonJS是运行时加载, es6模块是编译时加载

  3. class实现原理:
    class是一个语法糖,很多都可以用es5的语法实现

  4. async和await实现原理:
    基于generator实现的,generator又是基于promise实现的

  5. 说一下你对generator的了解?

  6. 说一下macrotask 和 microtask?

  7. node中事件队列模型

  8. fetch api

  9. HTTP和HTTPS的区别,如何升级成HTTPS

  10. Http请求中的keep-alive有了解吗。

  11. 如何让网页离线后还能访问

  12. 强缓存和协商缓存

  13. vue中用到的设计模式

  14. webpack常见的loader和plugin,loader和plugin的区别

  15. babel-polyfill

this

this

function foo(num) {
    console.log("foo: " + num);
    this.count++;
}
foo.count = 0;
var i;
for(i = 0;i <10; i++) {
    if(i > 5) {
        foo(i);
    }
}
console.log(foo.count);

foo: 6
foo: 7
foo: 8
foo: 9
0

执行foo.count = 0,的确向函数随想foo添加了一个属性count,但是函数内部代码this.count中的this并不是指向那个函数对象;虽然属性相同,但是根对象却并不相同
无意中创建了一个全局变量count,它的值我NaN

this是在运行时进行绑定的,而不是在编写时进行绑定,它的上下文取决于函数调用时的各种条件,this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式;

当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文).这个记录会包含函数在哪里被调用(调用栈),函数的调用方法,传入的参数等信息.this就是记录的其中一个属性,会在函数执行过程中用到

调用栈

调用栈是解析器(如浏览器中的javascript解析器)的一种机制,可以在脚本调用多个函数时,跟踪每个函数在完成执行时应该返回控制的点(如什么函数正在执行,什么函数被这个函数调用, 下一个调用的函数是谁)

当脚本要调用一个函数时,解析器把该函数添加到栈中并且执行这个函数。
任何被这个函数调用的函数会进一步添加到调用栈中,并且运行到它们被上个程序调用的位置。
当函数运行结束后,解释器将它从堆栈中取出,并在主代码列表中继续执行代码。
如果栈占用的空间比分配给它的空间还大,那么则会导致“堆栈溢出”错误。
调用栈

this的隐式赋值

function foo() {
    console.log(this.a);
}
var obj = {
    a: 2,
    foo: foo,
};
var bar = obj.foo;
var a = 'oops, global';
bar(); // oops, global

虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,因为此时的bar()其实是一个不带任何参数的函数调用,因此应用了默认绑定;

function foo() {
    console.log(this.a);
}
function doFoo(fn) {
    fn();
}
var obj = {
    a: 2,
    foo: foo,
};
var a = 'oops, global';
doFoo(obj.foo); // oops, global

参数传递其实就是一种隐式赋值,因为我们传入函数时也会被隐式赋值

function foo() {
    console.log(this.a);
}
function doFoo(fn) {
    fn();
}
var obj = {
    a: 2,
    foo: foo,
};
var a = 'oops, global';
setTimeout(obj.foo, 100)

JavaScript环境内置的setTimeout()函数实现和下面的伪代码类似

function setTimeout(fn, delay) {
    // 等待delay毫秒
    fn(); // <-- 调用位置
}

回调函数丢失this是非常常见的;
调用回调函数的函数可能会修改this;一些流行的javascript库中事件处理器常会把回调函数的this强制绑定到触发事件的DOM元素
你无法控制回调函数的执行方式,因此就没有办法控制会影响绑定的调用位置;如何通过固定this来修复

显式绑定

函数的call(..)和apply(..)方法
它们的第一个参数是一个对象,它们会把这个对象绑定到this,接着在调用函数时指定这个this,因此你可以直接指定this的绑定对象
如果你传入一个原始值(字符串类型, 布尔类型或者数字类型)来当做this的绑定对象,这个原始值会被转换成它的对象形式(也就是 new String(..), new Boolean(..) 或者 new Number(..));

function foo() {
    console.log(this.a);
}
var obj = {
    a: 2,
};
var bar = function () {
    foo.call(obj);
};
bar(); // 2
setTimeout(bar, 100); //2
bar.call(window); //2

bar()内部手动调用foo.call(obj), 因此强制把foo的this绑定到了obj, 无论之后怎么调用函数bar,它总会手动在obj上调用foo

被忽略的this

如果你把null或者undefined作为this的绑定对象传入call, apply或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则;
什么情况下你会传入 null 呢
一种非常常见的做法是使用apply(..)来'展开'一个数组,并当做参数传入一个函数,类似的,bind(..)可以对参数进行柯里化

function foo(a, b) {
    console.log("a:" + a + ", b:" + b);
}
// 将数组展开成参数
foo.apply(null, [2, 3]);
// 使用bind(..)进行柯里化
var bar = foo.bind(null, 2);
bar(3);

这两种都需要传入一个参数当做this的绑定对象,如果函数不关心this的话,你仍然需要传入一个占位符,这时null 可能是个不错的选择

但是使用null来忽略this绑定可能禅城一些副作用,如果某个函数确实使用了this(比如第三方库中的一个函数),那默认绑定规则会把this绑定到全局对象(在浏览器中这个对象是window),这将导致不可预计的后果(比如修改全局对象);
Object.create(null){}很像,但是并不会创建Object.prototype这个委托,所以它比{}更空

var ø = Object.create(null);
foo.bind(ø, 2);

间接引用

最容易在赋值时发生

function foo() {
    console.log(this.a);
}
var a = 2;
var o = {
    a: 3,
    foo: foo,
};
var p = {
    a: 4,
};
o.foo(); // 3
(p.foo = o.foo)(); // 2

赋值表达式p.foo = o.foo的返回值是目标函数的引用,因此调用位置是foo()而不是p.foo()或者o.foo();这里会应用默认绑定

对于默认绑定来说,决定this绑定对象的并不是调用位置是否处于严格模式,而是函数体是否属于严格模式,如果函数体处于严格模式,this被绑定到undefined,否则this会被绑定到全局对象

箭头函数的this

function foo() {
    return (a) => {
        // this继承自foo
        console.log(this.a);
    }
}
var obj1 = {
    a: 2,
}
var obj2 = {
    a: 3,
}
var bar = foo.call(obj1);
bar.call(obj2); // 2,不是3!

foo()内部创建的箭头函数会捕获调用时 foo()的this.由于foo()的this绑定到obj1, bar(引用箭头函数),的this也会绑定到obj1,
箭头函数的绑定无法被修改(new也不行)

对象

类型

null

console.log(typeof null);  // object

原理: 不同的对象在底层都表示为二进制,而javascript二进制前三位都为0的话会被判断为object类型,null的二进制表示是全0,自然前三位也是0, 所以执行typeof 时会返回object
函数值对象的一个子类型(从技术角度来说就是'可调用的对象')

内置对象

  • String
  • Number
  • Boolean
  • Object
  • Function
  • Array
  • Date
  • RegExp
  • Error
var strObject = new String('I am a string');
Object.prototype.toString.call(strObject); // [Object String]

原始值I am a string 并不是一个对象,它只是一个字面量,而且是一个不可变的值,如果要在这个字面量上执行一些操作,比如获取
长度,访问其中某个字符,那就需要将其转换为String对象;

var strObject = 'I am a string';
console.log(strObject.length);
console.log(strObject.charAt(3));

引擎自动将字面量转换成String对象,所以可以访问属性和方法;类似 1.2345.toFixed(2)的方法,引擎会把1.2345转换为new Number(1.2345);
null和undefined没有对象的构造形式,只有文字形式;Date只有构造形式,没有文字形式;
Object,Array,Function, RegExp,无论使用文字形式还是构造形式,他们都是对象,不是字面量;

内容

.操作符和[]操作符;.a语法为'属性访问', ['a]语法是'键访问';区别['..']可以接受任意UTF-8/Unicode字符串作为属性名;
在对象中,属性名永远都是字符串

1.可计算属性名
[]包裹一个表达式来当做属性名

存在性

in操作符会检查属性是否在对象及其[[prototype]]原型链中, hasOwnProperty(..)只会检查属性是否在myObject对象中,不会检查[[protoType]]链;
普通对象都可以对于 Object.prototype的委托来访问 hasOwnProperty(..),但是有的对象可能没有连接到 Obeject.prototype(Object.create(null)),这种可以使用 Object.prototype.hasOwnProperty.call(myObject, 'a')

in操作符是检查容器内是否有某个值,实际上检查的是某个属性名是否存在,对于数组来说这个区别很重要, 4 in [2, 4 ,6]的结果并不是你所期待的true, 因为[2, 4, 6]这个数组中包含的属性名为 0, 1, 2, 没有4

for in 遍历对象是无法获取属性值的,它实际遍历的是对象中所有可枚举属性,需要手动获取属性值;
且遍历对象属性时的顺序是不确定的;

for of

for of 循环首先会向被访问对象请求一个迭代器对象,然后通过调用迭代器对象的next()方法来遍历所有值;
遍历数组

var myObject = {
    a: 2,
    b: 3,
};

Object.defineProperty(myObject, Symbol.iterator, {
    enumerable: false,
    writable: false,
    configurable: true,
    value: function () {
        var o = this;
        var idx = 0;
        var ks = Object.keys(o);
        return {
            next: function() {
                return {
                    value: o[ks[idx++]],
                    done: (idx > ks.length),
                }
            }
        }
    }
})
var it = myObject[Symbol.iterator]();
console.log(it.next());
console.log(it.next());
console.log(it.next());

/*
{ value: 2, done: false }
{ value: 3, done: false }
{ value: undefined, done: true }
*/

cookie

说一说cookie

设置cookie => cookie被自动添加到request header中 => 服务端接收到cookie

cookie怎么工作的

当网页要发送http请求时, 浏览器会先检查是否有相应的cookie, 有则自动添加request header字段中.这些事浏览器自动帮我们做的, 而且每一次http请求浏览器都会自动帮我们做;
每个域名下的cookie的大小最大为4kb, 每个域的cookie数量最多为20个

cookie的格式

如何设置cookie

cookie既可以由服务端设置, 也可以由客户端来设置

服务端设置cookie

不管你是请求一个资源文件(如html/js/css/图片), 还是发送一个ajax请求, 服务端都会返回response.而response header中有一项叫set-cookie, 是服务端专门用来设置cookie的;

  • 一个set-cookie只能设置一个cookie, 当你想设置多个, 需要添加同样多的set-cookie
  • 服务端可以设置cookie的所有选项: expires, domain, path, secure, HttpOnly

客户端设置cookie

document.cookie = "name=xiaoming; age=12 "
  • 客户端可以设置cookie的一下选项: expires, domain, path, secure(只有在https协议的网页中, 客户端设置secure类型cookie才能生效), 但无法设置httpOnly选项

cookie的属性

domain表示cookie所在的域

a.com默认的domain是a.com, test.a.com和test1.a.com访问都会有跨域问题, 如果想一个cookie所有以"a.com"结尾的域名中都可以访问, 则需要设置domain为".a.com";

domain可以访问该Cookie的域名。如果设置为“.baidu.com”,则所有以“baidu.com”结尾的域名都可以访问该Cookie;第一个字符必须为“.”

path表示cookie的使用路径

isHttpOnly

cookie和session的区别

cookie保存在客户端, session保存在服务端
常用的是sessionId存在cookie中

锚点的添加

锚点的添加

1.使用 id 定位

这种定位最大的好处就是可以针对任何标签来定位
给一个a标签一个href=“#锚点”,然后要跳的锚点给个id=“锚点”

<!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>锚点</title>
    <style>
        #div1 {
            margin-top: 2000px;
        }
    </style>
</head>
<body>
    <h2>
        <a href="#div1">to div1</a>
    </h2>
    <div id="div1">div1</div>
</body>
</html>

2.使用 name 定位

使用 name 定位只用于 <a>标签,其他的标签就不管用了。

<!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>锚点</title>
    <style>
        a[name="a"]{
            margin-top: 2000px;
            display: block;
        }
    </style>
</head>
<body>
    <h2>
        <a href="#a">定位到A</a>
    </h2>
    <a name="a">A</a> 
</body>
</html>

3.使用js定位

element.scrollIntoView()方法让当前元素滚动到浏览器窗口的可视区域内(有浏览器的兼容问题)

<!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>锚点</title>
    <style>
        #a {
            margin-top: 2000px;
        }
    </style>
</head>
<body>
    <div onclick="javascript:document.getElementById('a').scrollIntoView()">
        定位到A
    </div>
    <div id="a">
        A
    </div>
</body>
</html>

Webpack-dev-server的proxy用法

前言

  1. 如果你有单独的后端开发服务器 API,并且希望在同域名下发送 API 请求 ,那么代理某些 URL 会很有用。
  2. 解决开发环境的跨域问题(不用在去配置nginx和host, 爽歪歪~~)

webpack.config.js中配置

下面简单介绍一下五个经常使用的场景

使用一:

mmodule.exports = {
    //...
    devServer: {
        proxy: {
            '/api': 'http://localhost:3000'
        }
    }
};

请求到 /api/xxx 现在会被代理到请求 http://localhost:3000/api/xxx, 例如 /api/user 现在会被代理到请求 http://localhost:3000/api/user

使用二

如果你想要代码多个路径代理到同一个target下, 你可以使用由一个或多个「具有 context 属性的对象」构成的数组:

module.exports = {
    //...
    devServer: {
        proxy: [{
            context: ['/auth', '/api'],
            target: 'http://localhost:3000',
        }]
    }
};

使用三:

如果你不想始终传递 /api ,则需要重写路径:

module.exports = {
    //...
    devServer: {
        proxy: {
            '/api': {
                target: 'http://localhost:3000',
                pathRewrite: {'^/api' : ''}
            }
        }
    }
};

请求到 /api/xxx 现在会被代理到请求 http://localhost:3000/xxx, 例如 /api/user 现在会被代理到请求 http://localhost:3000/user

使用四:

默认情况下,不接受运行在 HTTPS 上,且使用了无效证书的后端服务器。如果你想要接受,只要设置 secure: false 就行。修改配置如下:

module.exports = {
    //...
    devServer: {
        proxy: {
            '/api': {
                target: 'https://other-server.example.com',
                secure: false
            }
        }
    }
};

使用五:

有时你不想代理所有的请求。可以基于一个函数的返回值绕过代理。
在函数中你可以访问请求体、响应体和代理选项。必须返回 false 或路径,来跳过代理请求。

例如:对于浏览器请求,你想要提供一个 HTML 页面,但是对于 API 请求则保持代理。你可以这样做:

module.exports = {
  //...
    devServer: {
        proxy: {
            '/api': {
                target: 'http://localhost:3000',
                bypass: function(req, res, proxyOptions) {
                    if (req.headers.accept.indexOf('html') !== -1) {
                        console.log('Skipping proxy for browser request.');
                        return '/index.html';
                    }
                }
            }
        }
    }   
};

解决跨域原理

上面的参数列表中有一个changeOrigin参数, 是一个布尔值, 设置为true, 本地就会虚拟一个服务器接收你的请求并代你发送该请求,

module.exports = {
    //...
    devServer: {
        proxy: {
            '/api': {
                target: 'http://localhost:3000',
                changeOrigin: true,
            }
        }
    }
};

vue-cli中proxyTable配置接口地址代理示例

修改 config/index.js

module.exports = {
    dev: {
    // 静态资源文件夹
    assetsSubDirectory: 'static',
    // 发布路径
    assetsPublicPath: '/',

    // 代理配置表,在这里可以配置特定的请求代理到对应的API接口
    // 使用方法:https://vuejs-templates.github.io/webpack/proxy.html
    proxyTable: {
        // 例如将'localhost:8080/api/xxx'代理到'https://wangyaxing.cn/api/xxx'
        '/api': {
            target: 'https://wangyaxing.cn', // 接口的域名
            secure: false,  // 如果是https接口,需要配置这个参数
            changeOrigin: true, // 如果接口跨域,需要进行这个参数配置
        },
        // 例如将'localhost:8080/img/xxx'代理到'https://cdn.wangyaxing.cn/xxx'
        '/img': {
            target: 'https://cdn.wangyaxing.cn', // 接口的域名
            secure: false,  // 如果是https接口,需要配置这个参数
            changeOrigin: true, // 如果接口跨域,需要进行这个参数配置
            pathRewrite: {'^/img': ''}  // pathRewrite 来重写地址,将前缀 '/api' 转为 '/'。
        }
    },
    // Various Dev Server settings
    host: 'localhost', // can be overwritten by process.env.HOST
    port: 4200, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
}

更多参数

dev-server 使用了非常强大的 http-proxy-middleware , http-proxy-middleware 基于 http-proxy 实现的,可以查看 http-proxy 的源码和文档:https://github.com/nodejitsu/node-http-proxy

target:要使用url模块解析的url字符串
forward:要使用url模块解析的url字符串
agent:要传递给http(s).request的对象(请参阅Node的https代理和http代理对象)
ssl:要传递给https.createServer()的对象
ws:true / false,是否代理websockets
xfwd:true / false,添加x-forward标头
secure:true / false,是否验证SSL Certs
toProxy:true / false,传递绝对URL作为路径(对代理代理很有用)
prependPath:true / false,默认值:true - 指定是否要将目标的路径添加到代理路径
ignorePath:true / false,默认值:false - 指定是否要忽略传入请求的代理路径(注意:如果需要,您必须附加/手动)。
localAddress:要为传出连接绑定的本地接口字符串
changeOrigin:true / false,默认值:false - 将主机标头的原点更改为目标URL

参考

微信小程序开发之安卓手机上数据不显示

总结一下小程序的坑

1,遇到安卓上数据显示不出来,但是ios和安卓上显示不出来

  • 上网查了一下,参考1参考2,
  • 可能原因如下:
  • 1.域名配置问题,证书信任问题
  • 2.es6在安卓上有的不支持,即使开启es6转es5的语法,安卓上也不支持
  • ...
  • 问题一,问了后端,他们说检查好几遍了,不是这儿的问题,所以排除,
  • 问题二,查了一下小程序中es6语法的支持把用到的不支持的语法改了(可以用webpack配置babel,但是发现此问题时已经打算提交审核,时间紧迫,所以先把不支持的语法改了),还是不行,排除问题2;
    后来就冷静的思考了一下,为什么安卓上没有数据,请求数据前的步骤是否走通,然后一步一个log的看,我们这里的登录逻辑是先有个get请求,后端返回true或false来判断是都需要登录,判断为true时需要获取返回信息头部的session字段,然后之后每次请求头部带上这个字段,
function getStorage(key) {
    return new Promise(function (resolve, reject) {
        // 先判断本地数据存储有没有cookie
        wx.getStorage({
            key: key,
            success: function (res) {
                resolve(res.data);
            },
            fail: function (res) {
                console.log(res);
                resolve(null);
                // if (res.errMsg == 'getStorage:fail' || res.errMsg == 'getStorage:fail data not found') {
                    <!--安卓返回 getStorage:fail, ios 返回 res.errMsg == 'getStorage:fail data not found'-->
                //     console.log('没有cookie');
                //     resolve(null);
                // } else {
                //     console.log('这是一个问题');
                //     reject(res.errMsg);
                // }
            },
        });
    });
}
  • 判断本地缓存没有 session的时候时请求的第一个登录接口,安卓返回 getStorage:fail, ios 返回 res.errMsg == 'getStorage:fail data not found',刚开始只判断了res.errMsg == 'getStorage:fail data not found',所以安卓上会一直走reject,最后错误被catch出来,而并没有请求接口,导致没有数据
  • 总结: 微信小程序开发过程中一定要用真机测试,微信开发者工具和真机上很多地方都有出入

求数组arr中的最大值

求数组arr中的最大的值

方法一: 0.072s

var arr = [[1, 2, ,3], 4, -1, [11, 22, 33, 45, [7, 123, 1]], 98, [[123]]];
function maxOfArr() {
    var targetArr = [];
    // forEach为es5的数组迭代方法
    // 展开多维数组为一维数组的函数,递归调用
    var fnExpandArray = function (arr) {
        arr.forEach(function(item) {
            if (Array.isArray(item)) {
                fnExpandArray(item);
            } else {
                targetArr.push(item);
            }
        });
    }
    fnExpandArray(arr);
    // console.log(targetArr);

    // 求出最大值得函数,假设第一个值为最大值,并且与后面的值进行比较
    var fnMax = function (arr) {
        var max = arr[0];
        arr.forEach( function (item) {
            if(item > max) {
                max = item;
            }
        })
        return max;
    }
console.log('最大值是' + fnMax(targetArr));
}
maxOfArr();

方法二: 0.068s

 function maxAndMin(arr) {
   return {
        max: Math.max.apply(null, arr.join(',').split(',')),
        min: Math.min.apply(null, arr.join(',').split(',')),
     }
 }
 console.log(maxAndMin([[1, 2, ,3], 4, -1, [11, 22, 33, 45, [7, 123, 1]], 98, [[123]]]));

微信小程序接口异步问题

####1,小程序接口异步问题

  • 没有使用别的框架,单纯的按照微信小程序的官方文档来写,所以并不能用es7的 async函数和await,只能用目前小程序所支持的promise,但是对每一个封装有很麻烦
  • 解决方案:写一个转换函数,将小程序的所有api转换成promise函数
const wx = {
    fun1: () => console.log(1),
    fun2: ({a, b, success, fail}) => {
        if (a + b > 10) {
            fail('error: a + b > 10');
            return;
        }
        success(a + b);
    },
};
function promiseify(func) {
    return (args) => {
        return new Promise((resolve, reject) => {
            func(Object.assign(args, {
                success: resolve,
                fail: reject,
            }));
        })
    }
}
for (const key in wx) {
    if (Object.prototype.hasOwnProperty.call(wx, key) && typeof wx[key] === 'function') {
        wx[`_${key}`] = promiseify(wx[key]);
    }
}
wx._fun2({a: 3, b: 4}).then(sum => {
    console.log(sum);
}).catch(err => {
    console.log(err);
});

wx._fun2({a: 5, b: 6}).then(sum => {
    console.log(sum);
}).catch(err => {
    console.log(err);
});

作用域和闭包

javascript是一门编译语言,不是提前编译,边编译边执行;'动态'或解释执行

编译
1、分词/词法分析
2.解析/语法分析
AST(抽象语法树): Abstract Syntax Tree ?
3.代码生成
AST转换成可执行代码

引擎?,编译器 ?,作用域
LHS
RHS

LHS, RHS

LHS: 赋值操作的目标;
a = 2

RHS: 谁是赋值操作的源头; 得到某某的值
console.log(a);
去找到a的值,并把它给我

function foo(a) {
    console.log(a);
}
foo(2);

a = 2; LHS查询, 2会被分配给参数a

对a的RHS引用,并且经得到的值传给 console.log(..);console.log(...)本身也需要一个引用才能执行,因此会对console对象进行RHS查询,并且检查得到的值中是否有一个叫log的方法

异常

在变量还未声明的情况下,两种查询的行为是不一样的;
RHS查询不到,引擎报 ReferenceError异常
LHS查询不到,(非严格模式下)全局作用域就会创建一个具有该名称的变量,并将其返还给引擎

RHS查询找到, 尝试对变量进行不合理的操作,试图
1.对一个非函数类型的值进行函数调用;
2.引用null和undefined类型的值中的属性,引擎会抛出 TypeError

ReferenceError同作用域判别失败有关,TypeError代表作用域判别失败了,但是岁结果的操作是非法或不合理的

小结

如果查找的目的是对变量进行赋值,就会使用LHS操作,如果目的是获取变量的值,就会使用RHS查询
赋值操作符会导致LHS查询. = 操作符或调用函数时传入的参数操作都会导致关联作用域的赋值操作

function foo(a) {
    var b = a;
    return a + b;
}
var c = foo(2);

3处LHS查询

c = ..; a = 2(隐式变量分配); b = ..

4处RHS查询

foo(2.., = a; a.. ..b

词法作用域

词法作用域以为这作用域是由书写代码函数声明的位置来决定的;编译的词法分析阶段基本能够知道全部标识在哪里以及是如何声明的,从而能够预测在执行过程中如何对它们进行查找;

javascript中有两个机制可以'欺骗'词法作用域: eval(..)和with;前者可以对一段包含或多个声明'代码'字符串进行演算,并借此来修改已经存在的的词法作用域(运行时);后者本质上是通过讲一个对象的引用当做作用域来处理,将对象的属性当做作用域中的标识来处理,从而创建了一个新的词法作用域(同样是在运行时);
这两个机制的副作用是引擎无法再编译时对作用域查找进行优化;因此引擎只能谨慎地认为这样的优化是无效的.使用这其中任何一个机制都将导致代码运行变慢,不要使用它们;

函数作用域和块级作用域

function是声明的第一个词,就是一个函数声明,否则就是函数表达式;
函数表达式可以是匿名的(匿名函数表达式),函数声明则不可以省略函数名

(function foo() { .. })(); => IIFE 立即执行函数表达式
第一个()将函数变成表达式,第二个()执行了这个函数;
(function foo() { .. }());

提升

引擎会在解释javascript代码之前首先对其进行编译,编译阶段中的一部分工作就是找到所有的声明,并用合适的作用域将他们关联起来
包含变量和函数在内的所有声明都会在任何代码前首先被处理;可以形象的想象成所有的声明(变量和函数)都会被'移动'到各自作用域的最顶端,这个过程被称为提升

var a = 2;
javascript实际上会将其看成两个声明: var a; 和a = 2;
第一个定义声明是在编译阶段进行的;第二个赋值声明会被留在原地等待执行阶段

a = 2;
var a;
console.log(a);

其中一部分是编译,而第二部分是执行;

console.log(a);
var a = 2;

实际按照以下流程处理

var a;
console.log(a);
a = 2;

只有变量声明和函数声明会被提升,但是函数表达式却不会被提升

foo(); // 不是ReferenceError, 而是TypeError
bar(); // ReferenceError
var foo = function bar() {

}

变量标识符foo()被提升并分配给所在作用域(在这里是全局作用域),因此foo()不会导致ReferenceError;
但是foo此时并没有赋值(如果它是一个函数声明而不是函数表达式,name就会赋值). foo()由于对undefined值进行函数调用而导致非法操作,因此跑出TypeError

函数声明和变量声明都会被提升,但是有一个值得注意的细节(这个细节可以出现在有多个'重复'声明的代码中)但是函数会首先被提升,然后才是变量,但是出现在后面的函数声明还是可以覆盖前面的

闭包

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使是在当期词法作用域之外执行的

function foo() {
    var a = 2;
    function bar() {
        console.log(a);
    }
    return bar;
}
var baz = foo();
baz();

函数bar()的作用域能够访问foo()的内部作用于,然后我们将bar()函数本身当做一个值类型进行传递;在这个例子中,我们将bar所引用的函数对象本身当做返回值;
在foo()执行后,其返回值(也就是内部bar()函数)赋值给变量baz并调用baz();实际上只是通过不同的标识符引用调用了内部函数bar();

? 
function wait(message) {
    setTimeout(function timer() {
        console.log(message);
    }, 1000)
};
wait('hello, closure')

将一个内部函数(timer)传递给setTimeout(..), timer具有涵盖wait(..)作用域的闭包,因此还保有对变量message的引用

?
function makeAdder(x) {
    function add(y) {
        return y + x;
    }
    return add;
}
var plusOne = makeAdder(1);
var plusTen = makeAdder(10);

console.log(plusOne(3)); // 4
console.log(plusTen(41)); // 51
console.log(plusTen(13)); // 23

本质上无论何时何地,如果将函数(访问它们各自的词法作用域)当做第一级的值类型并到处传递, 你就会看到闭包在这些函数中的应用;
在定时器,事件监听器,ajax请求, 跨窗口通信, Web Workers或者任何其他的异步(或者同步)任务中,只要使用了回调函数,实际上就是使用了闭包

循环和闭包

for(var i = 1; i <= 5; i++) {
    setTimeout(function timer() {
        console.log(i);
    }, i*1000)
}

运行时会以每秒一次的频率输出5次6
为什么?
1.6是哪里来的,这个循环的终止条件是i不再<=5;条件首次成立时i的值是6; 因此,输出显示的是循环结束时i的终值
2.延时函数的回调会在循环结束时才执行
我们试图假设循环中的每个迭代在运行时都会给自己'捕获'一个i的副本,但是根据作用域的工作原理,实际情况是尽管循环中的五个函数是在个个迭代中分别定义的,但是他们都被封闭在一个共享的全局作用域,因此实际上只有一个i

for(var i = 1; i <= 5; i++) {
    (function (j) {
        setTimeout(function timer() {
            console.log(j);
        }, j*1000)
    })(i)
    
}

在迭代内使用IIFE会为每个迭代都生成一个新的作用域,使得延迟函数的回调可以将新的作用域封闭在每个迭代内部,每个迭代中都会含有一个具有正确值的变量供我们访问;

模块

function CoolModule() {
    var something = 'cool';
    var another = [1, 2, 3];
    function doSomething() {
        console.log(something);
    }
    function doAnother() {
        console.log(another.join("!"));
    }
    return {
        doSomething: doSomething,
        doAnother: doAnother,
    }
}
var foo = CoolModule();
foo.doSomething();
foo.doAnother();

模块模式需要具备两个必要条件
1.必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例);
2.封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,而且可以访问或者修改私有的状态

模块

var MyModules = (function Manager() {
    var modules = {};
    function define(name, deps, impl) {
        for(var i = 0; i < deps.length;i++) {
            deps[i] = modules[deps[i]];
        }
        modules[name] = impl.apply(impl, deps);
    }
    function get(name) {
        return modules[name];
    }
    return {
        define: define,
        get: get,
    }
})();

MyModules.define('bar', [], function() {
    return hello(who) {
        return 'let me introduce: ' + who;
    }
    return {
        hello: hello,
    }
});

MyModules.define('foo', ['bar'], function(bar) {
    var hunhry = 'hippo';
    function awesome() {
        console.log(bar.hello(hungry).toUpperCase());
    }
    return {
        awesome: awesome
    }
});
var bar = MyModules.get('bar');
var foo = MyModules.get('foo');
console.log(bar.hello('hippo'));
foo.awesome();

动态作用域

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

JavaScript编码风格指南

前言

程序语言的编码风格指南对于一个长期维护的软件而言是非常重要的;
团队合作需要制定一些代码规范还有利用一些工具来强制要求团队代码的风格统一.毕竟很多情况下以后不一定是由写一手代码的人来维护代码,所以有一个统一的代码风格很重要!!!
最近看了一下编写可维护的JavaScript编写高质量代码:Web前端开发修炼之道,根据书中提倡的一些写法,同时结合我个人的经验和喜好做了一些改动,大致整理了如下JavaScript编码风格

JavaScript编码风格

1.缩进

每一行的层级由4个空格组成,避免使用制表符(Tab)进行缩进

if (true) {
    doSomething();
}

2.行的长度

每行长度不应该超过80个字符.如果一行多于80个字符,应当在一个运算符(逗号,加好等)后换行.下一级应当增加两级缩进(8个字符).

// 好的写法
doSomething(arg1, arg2, arg3, arg4,
        arg5);

// 不好的写法: 第二行只有4个空格的缩进
doSomething(arg1, arg2, arg3, arg4,
    arg5);
// 不好的写法: 在运算符之前换行
doSomething(arg1, arg2, arg3, arg4
        ,arg5);

3.原始值

特殊值null除了下述情况应当避免使用

  • 用来初始化一个变量,这个变量可能被赋值为一个对象
  • 用来和一个已经初始化的变量比较,这个变量可以是也可以不是一个对象
  • 当函数的参数期望是对象时,被用作返回值传出
// 好的做法
var person = null;

判断一个变量是否定义应当使用 typeof 操作符

// 好的写法
if (typeof variable == 'undefined') {
    // do something
}

// 不好的写法
if (variable == 'undefined') {
    // do something
}

4.运算符间距

二元运算符前后必须使用一个空格来保持表达式的整洁.操作符包括赋值运算符和逻辑运算符

// 好的写法
var found = (values[i] === item);
// 不好的写法: 丢失了空格
var found = (values[i]===item);

// 好的写法
if (found && (count > 10)) {
    doSomething();
}
// 不好的写法: 丢失了空格
if (found&&(count>10)) {
    doSomething();
}

// 好的写法
for(let i = 0; i < count; i++) {
    process(i);
}
// 不好的写法: 丢失了空格
for(let i=0; i<count; i++) {
    process(i);
}

5.括号间距

当使用括号时,紧接左括号之后和紧接右括号之前不应该有空格

// 好的写法
var found = (values[i] === item);
// 不好的写法: 左括号之后有额外的空格
var found = ( values[i] === item);

// 好的写法
if (found && (count > 10)) {
    doSomething();
}
// 不好的写法: 右括号之后有额外的空格
if (found && (count > 10) ) {
    doSomething();
}

// 好的写法
for(let i = 0; i < count; i++) {
    process(i);
}
// 不好的写法: 参数两边有额外的空格
for(let i = 0; i< count; i++) {
    process( i );
}

6.对象直接量

对象直接量应当使用如下格式

  • 起始左花括号应当同表达式保持同一行
  • 每个属性的名值对应当保持一个缩进,第一个属性应当在左花括号后另起一行.
  • 每个属性的名值对应当使用不含引号的属性名,其后紧跟一个冒号(之前不含空格),而后是值
  • 倘若属性值是函数类型,函数体应当在属性名之下另起一行,而且其前后均应保留一个空行
  • 一组相关的属性前后可以插入空行以提高代码的可读性
  • 结束的右花括号应当独占一行
// 好的写法
var object = {

    key1: value1,
    key2: value2,

    func: function() {

    },

    key3: value3,
};

// 不好的写法: 不恰当的缩进
var object = {
        key1: value1,
        key2: value2,
    };

// 不好的写法:函数体缺少空行
var object = {

    key1: value1,
    key2: value2,
    func: function() {

    },
    key3: value3,
};

当对象字面量作为函数参数时,如果值是变量,起始花括号应当同函数名在同一行.所有其余先前列出的规则同样适用

// 好的写法
doSomething({
    key1: value1,
    key2: value2,
});

// 不好的写法
doSomething({ key1: value1, key2: value2 });

7.注释

频繁地适用注释有助于他人理解你的代码.如下情况应当使用注释

  • 代码晦涩难懂
  • 可能被误认为错误的代码
  • 必要但不明显的针对特定浏览器的代码
  • 对于对象,方法或者属性,生成文档是有必要的(使用恰当的文档注释).

1).单行注释

使用单行注释当用来说明一行代码或者一组代码.单行注释可能有三种使用方式

  • 独占一行的注释,用来解释下一行代码
  • 在代码行的尾部的注释,用来解释它之前的代码
  • 多行,用来注释掉一个代码块
// 好的写法
if (condition) {

    // 如果代码执行到这里,则说明通过了所有的安全性检测
    allowed();
}

// 不好的写法:注释之前没有空行
if (condition) {
    // 如果代码执行到这里,则说明通过了所有的安全性检测
    allowed();
}

// 不好的写法: 错误的缩进
if (condition) {

// 如果代码执行到这里,则说明通过了所有的安全性检测
    allowed();
}

// 不好的写法: 这里应当用多行注释
// 接下来的这段代码非常难, 那么,让我详细的解释一下
// 1. xxxx
// 2. xxxx
if (condition) {

// 如果代码执行到这里,则说明通过了所有的安全性检测
    allowed();
}

对于代码行尾单行注释的情况,应确保代码结尾同注释之间至少一个缩进

// 好的写法
var result = something + somethingElse; // somethingElse will never be null

// 不好的写法: 代码和注释间没有足够的空格
var result = something + somethingElse;// somethingElse will never be null

注释一个代码块时在连续多行使用单行注释是唯一可以接受的情况.多行注释不应当在这种情况下使用

// 好的写法
// if(condition) {
//     doSomething();
// }

2).多行注释

多行注释应当在代码需要更多文字去解释的时候使用.每个多行注释都至少有如下三行.
1.首行仅仅包括 /* 注释开始.该行不应当有其他文字
2.接下来的行以 * 开头并保持左对齐.这些行可以由文字描述
3.最后一行以 */开头并同先前行保持对齐.也不应当有其他文字

多行注释的首行应当保持同它描述代码的相同层次的缩进.后续的每行应当有同样层次的缩进并附加一个空格(为了适当保持 * 字符的对齐).每一个多行代码之前应当预留一个空格

// 好的写法
if (condition) {

    /*
     * 如果代码执行到这里
     * 说明通过了所有的安全性检测
    */
    allowed();
}

// 不好的写法: 注释之前无空行
if (condition) {
    /*
     * 如果代码执行到这里
     * 说明通过了所有的安全性检测
    */
    allowed();
}
// 不好的写法: 星号后没有空格
if (condition) {

    /*
     *如果代码执行到这里
     *说明通过了所有的安全性检测
    */
    allowed();
}
// 不好的写法: 错误的缩进
if (condition) {

/*
 * 如果代码执行到这里
 * 说明通过了所有的安全性检测
*/
    allowed();
}

// 不好的写法: 代码尾部注释不要用多行注释格式
var result = something + somethingElse; /* somethingElse 不应当取值为null */

3)注释声明

注释有时候可以用来给一段代码声明额外的信息.这些声明的格式以单个单词打头并紧跟一个双引号.可使用的声明如下

  • TODO: 说明代码还未完成.应当包含下一步要做的事情
  • HACK: 表明代码实现走了一个捷径
  • XXX: 说明代码是有问题的并应当尽快修复
  • FIXME: 说明代码是有问题的并应当尽快修复.重要性略次于XXX
  • REVIEW: 说明代码任何可能的改动都需要评审

8.命名

变量命名应当采用驼峰命名格式,首字母小写,每个单词首字母大写.变量名的第一个单词应当是一个名词(而非动词)比避免同函数混淆.不要在变量名中使用下划线

// 好的写法
var myName = 'Jack';

// 不好的写法: 大写字母开头
var MyName = 'Jack';

// 不好的写法: 动词开头
var getMyName = 'Jack';

// 不好的写法: 使用下划线
var my_name = 'Jack';

函数命名应当采用驼峰命名格式.函数名的第一个单词应当是动词(而非名词)来避免同变量混淆.函数名中最好不要使用下划线.

// 好的写法
function doSomething() {
    // 代码
}

// 不好的写法: 大写字母开头
function DoSomething() {
    // 代码
}
// 不好的写法: 名词开头
function car() {
    // 代码
}
// 不好的写法: 使用下划线
function do_something() {
    // 代码
}

构造函数--通过new元素安抚创建新对象的函数--也应使用驼峰合适命名,首先首字母大写.构造函数命名应当以非动词开头,因为new代表着创建一个对象实例的操作

// 好的写法
function MyObject() {

}
// 不好的写法: 小写字母开头
function myObject() {
    
}
// 不好的写法: 使用下划线
function My_Object() {
    
}
// 不好的写法: 动词开头
function getMyObject() {
    
}

常量(不会被改变的变量)的命名应当是所有字母大写,不同单词之间用单个下划线隔开

// 好的写法
var TOTAL_COUNT = 10;
// 不好的写法
var totalCount = 10;
// 不好的写法: 混合模式
var total_COUNT = 10;

对象的属性同变量的命名规范相同.对象的方法同函数的命名规则相同.如果属性或者方法是私有的,应当在之前加一个下划线

// 好的写法
var object = {
    _count: 10,

    _getCount: function() {
        return this._count;
    }
}

8.赋值

当给变量赋值时,如果右侧是含有比较语句的表达式,需要用圆括号包裹

// 好的写法
var flag = (i < count);

// 不好的写法:遗漏圆括号
var flag = i < count;

9.等号运算符

使用 === (严格相等) 和 !==(严格不相等)代替 ==(相等) 和 !=(不等) 来避免弱类型转换错误

// 好的写法
var same = (a === b);

// 不好的写法: 使用 == 
var same = (a == b);

10.三元操作符

三元运算符应当仅仅用在条件赋值语句中,而不要作为if语句的替代品.

// 好的写法
var value = condition ? value1 : value2;

// 不好的写法: 没有赋值,应当使用 if 表达式
condition ? doSomething() : doSomethingElse();

11.语句

简单语句

每一行最多只包含一条语句.所有简单的语句都应该以分号(;)结束.

// 好的写法
var a = 1;
var b = 2;
var c = a + b;

// 不好的写法: 多个表达式写在一行
var a = 1;var b = 2;var c = a + b;

返回语句

返回语句当返回一个值的时候不应当使用圆括号包裹,除非在某些情况下这么做可以让返回值更容易理解.例如:

return;
return collection.size();
return (size > 0 ? size : defaultSize)

复合语句

复合语句是大括号括起来的语句列表;

  • 括起来的语句应当较复合语句多缩进一个层级
  • 开始的大括号应当在复合语句所在行的末尾;结束的大括号应当独占一行且同复合语句的开始保持同样的缩进.
  • 当括号时控制结构的一部分时,诸如if或者for语句,所有语句都需要用打括号括起来,也包括单个语句.这个约定使得我们更方便地添加语句而不用担心忘记加括号而引起bug
  • 像if一样的语句开始的关键词,其后应当紧跟一个空格,起始大括号应当在空格之后

if语句

if (condition) {
    statements
} else if (condition) {
    statements
} else {
    statements
}

绝不允许在if语句中省略花括号

// 好的写法
if (condition) {
    doSomething();
} 
// 不好的写法: 不恰当的空格
if(condition){
    doSomething();
} 
// 不好的写法: 遗漏花括号
if (condition) 
    doSomething();

// 不好的写法: 所有代码写在一行
if (condition) { doSomething(); }
// 不好的写法: 所有代码写在一行且没有花括号
if (condition) doSomething();

for语句

for (initialization; condition; update) {
    statements
}

for (variable in object) {
    statements
}

当使用 for-in 语句时,记得使用 hasOwnProperty() 进行双重检查来过滤出对象的成员

while语句

while (condition) {
    statements
}

do语句

do {
    statements
} while (condition)

switch语句

switch (expression) {
    case expression:
        statements
    default:
        statements
}

switch下的每一个case都叮当保持一个缩进.除第一个之外包括default在内的每一个case都应当在之前保持一个空行
每一组语句(除了default)都应当以break, return, throw结尾,或者用一行注释表示跳过

// 好的写法
switch (value) {
    case 1:
       /* falls through */
    case 2:
        doSomething();
        break;
    case 3:
       return true;
    default:
       throw new Error('this should not happen');
}

try语句

try {
    statements
} catch (variable) {
    statements
} finally {
    statements
}

12.严格模式

严格模式应当仅限在函数内部使用,千万不要在全局使用.

ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。

13.变量声明

所有的变量在使用前都应事先定义.变量定义应放在函数开头.
变量定义前应当初始化,并且赋值操作符应当保持一致的缩进.初始化的变量应当在未初始化变量之前.

14.函数声明

函数声明应当在使用前提前定义.
一个不是作为方法的函数(也就是没有作为一个对象的属性)应当使用函数定义的格式(不是函数表达式和Function构造器格式).
函数名和开始圆括号之前不应当有空格.结束的圆括号和右边的花括号之间应该留一个空格.右侧的花括号应当同function关键字保持同一行.开始和结束括号之间不应该有空格.参数名之间应当在逗号之后保留一个空格.函数体应当保持一级缩进

// 好的写法
function doSomething(arg1, agr2) {
    return arg1 + arg2;
}
// 不好的写法: 第一行不恰当的空格
function doSomething (arg1, agr2) {
    return arg1 + arg2;
}
// 不好的写法: 
var doSomething = function doSomething(arg1, agr2) {
    return arg1 + arg2;
}
// 不好的写法: 左侧的花括号位置不对
function doSomething(arg1, agr2)
{
    return arg1 + arg2;
}
// 错误的写法: 使用Function构造器
var doSomething = new Function('arg1', 'agr2', 'return arg1 + arg2');

15.留白

在逻辑相关的代码块之间添加空行可以提高代码的可读性

两行空行权限在如下情况使用

  • 在不同的源代码文件之间
  • 在类和接口定义之间

单行空行权限在如下情况使用

  • 方法之间
  • 方法中局部变量和第一行语句之间
  • 多行或单行注释之前
  • 方法中逻辑代码块之间以提高代码的可读性

空格应当在如下情况中使用

  • 关键词后跟括号的情况应当用空格隔开
  • 参数列表中逗号之后应当保留一个空格
  • 所有的除了点(.)之外的二元运算符,其操作数都应当用空格隔开.单目运算符的操作数之间不应该用空白隔开,诸如一元减号,递增(++),递减(--)
  • for语句中的表达式之间应当用空格隔开

16. 需要避免的

  • 切勿使用像String一类的原始包装类型创建的新对象
  • 避免使用eval()
  • 避免使用with语句.改语句在严格模式中不复存在,可能在未来也将去除

使用工具(eslint)来强制约束

eslint 规则

eslint规则在.eslintrc.js中定义,觉得不合理的可以禁掉某条规则,或者有好的建议的也可以添加;
主要注意一下几条:

  • 代码缩进用4空格
  • 语句必须默认后加分号
  • 使用单引号
  • 提交代码前将console.log语句删掉或注释掉(不然影响其他开发人员调试)
  • 禁止使用var,使用es6的let,const声明变量

还有一些情况是不需要检测的,例如第3方的库, 框架、组件、ui库等等,可以将这些文件放在.eslintignore文件中,可以忽略eslint的检测

在文件顶部加上下面这行,可以禁掉整个文件的eslint规则

/* eslint-disable */

pre-commit

代码提交之前会强制code-review,不符合规范的不允许提交代码
使用方法
1.在命令行安装

npm i --save-dev pre-commit

2.在package.json中配置

{
    "scripts": {
        "eslint": "eslint ./ --ext js,vue --ignore-pattern .eslintignore --cache --fix",
        "lint-message": "echo '开始 eslint 检查, 存在 error 则会拒绝提交'"
    },
    "pre-commit": [
        "lint-message",
        "eslint" // 进行eslint检查并自动修复一些简单的格式错误
    ],
}

代码提交之前会强制code-review,不符合规范的不允许提交代码

如果项目实在没时间去改的话,可以 git commit -m 'XXX' --no-verify 或 git commit -n 'xxx' 强制提交

小技巧-vscode可以配置保存自动修复eslint错误

vscode安装eslint插件,在配置中配置如下

{
     "eslint.autoFixOnSave": true,
     "eslint.enable": true,
     "eslint.options": {
        "extensions": [".js", ".vue", ".jsx"]
     },
     "eslint.validate": [
          {

              "language": "vue",
              "autoFix": true
          },
          {
              "language": "javascript",
              "autoFix": true
          },
          {
              "language": "javascriptreact",
              "autoFix": true
          }
      ],
}

参考

从零开始搭建vue项目

1.vue技术栈(vue-cli + vue-router + vuex + NPM + +es6 webpack + babel)

  • vue-cli: 是一个脚手架工具,用于自动生成vue项目的文件和目录;
  • vue-router:vue提供的前端路由工具,用其我们实现页面的路由控制,局部刷新及按需加载,构建单页面应用,实现前后端分离;
  • vuex:是状态管理工具,用于实现统一管理我们项目各种数据的交互与重用,存储我们所需要的数据对象;
  • NPM: 是nodejs的包管理工具,用于统一管理项目中需要用到的包,插件,工具,命令,便于维护和开发;
  • es6:JavaScript的最新版本,又称ECMAJavaScript2015,用它我们可以简化我们的代码,并利用其提供的强大功能来快速实现我们的js逻辑;
  • webpack 是一个强大的文件打包工具,可以将我们前端项目文件统一打包压缩到js中,用vue-loader加载器实现语法转化与加载
  • babel 是一款将es6 代码转化为浏览器兼容的es5代码的插件

2.构建大型应用

项目前端目录及文件构建(vue-cli)

npm install -g vue-cli
vue init webpack my-project
cd my-project
npm install
npm run dev

浏览器的缓存机制

浏览器的缓存机制

缓存过程


浏览器与服务器通信的方式为应答模式, 即是:
浏览器发起HTTP请求-服务器响应请求, 那么浏览器怎么确定一个资源该不该请求缓存呢?
浏览器第一次向服务器发起该请求后拿到请求结果, 将请求结果和缓存标识存入浏览器缓存, 浏览器对于缓存的处理是根据第一次请求资源时返回的响应头来确定的

分为强制缓存和协商缓存

强制缓存优先于协商缓存, 强制缓存(expires和Cache-control)生效则直接使用强制缓存, 若不生效则进行协商缓存(Last-Modified/If-Modified-Since和Etag/If-None-Match), 协商缓存由服务器决定是否使用缓存, 如果协商缓存失效, 那么代表该请求的的缓存失效, 返回200,重新返回资源的缓存标识, 再存入浏览器缓存中; 生效则返回304, 继续使用缓存;

Cache-control优先级高于expires
Etag/If-None-Match优先级高于Last-Modified/If-Modified-Since

区别

  1. 强缓存不发请求, 直接在缓存中获取; 弱缓存需要发送一个请求, 验证是都可以使用缓存, 如果可以则返回304直接使用缓存, 如果不可以使用缓存, 则直接返回该资源的数据
  2. 强缓存返回200(from cache), 弱缓存返回304

强制缓存

不会向服务器请求资源, 直接从缓存中读取资源, 在chrome控制台的netWork选项中可以看到该请求返回200的状态码, size显示 from disk cache和from memory cache;

  • from disk cache 从硬盘中读取缓存;
  • from memory cache 从内存中读取缓存;浏览器会在js和图片资源等文件解析后直接存入内存缓存中, 那么当刷新页面时只需直接从内存缓存中读取(from memory cache);

协商缓存

协商缓存是无法减少请求数的开销的, 但是可以减少返回的正文大小;

用户行为对缓存的影响

1.地址栏访问, 链接跳转是正常用户的行为, 将会触发浏览器缓存机制
2.F5刷新, 浏览器会设置max-age=0, 跳过强制缓存判断, 会进行协商缓存判断
3.ctrl+F5刷新, 会跳过强制缓存和协商缓存, 直接从服务器拉取资源

关于js中的数字问题

js中的数字范围

Number.MAX_SAFE_INTEGER Number.MIN_SAFE_INTEGER
Number.MAX_VALUE Number.MIN_VALUE
最大的整数 2^53-1

整数检测

Number.isInteger()
检测是否是安全的整数
Number.isSafeInteger()

32位有符号整数

虽然最大整数能达到53位,但是有的运算(数位操作)只适用于32位有符号整数,Math.pow(-2,31)~Math.pow(2, 31);

a | 0,可以将变量a转换为有效整数;

一些特殊值NaN和Infinity并不是32位安全范围的

// 特殊值之NaN 是js中唯一一个不等于自身的值
// NaN !== NaN  true 他与任何一个值都不相等
// isNaN() 检测一个参数是否不是NaN,也不是一个数
window.isNaN('foo') // true 'foo'既不是NaN,也不是数字
window.isNaN(2/'foo') // true
ES6 Number.isNaN('foo') //false

// 特殊值之Infinity 无穷数
1/0 Infinity
-1/0 -Infinity
Infinity/Infinity  NaN

 特殊值之 -0
-0除了可用作常量外,也可以是某些数学运算的返回值
var a = 0 / -3 // -0
var b = 0 * -3 // -0

JSON.stringify(-0) // "0"
JSON.parse('-0') //-0
-0 == 0 // true
-0 === 0 // true
区分 -0 和 0
function isNegZero(n) {
    n = Number(n);
    return (n === 0) && (1 / n === Infinity);
}


// Object.is()判断两个值是否绝对相等
 var a = 2 / 'foo';
 var b = -3 * 0;
 Object.is(a, NaN); // true
 Object.is(b, -0); // true
 Object.is(b, 0); // false
 if(! Object.is) {
     Object.is = function(v1, v2) {
        //  判断是否是-0
        if(v1 === 0 && v2 === 0) {
            return 1 / v1 === 1 / v2;
        }
        // 判断是否是NaN
        if(v1 !== v1) {
            return v2 !== v2;
        }
        // 其他情况
        return v1 === v2;
     }
 }

输入url发生了什么

输入url发生了什么

img

浏览器解析

img

白屏时间

Chrome
window.chrome.loadTimes().firstPaintTime - timing.navigationStart
IE
timing.msFirstPaint - timing.navigationStart

常见的日期格式化

处理时间,获得昨天,明天,后天...以及对应的星期

const arr = [ '周日', '周一', '周二', '周三', '周四', '周五', '周六', ];
        function getDateStr(count) {
            if (count === 0) {
                return '今天';
            }
            const date = new Date();
            date.setDate(date.getDate() + count);
            const index = date.getDay();  // 获取周几,0为周日
            const year = date.getFullYear();
            const month = date.getMonth() + 1;
            const day = date.getDate();
            return year + '-' + month + '-' + day + '(' + arr[index] + ')';
        }
        for (let i = 0; i < this.startDayItems.length; i++) {
            this.startDayItems[i] = getDateStr(i);
        }
        
        // 得出一个数组
        startDayItems: [ '今天', '2017-04-17(周二)', '2017-04-18(周三)', '2017-04-19(周四)', '2017-04-20(周五)', '2017-04-21(周六)', '2017-04-22(周日)', ],
        

时间戳转换成 2017.03

const date = new Date();
const Y = date.getFullYear() + '.';
const M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1);
this.todayTime = Y + M;

时间戳装换成 yyyy-MM-dd hh:mm:ss
2017-04-21 08:19:00

time(value) {
    const date = new Date(value * 1000);
    const Y = date.getFullYear() + '-';
    const M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-';
    const D = date.getDate() + ' ';
    const h = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours()) + ':';
    const m = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes() + ':';
    const s = date.getSeconds(); 
    return (Y + M + D + h + m + s);
},

日期格式转换为 时间戳

// 也很简单
const strtime = '2017-04-20 20:55:49:123';
const date = new Date(strtime); //传入一个时间格式,如果不传入就是获取现在的时间了,这样做不兼容火狐。
// 可以这样做
const date = new Date(strtime.replace(/-/g, '/'));

// 有三种方式获取,在后面会讲到三种方式的区别
time1 = date.getTime();
time2 = date.valueOf();
time3 = Date.parse(date);

/* 
三种获取的区别:
第一、第二种:会精确到毫秒
第三种:只能精确到秒,毫秒将用0来代替
比如上面代码输出的结果(一眼就能看出区别):
1398250549123
1398250549123
1398250549000 
*/

Date()的参数

new Date("month dd,yyyy hh:mm:ss");
new Date("month dd,yyyy");
new Date("yyyy/MM/dd hh:mm:ss");
new Date("yyyy/MM/dd");
new Date(yyyy,mth,dd,hh,mm,ss);
new Date(yyyy,mth,dd);
new Date(ms);

比如
new Date("September 16,2016 14:15:05");
new Date("September 16,2016");
new Date("2016/09/16 14:15:05");
new Date("2016/09/16");
new Date(2016,8,16,14,15,5); // 月份从0~11
new Date(2016,8,16);
new Date(1474006780);

script的onload和onreadystatechange

在Chrome和FireFox上,onload会在脚本加载完之后触发,在回调函数中脚本的内容已经加载完全。而这两个浏览器的script标签是不支持onreadystatechange事件的。
在IE上比较有趣, 在onreadystatechange事件中还要判断script.readyState属性

判断引入js文件是否加载完毕后执行之后的代码(参考jquey的源码):

var script = document.createElement('script');
script.src="xx.js";
script.onload = script.onreadystatechange = function(){
     if(  ! this.readyState     //这是FF的判断语句,因为ff下没有readyState这人值,IE的readyState肯定有值
          || this.readyState=='loaded' || this.readyState=='complete'   // 这是IE的判断语句
    ){
          console.log('loaded');
    }
};

1.IE8和IE8以下浏览器中,script标签并不支持onload事件,但是支持onreadystatechange事件。
2.IE8以上浏览器、谷歌浏览器和火狐浏览器支持onload事件。
3.readyState是onreadystatechange事件的一个状态,当值为loaded或者complete的时候,都表示已经加载完毕。
4.if(!this.readyState||this.readyState=='loaded'||this.readyState=='complete'),!this.readyState表示不是不是IE11以下浏览器(IE11以下浏览器也是支持onreadystatechange事件的)

函数式编程概念之尾调用

尾调用,就是在函数的最后一步调用另一个函数

function foo(x) {
     return g(x);
}

尾调用不一定出现在函数尾部,只要是最后一步操作就行

function f(x) {
  if (x > 0) {
    return m(x)
  }
  return n(x);
}

函数m和n都属于尾调用,他们都是函数的最后一步操作

尾调用优化

函数调用会在内存形成一个’调用记录‘,保存调用位置和内部变量等信息,如果在A函数中调用B函数,在A函数的调用记录上还会形成一个B的调用记录,等到B函数运行结束后,把结果返回到A,B的调用记录才会消失。如果函数B内部还调用C函数,那就是还有一个C的调用记录栈,以此类推,所有的调用记录,就会形成一个’调用栈‘。

尾调用在于它特殊的调用位置。由于是函数最后一步操作,所以不需要保留外层函数的调用记录,因为调用位置和内部变量都不会再用到了。

’尾调用优化‘,只调用内层函数的调用记录。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用记录只有一项,这将大大节省内存。

使用css自定义属性实现一键换肤功能

使用css自定义属性实现一键换肤功能

css自定义属性简介

css 的自定义属性功能(var), 在css中使用变量,和less及scss不同的是,在js中可以直接修改变量值;

用法: MDN

它们使用自定义属性符号(例如--main-color: black;)进行设置,并使用var()函数。
例如: color: var(--main-color);

:root {
  --main-color: brown;
}

.one {
  color: white;
  background-color: var(--main-color);
  margin: 10px;
  width: 50px;
  height: 50px;
  display: inline-block;
}

可以在javascript中设置

// get variable from inline style
element.style.getPropertyValue("--my-var");

// get variable from wherever
getComputedStyle(element).getPropertyValue("--my-var");

// set variable on inline style
element.style.setProperty("--my-var", 'red');

浏览器支持
css自定义属性目前支持的浏览器有

使用css-vars-polyfill

polyfill

补充polyfill 的简单解释
polyfill用于表示根据新特性的定义,创建一段与之行为等价但能够在旧的javascript环境中运行的代码

引用 wiki 里的一句话解释

In web development, a polyfill is code that implements a feature on web browsers that do not support the feature. Most often, it refers to a JavaScript library that implements an HTML5web standard, either an established standard (supported by some browsers) on older browsers, or a proposed standard (not supported by any browsers) on existing browsers. Formally, "a polyfill is a shim for a browser API".

可以这样简单的理解一下:
css自定义变量在IE浏览器中无法使用,但是使用了css-vars-polyfill ,就可以让我们在IE9+中也可以使用该属性;
还记得 babel-polyfill 么, babel允许我们使用es6的语法,它主要是把es6的语法转成大多数浏览器可是支持的es5语法, 可以使用 工具 来看一下平常所写的es6语法对应到es5是什么样的, 例如

但是像 Array.from , Object.assign, Array.prototype.includes, promise, async ,await 这些都是es5语法中没有的,我们还是想使用,但是很多浏览器不支持怎么办, babel-polyfill就是帮我们解决这个问题的

用Object.defineProperty()简单实现vue中的数据绑定

用Object.defineProperty()简单实现vue中的数据绑定$watch

function Observer(data) {
    this.data = {};
    for (const key of Object.keys(data)) {
        Object.defineProperty(this.data, key, {
            enumerable: true,
            configurable: true,
            get: function () {
                console.log('你访问了' + key);
                return data[key];
            },
            set: function (newVal) {
                console.log('你设置了' + key);
                console.log('新的' + key + '=' + newVal);
                if (newVal === data[key]) {
                    return;
                }
                data[key] = newVal;
            }
        })
    }
}
Observer.prototype.$watch = function (key, cd) {
    let value = this[key];
    Object.defineProperty(this, key, {
        enumerable: true,
        configurable: true,
        get: function () {
            console.log(`你访问了 ${key}`);
            return value;
        },
        set: function (newValue) {
            cd(newValue);
            value = newValue;
            return newValue;
        },
    })
}

const app1 = new Observer({
    name: 'wangyaxing',
    age: '24',
    a: {
        b: 1,
        c: 2,
    },
});
const app2 = new Observer({
    city: 'beijing'
});
app1.$watch('age', function(newValue) {
    console.log(`我年纪变了,我现在 ${newValue}岁了`);
});

app1.age = 100;
app1.age;
app1.age = 120;
app1.a.b;
app1.a.c = 10;
app1.a = {
    e: 100,
}

关于Node.js的几个问题

1, 什么是错误优先的回调函数?

  • 错误优先的回调函数用于传递错误和数据。第一个参数始终应该是一个错误对象, 用于检查程序是否发生了错误。其余的参数用于传递数据。
fs.readFile(filePath, function(err, data) {  
	if (err) {
		//handle the error
	}
	// use the data
}

2,如何避免回调地狱

你可以有如下几个方法

  • 模块化:将回调函数分割为独立的函数
  • 使用Promises
  • 使用yield来计算生成器或Promise

3,如何用Node监听80端口

在类Unix系统中你不应该尝试去监听80端口,因为这需要超级用户权限。 因此不推荐让你的应用直接监听这个端口。
如果你一定要让你的应用监听80端口的话,你可以有通过在Node应用的前方再增加一层反向代理 (例如nginx)来实现,如下图所示。否则,建议你直接监听大于1024的端口。
方向代理指的是以代理服务器来接收Internet上的连接请求,然后将请求转发给内部网络上的服务器, 并且将服务器返回的结果发送给客户端。

4,什么是事件循环

Node采用的是单线程的处理机制(所有的I/O请求都采用非阻塞的工作方式。而在底层,Node.js借助libuv来作为抽象封装层, 从而屏蔽不同操作系统的差异,Node可以借助livuv来来实现多线程。
Libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个事件循环, 以异步的方式将任务的执行结果返回给V8引擎。

异步和性能

异步: 现在与将来

任务

任务队列: 挂在事件循环队列的每个tick之后的一个队列,在事件樽还的每个tick中,可能出现的异步动作不会导致一个完整的新事件添加到事件循环对垒中,而会在当前tick的任务队列末尾添加一个项目(一个任务)

小结

JavaScript程序至少分成两个块: 第一块现在运行,下一块将来运行,已响应某个事件;
一旦有事情需要执行,事件循环就会运行,直到队列清空.事件循环的每一轮称为一个tick.用户交互, IO和定时器会向事件队列中加入事件;
并发是指两个或多个事件链随时间发展交替执行;

回调

存在一下问题

  • 调用回调过早
  • 调用回调过晚(或者不被调用)
  • 调用回调次数过多或过少
  • 未能传递所需的环境和参数

Promise

var p3 = new Promise(function(resolve, reject) {
    resolve('b');
});
var p1 = new Promise(function(resolve, reject) {
    resolve(p3);
});
var p2 = new Promise(function(resolve, reject) {
    resolve('a');
});

p1.then(function(v) {
    console.log(v);
});
p2.then(function(v) {
    console.log(v);
})
a,
b

p1不是立即值而是用另一个promise p3决议,后者本身决议为值'b'

Promise Api

Promise.resolve()

Promise.reject()

then()和catch()

Promise.all([..]): 只有传入的所有promise都完成,返回promise才能完成,如果有任何promise拒绝, 就立刻会被拒绝(抛弃任何其他promise的结果);如果完成的话,会得到一个数组,包含传入所有promise的完成值;对于拒绝的情况,你只会得到第一个拒绝promise的拒绝理由值;

Promise.race([..]): 只有第一个决议promise(完成或拒绝)取胜,并且其决议结果成为返回promise的决议;

若向Promise.all([..])传入空数组,他会立即完成,但是Promise.race([..])会挂住,且永远不会决议;

Promise局限性

1.顺序错误处理
2.单一值

生成器

es6引入了一种新的函数类型,它并不符合这种运行到结束的特性,这类新的函数被称为生成器

打破完整运行

输入和输出

function *foo(x) {
    var y = x * (yield);
    return y;
}

var it = foo(6); 
it.next();
var res = it.next(7);
console.log(res);
//{ value: 42, done: true }

创建了一个迭代器对象,并把它赋给一个变量it,用于控制生成器 *foo();然后调用next(),指示生成器 *foo()从当前位置继续运行,停在下一个yield处或者直到生成器结束;
一般来说,需要的next()调用要比yield语句多一个,前面的代码片段有一个yield和两个next()调用;

因为第一个next(..)总是启动一个生成器,并运行到第一个yield处;不过,是第二个next(..)调用完成第一个被暂停的yield表达式,第三个next(..)调用完成第二个yield,一次类推;
只有暂停yield才能接受这样一个通过next(..)传递的值,二生成器的起始处我们调用第一个next()时,还没有暂停的yield来接受这样的一个值;
启动生成器时一定要用不带参数的next();

多个迭代器

function *foo() {
    var x = yield 2;
    z++;
    var y = yield(x*z);
    console.log(x, y, z);
}
var z = 1;
var it1 = foo();
var it2 = foo();
var val1 = it1.next().value; // 2 <--yield 2
var val2 = it2.next().value; // 2 <--yield 2
val1 = it1.next(val2 * 10) .value; // 40 <--x:20,z:2
val2 = it2.next(val1 * 5) .value; // 600 <--x:200,z:3
it1.next(val2 /2);  // y:300  20 300 3
it2.next(val1/4); // y:10 200 10 3

生成器产生值

生成器的一种有趣用法是作为一种产生值的方式;
假定要产生一系列值,其中每个值都与前面一个有特定的关系.要实现这一点,需要有一个有状态的生产者能够记住其生成的最后一个值;
生成任意数字序列?
可以使用迭代器,迭代器用于从一个生产者一步步得到一系列值;就是没次想要从生产者得到下一个值得时候 调用next();

生成者与迭代器

var something = (function() {
    var nextVal;
    return {
        [Symbol.iterator]: function() {
            return this;
        },
        next: function() {
            if(nextVal === undefined) {
                nextVal = 1;
            } else {
                nextVal = (3 * nextVal) + 6;
            }
            return {
                done: false,
                value: nextVal
            }
        }
    }
})();
something.next().value; // 1
something.next().value; // 9
something.next().value; // 33
something.next().value; // 105

[..]被称为计算属性名:指定一个表达式并用这个表达式的结果作为属性的名称;
Symbol.iterator是ES6预定义的特殊Symbol值之一;

next()调用返回一个对象,这个对象有两个属性,done是一个boolean值,标识迭代器的完成状态,value中放置迭代器

除了构造自己的迭代器,许多JavaScript的内建数据结构,比如Array,也有默认的迭代器

如果想迭代对象所有属性(不需要保证特定的顺序),可以使用Object.keys(..),返回一个Array,类似于for (var k of Object.keys(obj)){}这样使用,这样在一个对象的键值上使用 for ... of循环于for ..in 循环类似;除了Object.keys()并不包含来自于[[Prototype]]链上的属性,而 for ..in则包含

iterable

前面例子中的something对象叫做迭代器,因为它的接口中有一个next()方法;而于其紧密相关的术语iterable(可迭代),即指一个包含可以在其指上迭代的迭代器对象;
ES6开始,从一个iterable中提取迭代器的方法是: iterable必须支持一个函数,其名称是专门的ES6符号值Symbol.iterable.调用这个函数时,它会返回一个迭代器,通常每次调用都会返回一个全新的迭代器;

for ..of循环自动调用它的 Symbol.iterator函数来构建一个迭代器

生成器迭代器

可以把生成器看做一个值的生产者,我们通过迭代器接口的next()调用一次提取出一个值
当你执行一个生成器,就得到了一个迭代器

停止生成器

for ..of 循环'异常结束'(也就是'提前终止'),通常由break,return 或者未捕获异常引起,会向生成器的迭代器发送一个信号使其终止;

异步迭代生成器 ?
同步错误处理

生成器 + promise

Array.prototype.slice(), slice方法返回一个从开始到结束(不包括结束)选择的一部分浅拷贝到另一个新数组的对象,且原始数组不会被修改
[].slice.call()或者Array.prototype.slice.call(), 就是截取(更重要的是获取,slice是得到的子数组的)函数的参数, 然后让arguments等'伪数组'也可以使用数组的各种方法;
ES6的Array.from()也是类似的用法;不单单可以转换arguments,元素集合,还可以转化类数组对象;
Array.prototype.slice.call()方法详解

function run(gen) {
    var args = [].slice.call(arguments, 1), it;
    // 在当前上下文初始化生成器
    it = gen.apply(this, args);
    // 返回一个promise用于生成器完成
    return Promise.resolve()
          .then(function handleNext(value) {
              // 对下一个yield出的值运行
            var next = it.next(value);
            return (function handleResult(next) {
                // 生成器运行完毕了吗?
                if(next.done) {
                    return next.value;
                } 
                // 否则继续运行
                else {
                    return Promise.resolve(next.value) 
                          .then(
                              // 成功就恢复异步循环,把决议的值发回生成器
                              handleNext,
                              // 如果value是被拒绝的promise
                              // 就把错误传回生成器进行出错处理
                              function handleError(err) {
                                  return Promise.resolve(
                                      it.throw(err);
                                  )
                                  .then(handleResult);
                              }
                          )
                }
            })(next)
    })
}
function *main() {
    // 
}
run(main);

运行run(..)的方式,它会自动异步运行你传给它的生成器,知道结束

我们定义的run(..)返回一个promise,一旦生成器完成,这个promise就会决议,或者收到一个生成器没有处理的未捕获异常

前面的模式--生成器yield出Promise,然后其控制生成器的迭代器来执行它,直到结束;如果我们无需库工具的辅助函数run()就能实现就好了

async/await

它现在是一类新的函数: async函数,我们不再yield出Promise,而是用await等待它决议;
如果你await了一个promise,async函数就会自动获知要做什么,它会暂停这个函数(就像生成器一样),直到Promise决议

生成器委托

function *foo() {
    console.log('*foo() starting');
    yield 3;
    yield 4;
    console.log('*foo finished');
}
function *bar() {
    yield 1;
    yield 2;
    yield *foo();
    yield 5;
}
var it = bar();
console.log(it.next().value);
console.log(it.next().value);
console.log(it.next().value);
console.log(it.next().value);
console.log(it.next().value);

/*
1
2
*foo() starting
3
4
*foo finished
5

*/

yield委托的主要目的是代码组织,以达到与普通函数调用的对称;

function *foo() {
    console.log('inside *foo():', yield 'B');
    console.log('inside *foo():', yield 'C');
    return 'D';
}
function *bar() {
    console.log('inside *bar():', yield 'A');
    console.log('inside *bar():', yield *foo());
    console.log('inside *bar():', yield 'E');
    return 'F';
}
var it = bar();
console.log('outside:', it.next().value);
// outside: A
console.log('outside:', it.next(1).value);
// inside *bar(): 1
// outside: B
console.log('outside:', it.next(2).value);
//inside *foo(): 2
// outside: C
console.log('outside:', it.next(3).value);
// inside *foo(): 3
// inside *bar(): D
// outside: E
console.log('outside:', it.next(4).value);
// inside *bar(): 4
// outside: F
function *bar() {
    console.log('inside *bar():', yield 'A');
    console.log('inside *bar():', yield *['B', 'C', 'D']);
    console.log('inside *bar():', yield 'E');
    return 'F';
}
var it = bar();
console.log('outside:', it.next().value);
// outside: A
console.log('outside:', it.next(1).value);
// inside *bar(): 1
// outside: B
console.log('outside:', it.next(2).value);
// outside: C
console.log('outside:', it.next(3).value);
// outside: D
console.log('outside:', it.next(4).value);
// inside *bar(): undefined
// outside: E
console.log('outside:', it.next(5).value);
// inside *bar(): 5
// outside: F

默认的数组迭代器并不关心通过next(..)调用发送的任何消息,所以值 2, 3和4根本就被忽略了,还有,因为迭代器没有显式的返回值(和前面使用的*foo()不同),所以yield *表达式完成后得到的是一个undefined

异步委托

异常也可以被委托

function *foo() {
    try {
        yield 'B';
    }
    catch(err) {
        console.log('error caught inside *foo():', err);
    }
    yield 'C';
    throw 'D';
}
function *bar() {
    yield 'A';
    try {
        yield *foo();
    }
    catch(err) {
        console.log('error caught inside *bar():', err);
    }
    yield 'E';
    yield *baz();
    yield 'G';
}
function *baz() {
    throw 'F';
}
var it = bar();
console.log('outside:', it.next().value);
// outside: A
console.log('outside:', it.next(1).value);
// outside: B
console.log('outside:', it.throw(2).value);
// error caught inside *foo(): 2
// outside: C
console.log('outside:', it.next(3).value);
// error caught inside *bar(): D
// outside: E
try {
    console.log('outside:', it.next(4).value);
}
catch(err) {
    console.log('error catch outside:', err);
}
// error catch outside: F

递归委托 ?

function *foo(val) {
    if(val > 1) {
        // 生成器递归
        val = yield *foo(val -1);
    }
    return yield request('http://some.url/?v=' + val)
}
function *bar() {
    var r1 = yield *foo(3);
    console.log(r1);
}
run(bar);

通信顺序进程 ?

runAll(
    function *(data) {
        data.res = [];
        var url1 = yield 'https://some.url.2';
        var p1 = request(url1);
        yield;
        data.res.push(yield p1);
    },
    function *(data) {
        var url2 = yield 'https://some.url.2';
        var p2 = request(url2);
        yield;
        data.res.push(yield p2);
    },
)

形实转换程序(thunk): 一个用于调用另外一个函数的函数,没有任何参数;
用一个函数定义封装函数调用,包括需要的任何参数,来定义这个调用的执行,那么这个封装函数就是一个形实转换程序;

function foo(x, y) {
    return x + y;
}
function fooThunk(){
    return foo(3, 4);
}
console.log(fooThunk()); // 7

异步的thunk

function foo(x, y, cb) {
    setTimeout(function() {
        cb(x+y);
    }, 1000);
}
function fooThunk(cb) {
    foo(3, 4, cb);
}
fooThunk(function(sum) {
    console.log(sum);
})

写一个工具来封装

function foo(x, y, cb) {
    setTimeout(function() {
        cb(x+y);
    }, 1000);
}

function thunkify(fn) {
    var args = [].slice.call(arguments, 1);
    return function(cb) {
        args.push(cb);
        return fn.apply(null, args);
    }
}
var fooThunk = thunkify(foo, 3, 4);
fooThunk(function(sum) {
    console.log(sum); // 7
})

前面thunkify(..)的实现接收foo(..)函数引用以及它需要的任意参数,并返回thunk本身(fooThunk(..));
典型的方法

function thunkify(fn) {
    return function() {
        var args = [].slice.call(arguments);
        return function(cb) {
            args.push(cb);
            return fn.apply(null, args);
        }
    }
    
}
var whatIsThis = thunkify(foo);
var fooThunk = whatIsThis(3, 4);
fooThunk(function(sum) {
    console.log(sum);
})

thunkify(..)工具产生一个生成thunk的函数

thunkify(..)生成一个thunkory,然后thunkory生成thunk;

var fooThunkory = thunkify(foo);
var fooThunk1 = fooThunkory(3, 4);
var fooThunk2 = fooThunkory(5, 6);
// 而不是
var fooThunk1 = thunkify(foo, 3, 4);
var fooThunk2 = thunkify(foo, 5, 6);

尾调用优化

尾调用(TCO)就是一个出现在另一个函数'结尾'处的函数调用;这个调用结束后就没有其余的事情要做了(除了可能要返回结果值)

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

调用一个新函数需要额外的一块预留内存来管理调用栈,称为栈帧
支持TCO的引擎能够意识到foo(y+1)调用位于尾部,这就意味着bar(..)基本上已经完成了,那么在调用foo(..)时,它就不需要创建一个新的栈帧,而是可以重用已有的bar(..)的栈帧。这样不仅速度快,也更节省内存;
有了TCO,尾调用的递归函数本质上就可以任意运行,因为再也不需要额外的内存

初始react

1,做react需要会什么

  • react主要负责渲染的功能,想做好一个项目,需要其他库和工具的配合,比如用redux管理数据,react-router管理路由,react全面拥抱es6
    webpack也的会用,要提高性能,需要按需加载,immutable.js也的用上,还有单元测试

2,react是什么

  • 用脚本进行DOM操作的代价很昂贵。DOM和js各为一个岛屿,他们之间用收费桥梁连接,js每次访问DOM,都必须交纳过桥费,推荐的做法是努力减少过桥的次数,努力待在js岛上.
    react是虚拟DOM,他创造了虚拟DOM并将他们储存起来,每当状态发生变化的时候就会创造新的虚拟节点和以前的进行对比,将变化的部分进行渲染,整个过程没有对DOM进行获取和操作,只有一个渲染的过程,所以说react是一个UI框架

3,React的组件化

  • react的一个组件是由dom视图和state数据组成,state是数据中心,他的状态决定这视图的状态。react采用setState来控制视图的更新。setState会自动调用render函数,触发视图的重新渲染,如果只是state数据的变化而没有调用setState,并不会触发更新。

4,react的diff算法

  • 组件更新的时候回创建一个新的虚拟DOM树并且会和之前储存的dom🌲进行计较。react提出一种假设,相同的节点具有类似的解构,而不同的节点具有不同的结构,在这种假设之上进行逐层的比较,如果发现节点是不同的,那就直接删除旧的节点以及它所包含所有子节点然后替换成新的节点,如果是相同的节点,则只进行属性的更改
    对于列表的diff算法稍有不同,因为雷彪通常具有相同的结构,在对列表节点进行删除,插入,排序的时候,单个节点的整体操作远比一个个对比一个个替换要好的多,所以在创建列表的时候需要设置key值,这样react才能分清谁是谁,当然不写key值也可以,但这样通常会报出警告,通知我们加上key值以提高react的性能

5,React组件是怎么来的

  • react系统内部设计了一套类系统,利用它来创造react组件, React.createClass() - 创造一个类,当然这不是必须的,Facebook官方推荐的写法是用es6的class类来创造组件
class Main extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            productList: [],
            params: '',
        }
    }
}

两种写法的功能实现的功能一样但是原理却不同,es6的class类可以看作是构造函数的语法糖,可以把他当做构造函数来看,extends实现了类之间的继承 -- 定义一个类Main继承React.Component所有的属性和方法,组件的生命周期函数就是从这儿继承来的.constructor是构造器,在实例化对象时调用,super调用父类的constructor创造了父类的实例对象this,然后用子类的构造函数进行修改
当我们使用组件< Main />时,其实是对Main类的实例化——new Main,只不过react对这个过程进行了封装,让它看起来更像是一个标签。
有三点值得注意:1、定义类名字的首字母必须大写 2、因为class变成了关键字,类选择器需要用className来代替。 3、类和模块内部默认使用严格模式,所以不需要用use strict指定运行模式。

6,组件生命周期

组件在初始化时会触发5个钩子函数
1,getDefaultProps() 设置默认的props,也可以用defaultProps设置组件的默认属性
2,getInitialState() 使用es6的class语法时是没有这个钩子函数的,可以直接在constructor中定义this.state。此时可以直接访问this,props。
3,componentWillMount() 组件初始化时调用,以后组件更新不调用,整个生命周期只调用一次,此时可以修改state。
4,render() react中最重要的步骤,创建虚拟dom,进行diff算法,更新dom树都在此进行,此时就不能更改state了。
5,componentDidMount() 组件渲染之后调用,可以通过this.getDOMNode()获取和操作dom节点,只调用一次

更新时也会触发5个钩子函数
6,componentWillReceiveProps(nextProps) 组件初始化时不调用,组件接受新的props时调用。
7,shouldComponentUpdate(nextProps,nextState) react性能优化非常重要的一环。组件接受新的state或者props时调用,我们可以设置在此对比前后两个props和state是否相同,如果相同返回false组织更新。不过调用this.forceUpdate会跳过此步骤
8,componentWillUpdate(nextProps, nextState) 组件初始化时不调用,只在组件将要更新时才调用,此时可以修改state
9,render()
10,componentDidUpdate() 组件初始化时不调用,组件更新完成后调用,此时可以获取dom节点

还有一个卸载钩子函数
11,componentWillUnMount() 组件将要卸载时调用,一些事件监听和定时器需要在此时清除

以上可以看出来react共有10个周期函数(render重复一次),这10个函数可以满足我们所有对组件操作的需求,利用的好可以提高开发效率和组件性能

简单说一下babel

中文文档
英文文档

babel-preset-env

根据你支持的环境自动决定适合你的babel插件;

useBuiltIns:

useBuiltIns是配合polyfill使用的, 此选项配置如何@babel/preset-env处理polyfill。

  • false: 默认为false, 意思是babel-polyfill全量引入

  • entry:
    是按照目标环境(即oreset-env的targets属性)去polyfill的,不关心代码中是否使用,可以保证在目标环境一定可用

  • usage(实验性):
    会分析代码调用,但是对于原型链上的方法仅仅按照名字去匹配,可以得到更小的polyfill体积;

因为很难去分析代码中是否调用了,例如以下情况就分析不了,会导致出错

  1. 无法分析一个对象是什么类型的
    data.split(); 它会认为你用的是String方法的split,但是有可能data是个数组, split是我自己定义的方法,此时polyfill就不准确了

  2. 无法分析第三方包是否用到了
    include中配置了一个第三方包, 第三包中用到了ES6的 Promise,但是你自己的代码中没有用到Promise, 此时就会判断你的代码中不需要对Promise对象polyfill,然后运行的时候代码就报错了

综上所述,最优的方案是配置 useBuiltIns为entry, 然后配置targets.browsers, 根据环境去进行polyfill

polyfill的时机

编译的时候进行polyfill, 运行的时候判断是否需要用到polyfill

babel-polyfill

Babel默认只转换新的JavaScript句法(比如箭头函数), 而不转换新的API, 比如Iterator, Generator, Set, Maps, Proxy, Reflect, Symbol, Promise等全局对象, 以及定义在全局对象上的方法, 比如(Object.assign)都不会转码;
可以使用 babel-polyfill 支持全新的全局变量, 例如Promise, 新的原生方法如String.padStart(), 它使用了core-jsregenerator';

安装

npm install --save babel-polyfill

使用它时需要在你应用程序的入口订单或打包配置中引入;

import 'babel-polyfill';
// 或者
require('babel-polyfill');

babel-plugin-transform-runtime

外部引用辅助函数和内置函数,自动填充代码而不会污染全局变量;

思考:babel-runtime 为什么适合 JavaScript 库和工具包的实现?

    1. 避免 babel 编译的工具函数在每个模块里重复出现,减小库和工具包的体积;
    1. 在没有使用 babel-runtime 之前,库和工具包一般不会直接引入 polyfill。否则像 Promise 这样的全局对象会污染全局命名空间,这就要求库的使用者自己提供 polyfill。这些 polyfill 一般在库和工具的使用说明中会提到,比如很多库都会有要求提供 es5 的 polyfill。在使用 babel-runtime 后,库和工具只要在 package.json 中增加依赖 babel-runtime,交给 babel-runtime 去引入 polyfill 就行了;

配置文件.babelrc

{
  "presets": [],
  "plugins": []
}

presets: 规定转码规则

# ES2015转码规则
$ npm install --save-dev babel-preset-es2015

# react转码规则
$ npm install --save-dev babel-preset-react

# ES7不同阶段语法提案的转码规则(共有4个阶段),选装一个
$ npm install --save-dev babel-preset-stage-0
$ npm install --save-dev babel-preset-stage-1
$ npm install --save-dev babel-preset-stage-2
$ npm install --save-dev babel-preset-stage-3

参考

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.