白话说说new操作符的背后

一、前言

今天群里有一段代码,发现小伙伴想尝试模拟new操作符,想想写一篇相关的博客吧。

二、作用

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。

三、语法

1
new constructor[([arguments])]

参数:

  • constructor
  • arguments

四、new执行的背后

举例当new Foo(…) 执行时,会发生以下事情:

  1. 一个继承自 Foo.prototype 的新对象被创建。
  2. 使用指定的参数调用构造函数 Foo ,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。
  3. 由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤1创建的对象。(一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤)

五、自己模拟new实现

既然已经知道new操作符背后做了些什么我们可以尝试自己模拟下。

1
var a = objectFactory(Constructor,...);
1
2
3
4
5
6
7
8
9
10
11
12
13
 function objectFactory() {

var obj = {};

var Constructor = [].shift.call(arguments);

obj.__proto__ = Constructor.prototype;

var ret = Constructor.apply(obj, arguments);

return typeof ret === 'object' ? ret : obj;

};
Share

js复习基本概念(数据类型)

一、 什么是数据类型

数据:未经过处理的原始记录,在计算机系统中,数据以二进制信息单元0,1的形式表示。

数据类型: 是用来约束数据的解释规约。

二、基础数据类型、引用数据类型

1. typeof操作符

(1) typeof的所有返回值都是字符串

(2) 六种返回值类型

  • undefined

  • number

  • string

  • boolean

  • Object

  • Function

(3) 建议使用()

1
(type of a);

原因:原因操作符优先级,js中有15种优先级,为避免混乱建议使用()包裹。

(4) typrof null

  • 返回值为”Obejct”
  • 原因

  • (1)从逻辑角度来看,null值表示一个空对象指针,而这正是使用typeof操作符检测null值时会返回“object”的原因。摘自《JAVASCRIPT高级程序设计 第三版 p25》

  • (2)null不是一个空引用, 而是一个原始值, 参考ECMAScript5.1中文版 4.3.11节; 它只是期望此处将引用一个对象, 注意是”期望”, 参考 null - JavaScript.

  • (3)typeof null结果是object, 这是个历史遗留bug, 参考 typeof - JavaScript

  • (4)在ECMA6中, 曾经有提案为历史平凡, 将type null的值纠正为null, 但最后提案被拒了. 理由是历史遗留代码太多, 不想得罪人, 不如继续将错就错当和事老, 参考 harmony:typeof_null [ES Wiki]

以上内容作者:克荷林
链接:https://www.zhihu.com/question/21691758/answer/98782260
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2. undefined

  • undefined类型只有一个值,即特殊的undefined
  • 在使用var关键字声明变量,但未对其初始化时,这个变量就是undefined

3. null

  • null类型只有一个值,这个特殊值是null,从逻辑角度来看,null表示一个空对象指针,而这也是typeof操作符检测null时会返回“obeject”的原因
  • 在使用var关键字声明变量,但未对其初始化时,这个变量就是undefined

4. null 和 undefined

(1)相同点

(1)参与判断 返回false

1
2
3
4
5
6
7
8
9
// if判断为false的情况

if (false)
if (null)
if (undefined)
if (0)
if (NaN)
if ('')
if ("")

(2)没有方法

(3)都是falsy,不加!比较都是false

(2)不同点

  • (1) 语义上不同,null是有值,但值为空,undefined是值未定义

  • (2) typeof

    null typeof 是obejct undefined typeof undefined

  • (3) 数字转化

    undefined =>NAN null => 0

  • (4) null可以json化

  • *(5)通常情况下,undefined代表未初始化,null已经初始化

  • *(6)在浏览器环境中,undefined是windows的一个属性,只不过属性对应的值是undefined

  • *(7) null不可以作为变量名 undefined可以作为变量名

    原因:null不能做变量名的原因应该是因为null是字面量

(3)为什么不同

JavaScript的最初版本是这样区分的:null是一个表示”无”的对象,转为数值时为0;undefined是一个表示”无”的原始值,转为数值时为NaN。

原因有如下两个:

  • null像在Java里一样,被当成一个对象。但是,JavaScript的数据类型分成原始类型(primitive)和合成类型(complex)两大类,Brendan Eich觉得表示”无”的值最好不是对象。

  • null像在Java里一样,被当成一个对象。但是,JavaScript的数据类型分成原始类型(primitive)和合成类型(complex)两大类,Brendan Eich觉得表示”无”的值最好不是对象。

因此,Brendan Eich又设计了一个undefined。

如果不深究,简单理解可以归纳为历史原因

(4)使用场景

[1] null

null表示”没有对象”,即该处不应该有值

典型用法:

  • null表示”没有对象”,即该处不应该有值
1
var test = null;
  • 作为对象原型链的终点
1
2
Object.getPrototypeOf(Object.prototype)
// null
[2] undefined
  • undefined表示”缺少值”,就是此处应该有一个值,但是还没有定义

  • 变量被声明了,但没有赋值时,就等于undefined

  • 调用函数时,应该提供的参数没有提供,该参数等于undefined

  • 对象没有赋值的属性,该属性的值为undefined

  • 函数没有返回值时,默认返回undefined

1
2
3
4
5
6
7
8
9
10
11
var i;
i // undefined

function f(x){console.log(x)}
f() // undefined

var o = new Object();
o.p // undefined

var x = f();
x // undefined

有些框架会定义

1
var undefined

建立undefined的引用是为了提高性能。

[3] == && ===
  • undeined是派生自null的值,因此ECMA-262规定他们的相等性测试要返回true
  • 原因:由于 == 会进行转换操作
1
2
console.log(null == undeinded) // true
console.log(null === undeinded) // false

5.boolean

名称源自数学家乔治布尔。

(1) 特点

[1] 该类型只有2个字面值,true和false

这两个值与数字不是一回事,true不一定等于1, false也不一定等于0

[2] 所有数据类型都可以转换为 boolean , js提供了Boolean方法转换

js中只有6个数据会被转化为fasle(fasly)

  • null

  • undefined

  • 0

  • -0

  • NaN

  • 空字符串

(2) 转换boolean

  • Boolean()

  • !!转换为boolean

5.Number

(1) 标准

  • ECMAScript使用的是IEEE 754标准(浮点数算术标准)来定义Number类型,ECMAScript中所有的数字,无论是整数还是小数,其类型均为Number。

  • 在程序内部,Number类型的实质是一个64位的浮点数,这与Java中double类型的浮点数是一致的。

(2)数字表示方式:

  • 十进制
1
var intNum = 12;
  • 八进制

建议不使用(严格模式、ES5下无效,会导致Javascript引擎抛出错误)

1
var octalNum1 = 070
  • 十六制
1
var hexNum = 0xA

(3)数值范围:

[1] JavaScript所能表示的数值范围为:
正负1.7976931348623157乘以10的308次方,其最小所能表示的小数为正负5乘以10的负324次方
[2] JavaScript能表示并进行精确算术运算的整数范围为:
正负2的53次方,也即从最小值-9007199254740992到最大值+9007199254740992之间的范围

对于超过这个范围的整数,JavaScript依旧可以进行运算,但却不保证运算结果的精度。值得注意的是,对于整数的位运算(比如移位等操作),JavaScript仅支持32位整型数,也即从-2147483648到+2147483647之间的整数。

获取边界值:

两个边界值可以分别通过访问Number对象的MAX_VALUE属性和MIN_VALUE属性来获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Number.MAX_VALUE // 1.7976931348623157e+308
Number.MIN_VALUE // 5e-324
```

**超过边界值:**

```javascript
Infinity // 如果超过Number.MAX_VALUE
-Infinity // 如果小于Number.MIN_VALUE

var a = 9007199254740992; // 对正负2的53次方范围以外的整数

console.log(a+3); // 正确的运算结果应该是9007199254740995,但JavaScript给出的计算结果却是9007199254740996
```


#### (4) float

##### [1] 所谓浮点数值,就是该数值中必须包含一个小数点,并且小数点后至少有一位数字

```javascript
var floatNum1 = 1.1;
[2] 虽然小数点前面可以没有整数,但不推荐:
1
var floatNum2 = .1;
[3] 由于保存浮点数值需要的内存是保存整数的两倍,因此ECMAscript会不失时机地将浮点数值转换为整数
  • 小数点后没有任何数字,那么数值就可以作为为整数值来保存
1
var floatNum1 = 1.; // 解析为1
  • 浮点数本身表示就是整数,那么该值会被转换为整数
1
var floatNum2 = 10.0; // 解析为10

(5)科学计数法:

  • 当数字极大或极小,可以用e表示法(即科学计数法)表示。

    1
    var floatNum = 3.125e7; // 等于31250000 3.125乘以10的7次方
  • ECMAscript会将那些小数点后面带有6个0以上的浮点数值转化为科学计数表示法。

    1
    var floatNum = 3e-7 // 等于0.0000003
  • 浮点数值的最高精度是17位小数,但在进行算术计算时其精度远远不如整数。建议永远不要测试某个特定的浮点数值。

(6) Infinity

1
11 / 0 // Infinity

(7) NaN

Not a Number

1
0 / 0 // NaN
1
Nan === NaN // false
  • isNaN()

  • 返回结果只有 true false

  • 使用isNaN()判断传入值是不是NaN
  • 但实际使用中会判断传入值是否能被转化为数字
1
alert(isNaN(NaN)); // true
alert(isNaN(10)); // false
alert(isNaN("10")); // false
alert(isNaN("blue")); // true
alert(isNaN(true)); // false

(7) 转换方法

目的:将数据转换成数字、整数、浮点数。

[1] Number()

作用:任何数据类型转Number

- 数字

- NaN

[2] parseFloat

作用:处理字符串

建议:传第二个参数10

[3] parseInt

作用:处理字符串

建议:传第二个参数10 (表示第一个参数是什么进制,返回值结果始终十进制)

(8) 精度损失 (0.1 + 0.2 != 0.3)

1
2
3
4
5
6
7
var a = 0.1;

var b = 0.2;

if (a + b === 0.3) {
console.log('You got 0.3'); // 无法到达
}
[1] 原因

关于浮点数值计算误差的问题,是因为基于IEEE754标准。

[2] 解决方案

(1) 基数

1
2
3
4
5
6
7
var a = 0.1;

var b = 0.2;

var res = ((a * 10) + (b * 10)) / 10;

console.log(res); // 0.3

(2) toFixed

1
2
3
4
5
6
7
8

var a = 0.1;

var b = 0.2;

var res = 0.1 + 0.2;

console.log(res.toFixed(1));

6.String

String类型用于表示由零或多个16位unicode字符组成的字符序列,即字符串。

(1) 字符串特性

ECMAScript中字符串是不可变的

1
2
3
4
'abcd'
'abcd'.length = 5

console.log('abcd'.length); // 4

(2) 转义符

(3) unicode 编码

\uxxxx

(4) 字符串类型转换

toString

null undefined 不可用

String()

可以转换传进去所有类型

6.基础数据类型转换归纳

相同点:

(1) 都有String Boolean

(2) 首字母大写

(3) 都可以穿入任何类型

(4) 得到的结果都是和自己对应的

(5) 传入对象[Object,Object]

8. Object

定义:属性构成对象的无序的集合

对象的比较是引用的比较

(1) Object对象的分类

[1] 内部对象 (17个)

a. 内置对象 (3)

  • Math
  • Global
  • JSON

b. 常用对象 (8)

  • Boolean

  • String

  • Number

  • Object

  • Array

  • Function

  • Date

  • RegExp

c. 错误对象 (6)

  • Error

  • EvalError

  • RangeError

  • ReferenceError

  • SyntaxError

  • TypeError

  • URIError

[2] 宿主对象

  • window (浏览器)
  • global (node)
  • document

[3] 自定义对象

(2)基础数据类型转对象

[1] boolean => Object
1
2
3
4
5
Object(false);

Boolean {false}
__proto__: Boolean
[[PrimitiveValue]]:false
[2] null => Object
1
2
3
Object(null);

{}
[3] undefined => Object
1
2
3
Object(undefined);

{}
[4] string => Object
1
2
3
4
5
6
7
8
9
Object('abc');

String {"abc"}
0: "a"
1: "b"
2: "c"
length: 3
__proto__: String
[[PrimitiveValue]]: "abc"
[5] number => Object
1
2
3
4
5
Object(123);

Number {123}
__proto__: Number
[[PrimitiveValue]]: 123

(3) 对象转基础数据类型

[1] Object => boolean

任何对象转换boolean类型,都是true

1
2
3
Boolean(Object); // true
Boolean({}); // true
Boolean(new Boolean(false)); // true
[2] Object => number

先调用valueOf(),后调用toString()

调用valueOf(),返回原始值,若无原始值则返回对象本身

[1] date对象特殊,当前系统时间与1970年1月1日差额毫秒

1
2


[2] 对象

[3] Object => string

先调用toString(),后调用valueOf()

调用toString(),当前对象通过字符串表示

  • 数组
1
2
3
4

String([1,2,3])

[1,2,3].toString() // 等同于调用了 结果:"1,2,3"
  • 函数
1
2
3
4
5
function a() {
console.log('test');
}

a.toString()
  • 日期
1
2
3
4
5
6
7
var a = new Date();

String(a);

a.toString(); // 相当于

"Sat Jun 30 2018 23:43:59 GMT+0800 (China Standard Time)"
  • 正则
1
2
3
4
5
var a = new RegExp();

String(a); // 输出"/(?:)/"

a.toString() // 相当于

(4). 创建Object

[1] 对象字面量
1
var o1 = {test: 1};
[2] new Object()
1
var o1 = new Object({test: 1111});
[3] Object.create()
  • 语法

Object.create(proto, [propertiesObject])

  • 参数

proto:

新创建对象的原型对象。

propertiesObject:

可选。如果没有指定为 undefined,则是要添加到新创建对象的可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)对象的属性描述符以及相应的属性名称。这些属性对应Object.defineProperties()的第二个参数

  • 返回值

一个新对象,带着指定的原型对象和属性

1
var a = Object.create(Object.prototype,{foo: {value: '111'}});
[4] new Object() 和 Object.create()区别

new操作符——完成四件事

  1. 创建一个新的空对象;

  2. 绑定this到这个空对象,即切换上下文;

  3. 给这个新对象添加proto,并指向构造函数的原型;

  4. 如果构造函数未返回对象(如返回对象,则替换return this),则将该新对象返回,即return this(第2条:this指向新对象);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

var a = Object.create(null,{foo: {value: '111'}});

{foo: "111"}
foo: "111"

var a = Object.create(Object.prototype,{foo: {value: '111'}});

{foo: "111"}

foo: "111"

__proto__:
Objectconstructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()

Object.create

  1. Object.create传入对象将作为新建对象的原型。

  2. Object.create传入构造函数原型时,无论构造函数是否返回对象,Object.create只会return 以构造函数原型来新建的对象(参见下面的polyfill),而new则参见上节粗体描述。

[5] Object.assign vs new

Object.assign 只拷贝对象的属性和函数,丢失原型链,proto指向Object.prototype

1
2
3
4
5
6
7
8
9
10
11
function Dog(name){
this.name = name;
}
Dog.prototype.getName = function(){
return this.name;
};
const pet1 = new Dog('rocky');
console.log(pet1.getName()); //rocky
const pet2 = Object.assign({},pet1);
console.log(pet2.name) //rocky
console.log(pet2.getName()); //error!!!!! getName undefined

(5). 对象属性查找

[1] .

解释器解析步骤:

  • 判断.之前是否为null 或 undefined
  • 判断.前时候为对象,若不是转化为对象
  • .后名字匹配
  • 返回值或undefined
[2] []

解释器解析步骤

  • 判断[]之前是否为null 或 undefined
  • 判断[]前时候为对象,若不是转化为对象
  • 计算[],转换为字符串,匹配值
  • 返回值或undefined

7.symbol

ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。

[1] Symbol值通过Symbol函数生成的

Symbol 值通过Symbol函数生成,Symbol函数前不能使用new命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。也就是说,由于 Symbol 值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。基本上,它是一种类似于字符串的数据类型。

  • 创建
1
let s = Symbol();
  • 不能使用new操作符
1
let s = new Symbol(); //报错
  • 不能添加属性
1
let s = Symbol(); // s.name 等 '123' 不可行

[2] typeof

typeof运算符的结果,表明变量s是 Symbol 数据类型

1
typeof s // "symbol"

[3] 参数

Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。

1
2
3
4
5
6
7
8
let s1 = Symbol('foo');
let s2 = Symbol('bar');

s1 // Symbol(foo)
s2 // Symbol(bar)

s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"

如果 Symbol 的参数是一个对象,就会调用该对象的toString方法,将其转为字符串,然后才生成一个 Symbol值

1
2
3
4
5
6
7
const obj = {
toString() {
return 'abc';
}
};
const sym = Symbol(obj);
sym // Symbol(abc)

[4] 比较

Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的

1
2
3
4
5
6
7
8
9
10
11
// 没有参数的情况
let s1 = Symbol();
let s2 = Symbol();

s1 === s2 // false

// 有参数的情况
let s1 = Symbol('foo');
let s2 = Symbol('foo');

s1 === s2 // false

[5] 运算

  • Symbol 值不能与其他类型的值进行运算,会报错
1
2
3
4
5
6
let sym = Symbol('My symbol');

"your symbol is " + sym
// TypeError: can't convert symbol to string
`your symbol is ${sym}`
// TypeError: can't convert symbol to string
  • Symbol 值可以显式转为字符串
1
2
3
4
let sym = Symbol('My symbol');

String(sym) // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'
  • Symbol 值也可以转为布尔值,但是不能转为数值
1
2
3
4
5
6
7
8
9
10
let sym = Symbol();
Boolean(sym) // true
!sym // false

if (sym) {
// ...
}

Number(sym) // TypeError
sym + 2 // TypeError

[6] 作为属性名的 Symbol

  • 定义

由于每一个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let mySymbol = Symbol();

// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';

// 第二种写法
let a = {
[mySymbol]: 'Hello!'
};

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

// 以上写法都得到同样结果
a[mySymbol] // "Hello!"
  • Symbol 值作为对象属性名时,不能用点运算符
1
2
3
4
5
6
const mySymbol = Symbol();
const a = {};

a.mySymbol = 'Hello!';
a[mySymbol] // undefined
a['mySymbol'] // "Hello!"

上面代码中,因为点运算符后面总是字符串,所以不会读取mySymbol作为标识名所指代的那个值,导致a的属性名实际上是一个字符串,而不是一个 Symbol 值。

同理,在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中。

1
2
3
4
5
6
7
let s = Symbol();

let obj = {
[s]: function (arg) { ... }
};

obj[s](123);

上面代码中,如果s不放在方括号中,该属性的键名就是字符串s,而不是s所代表的那个 Symbol 值。

  • 增强的对象写法
1
2
3
let obj = {
[s](arg) { ... }
};
  • 增强的对象写法

Symbol 类型还可以用于定义一组常量,保证这组常量的值都是不相等的。

1
2
3
4
5
6
7
8
9
const log = {};

log.levels = {
DEBUG: Symbol('debug'),
INFO: Symbol('info'),
WARN: Symbol('warn')
};
console.log(log.levels.DEBUG, 'debug message');
console.log(log.levels.INFO, 'info message');

下面是另外一个例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
const COLOR_RED    = Symbol();
const COLOR_GREEN = Symbol();

function getComplement(color) {
switch (color) {
case COLOR_RED:
return COLOR_GREEN;
case COLOR_GREEN:
return COLOR_RED;
default:
throw new Error('Undefined color');
}
}

常量使用 Symbol 值最大的好处,就是其他任何值都不可能有相同的值了,因此可以保证上面的switch语句会按设计的方式工作。

还有一点需要注意,Symbol 值作为属性名时,该属性还是公开属性,不是私有属性。

8.可变类型与不可类型

注:数据类型动态性 定义变量只是赋值,但过程中没有确定数据类型,在使用时在确定数据类型

(1) 基础数据类型是不可变:

[1] 值比较
  • 基础数据类型,是值的比较
1
2
3
4
5
var a = 1;

var b = 1;

a === b // true
[2] 动态的属性
  • 不可以添加属性和方法,也不能改变和删除属性和方法
1
2
3
4
5
var a = 'test';

a.name = 'helloworld';

console.log(a.name); // undefined
[3] 复制变量值

从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制为新变量分配的位置上。

1
2
3
4
5
var num1 = 1;

var num2 = num1; // num1: 1 num2: 1

num2 = 3; // num1: 1 num2: 3
[4] 传递参数
  • 传递参数的方式为值传递

  • 向参数传递基本类型,被传递的值会复制给一个局部变量,因此这个局部变量的变化会反应在函数外部。

1
2
3
4
5
6
7
8
9
10
11
12
function addTen(num) {
num += 10;
return num;
}

var count = 20;

var result = addTen(count);

console.log(count); // 20

console.log(result); // 30

(2) 引用数据类型可变

[1] 值比较
  • 引用比较,独立对象不相等
1
2
3
4
5
var obj1 = {a: 1};

var obj2 = {a:1};

obj1 === obj2 // false
[2] 动态的属性
  • 可以添加属性和方法,也不能改变和删除属性和方法
1
2
3
4
5
var a = {};

a.name = 'helloworld';

a.name; // 'helloworld'
[3] 复制变量值

从一个变量向另一个变量复制引用类型的值,同样会将存储在变量对象中的值复制一份放到新变量分配的空间中。不同的是,这个值的副本实际上是一个指针,复制结束后,两个变量实际上将引用同一个对象。
因此,改变其中一个,会影响另一个。

1
2
3
4
5
6
7
8
9
var obj1 = {a: 1};

var obj2 = obj1

obj2.a = 2;

obj1.a // 2

obj2.b // 2
[4] 传递参数
  • 传递参数的方式为值传递

  • 向参数传递引用类型,被传递的对象的引用会复制给一个局部变量,因此外部和函数内部的变量指向同一引用,因此内部会影响外部:

1
2
3
4
5
6
7
8
9
function setName(obj) {
obj.name = "Neo";
}

var person = new Object();

setName(person);

console.log(person.name); // Neo
Share

js复习基本概念(语法、严格模式)

1.语法

ECMAScript的语法大量借鉴了C及其他类C语言(如JAVA和Perl)的语法。

(1)区分大小写

  • ECMAScript中的一切(变量、函数名和操作符都区分大小写。

  • 拓展 HTML(要求区分大小写,但实际浏览器会做兼容)和CSS中不区分大小写

[1] js写在html中是否区分大小写

1
2
- 事件 如(onclick属性不区分大小写,但是事件后的内容区分大小写)
- 事件内的部分需要区分大小写 如:onclick="这部分"

[2] data属性

1
2
3
data写入属性都会转为小写 如: data-Name === data-name (chrome下测试并不是,ie下会有问题)

一旦涉及-都会涉及大小写问题,主要表现就是定义时如果存在大写,调用时需要用小写,负责会出现undefined

(2)标识符

所谓标识符,就是指变量、函数、属性的名字,或者函数的参数。

js中标识符是按下列格式规则组合起来的一或多个字符:

  • 第一个字符可以是:

    1
    (1)字母 (2)下划线(_)(3)美元符号($);
  • 其他字符可以是:

    1
    2
    (1)字母 (2)下划线 (3)美元符号 (4)数字
    // 汉字也是字符,unicode(不建议使用,除涉及数学计算)
  • ECMAScirpt推荐标识符采用驼峰大小写格式

(3) 注释

ECMAScirpt推荐标识符采用驼峰大小写格式使用C风格的注释

包含

  • [1] 单行注释
1
// 单行注释
  • [2] 多行注释
1
2
/*
 * 这是一个多行
* (块级注释) */

第二第三行的*不是必须的,纯粹为了提高可读性。

2.严格模式

ES5引入了严格模式(strict mode)

1
2
3
// 进入表示

"use strict";

老版本的浏览器会把它当作一行普通字符串,加以忽略。

“严格模式”有两种调用方法,适用于不同的场合。

[1] 启用(整个脚本启用)

将”use strict”放在脚本文件的第一行,则整个脚本都将以”严格模式”运行。如果这行语句不在第一行,则无效,整个脚本以”正常模式”运行。如果不同模式的代码文件合并成一个文件,这一点需要特别注意。

(严格地说,只要前面不是产生实际运行结果的语句,”use strict”可以不在第一行,比如直接跟在一个空的分号后面。)

1
2
3
4
5
6
7
8
 <script>
   "use strict";
   console.log("这是严格模式。");
 </script>

 <script>
   console.log("这是正常模式。");kly, it's almost 2 years ago now. I can admit it now - I run it on my school's network that has about 50 computers.
 </script>

[2] 启用(指定函数启用)

1
2
3
4
5
6
7
8
 function strict(){
   "use strict";
   return "这是严格模式。";
 }

 function notStrict() {
   return "这是正常模式。";
 }

[3] 脚本文件的变通写法

因为第一种调用方法不利于文件合并,所以更好的做法是,借用第二种方法,将整个脚本文件放在一个立即执行的匿名函数之中。

1
2
3
4
5
6
7
 (function (){

   "use strict";

   // some code here

 })();

[4] 严格模式目的

  • 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;
  • 消除代码运行的一些不安全之处,保证代码运行的安全;
  • 提高编译器效率,增加运行速度;
  • 为未来新版本的Javascript做好铺垫。

[5] 语法和行为改变

(1) 全局变量显示声明

在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,全局变量必须显式声明。

1
2
3
4
5
6
"use strict";

v = 1; // 报错,v未声明

for(i = 0; i < 2; i++) { // 报错,i未声明
}

因此,严格模式下,变量都必须先用var命令声明,然后再使用。

(2) 静态绑定

[1] 禁止使用with语句
因为with语句无法在编译时就确定,属性到底归属哪个对象。

1
2
3
4
5
6
7
 "use strict";

 var v = 1;

 with (o){ // 语法错误
   v = 2;
 }

[2] 创设eval作用域

1
2
3
正常模式下,Javascript语言有两种变量作用域(scope):全局作用域和函数作用域。严格模式创设了第三种作用域:eval作用域。

正常模式下,eval语句的作用域,取决于它处于全局作用域,还是处于函数作用域。严格模式下,eval语句本身就是一个作用域,不再能够生成全局变量了,它所生成的变量只能用于eval内部。

(3) 增强的安全措施

[1] 禁止this关键字指向全局对象

1
2
3
4
5
6
7
8
9
10
 function f(){
   return !this;
 }
 // 返回false,因为"this"指向全局对象,"!this"就是false

 function f(){
   "use strict";
   return !this;
 }
 // 返回true,因为严格模式下,this的值为undefined,所以"!this"为true。

[2] 禁止在函数内部遍历调用栈

1
2
3
4
5
6
7
8
9
10
11
function f1(){

  "use strict";

  f1.caller; // 报错

  f1.arguments; // 报错

}

f1();

(4) 禁止删除变量

严格模式下无法删除变量。只有configurable设置为true的对象属性,才能被删除。

1
2
3
4
5
6
7
8
9
10
11
12
 "use strict";

 var x;

 delete x; // 语法错误

 var o = Object.create(null, {'x': {
     value: 1,
     configurable: true
 }});

 delete o.x; // 删除成功

(5) 显式报错

1
2
3
4
5
6
7
 "use strict";

 var o = {};

 Object.defineProperty(o, "v", { value: 1, writable: false });

 o.v = 2; // 报错

严格模式下,对一个使用getter方法读取的属性进行赋值,会报错。

1
2
3
4
5
6
7
8
9
 "use strict";

 var o = {

   get v() { return 1; }

 };

 o.v = 2; // 报错

(6) 重名错误

严格模式新增了一些语法错误。

(1)对象不能有重名的属性

正常模式下,如果对象有多个重名属性,最后赋值的那个属性会覆盖前面的值。严格模式下,这属于语法错误。

1
2
3
4
5
6
"use strict";

  var o = {
    p: 1,
    p: 2
  }; // 语法错误

(2)函数不能有重名的参数

正常模式下,如果函数有多个重名的参数,可以用arguments[i]读取。严格模式下,这属于语法错误。

1
2
3
4
5
6
7
 "use strict";

 function f(a, a, b) { // 语法错误

   return ;

}

(7) 禁止八进制表示法

正常模式下,整数的第一位如果是0,表示这是八进制数,比如0100等于十进制的64。严格模式禁止这种表示法,整数第一位为0,将报错。

1
2
3
 "use strict";

 var n = 0100; // 语法错误

(8) arguments对象的限制

arguments是函数的参数对象,严格模式对它的使用做了限制。

(1) 不允许对arguments赋值

1
2
3
4
5
6
7
8
9
10
11
  "use strict";

  arguments++; // 语法错误

  var obj = { set p(arguments) { } }; // 语法错误

  try { } catch (arguments) { } // 语法错误

  function arguments() { } // 语法错误

  var f = new Function("arguments", "'use strict'; return 17;"); // 语法错误

(2)arguments不再追踪参数的变化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  function f(a) {

    a = 2;

    return [a, arguments[0]];

  }

  f(1); // 正常模式为[2,2]

  function f(a) {

    "use strict";

    a = 2;

    return [a, arguments[0]];

  }

  f(1); // 严格模式为[2,1]

(3)禁止使用arguments.callee

1
2
3
4
5
  "use strict";

  var f = function() { return arguments.callee; };

  f(); // 报错

(9) 函数必须声明在顶层

将来Javascript的新版本会引入”块级作用域”。为了与新版本接轨,严格模式只允许在全局作用域或函数作用域的顶层声明函数。也就是说,不允许在非函数的代码块内声明函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
  "use strict";

  if (true) {

    function f() { } // 语法错误

  }

  for (var i = 0; i < 5; i++) {

    function f2() { } // 语法错误

  }

(10) 保留字

为了向将来Javascript的新版本过渡,严格模式新增了一些保留字:implements, interface, let, package, private, protected, public, static, yield。

使用这些词作为变量名将会报错。

1
2
3
4
5
6
7
  function package(protected) { // 语法错误

    "use strict";

    var implements; // 语法错误

  }

此外,ECMAscript第五版本身还规定了另一些保留字(class, enum, export, extends, import, super),以及各大浏览器自行增加的const保留字,也是不能作为变量名的。

[3] 语句

(1)语句以一个分号结尾;如果省略分号,则由解释器确定语句的结尾(建议不要忽略)

[4] 关键字和保留字

es3 es5(*标识):

1
2
3
4
5
6
7
8
9
10
11
break	do instanceof typeof

case else new var

catch finally return void

debugger* function this with

default if throw

delete in try

ecma-262还描述了另外一组不能用标识符的保留字。尽管保留字。尽管保留字在这门

es3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
abstract	enum	int short

boolean export interface static

byte extentds long super

char final native synchronized

class float package throws

const goto private transient

debugger implement protected volatile

double import public

es5:

把非严格模式下运行时的保留字缩减为下列这些:

1
2
3
class	enum	extends	super

const export import
1
2
3
4
5
implement	package	public

interface private static

let protected yield

3.语句

(1)分号

Q1: 解释器如何判断是否需自动增加分号:

解释器会判断前后语句是否可以放一起执行,如果可以合并为一句,如果不可以就补全分号,再不行就报错。

特例有如下2个

[1] return breank contiune

不会合并,直接结束。(return后的部分请不要换行)

1
2
3
4
5
6
function a() {
var res = 'aaa';
return
res; // 不会返回aaa

}

[2] ++ –等操作符

不会与上行语句合并,而会与下语句合并

1
2
3
4
5
6
7
8
9
10
var a = 1;

var b = 2;

a
++
b

console.log(a);
console.log(b);

(2)括号

建议能使用则使用

补充(变量类型)

ECMAscript的变量是松散类型的,所谓松散类型就是可以用来保存任何类型的数据。换句话说,每个变量仅仅是用于保存值的占位符而已。 js在定义变量时,不会严格限制类型,而是使用(调用的时候)的时候分配类型

Share

白话分析promise(二) promise规范

promise

图片来源https://objcer.com/2017/05/07/About-Promise/

Promise 规范有很多,如 Promise/A,Promise/B,Promise/D 以及 Promise/A 的升级版 Promise/A+,有兴趣的可以去了解下,最终 ES6 中采用了 Promise/A+ 规范。在讲解 Promise 实现之前,当然要先了解 Promise/A+ 规范。

1.Promise/A规范

Promises/A是由CommonJS组织制定的异步模式编程规范。

Promise及其API规范:

(1)有限状态机

它规定了Promises一个有限状态机,共有三种状态:unfulfilled(执行中)、fulfilled(执行成功)和failed(执行失败)。随着状态的转换将触发各种事件(如执行成功事件、执行失败事件等)。

[1] 状态类型

其中unfulfilled为初始状态,fulfilled和failed为结束状态(结束状态表示promise的生命周期已结束)。

[2] 状态转换关系

状态转换仅为:unfulfilled->fulfilled,unfulfilled->rejected。

[3] 状态不可变

当一次promise的状态已经是fulfilled或failed时,promise状态的值不可被改变

(2)必要Api

该规范没有定义如何创建承诺,但仅定义了承诺必须提供给承诺消费者与其交互的必要接口。实现者可以自由定义如何产生承诺。

[1] then(fulfilledHandler, errorHandler, progressHandler)
1
then(fulfilledHandler, errorHandler, progressHandler);
  • fulfilledHandler: 成功处理
  • errorHandler: 错误处理
  • progressHandler(可选): 获得进度

then函数应该返回一个新的promise,当它给定的fulfilledHandler或者errorHandler回调完成时。这样就允许promise实现链式操作。回调的返回的值是返回的promise的执行后的返回值。如果回调引发错误回调,则返回的promise将移至失败状态。

一个基于Promises A规范使用示例子:

1
2
3
4
>asyncComputeTheAnswerToEverything().
then(addTwo).
then(printResult, onError);
44

交互式promise是一个promise的扩展,它支持以下附加功能(Promise/A+已忽略,所以不详细说明):

[1] get(propertyName)

1
从promise的target中请求指定的属性

[2] call(functionName, arg1, arg2, …)

1
从promise的target中执行相应方法

2.Promise/A+规范

这部分,图灵社区已经有了十分完善的翻译,我就尽量结合翻译的基础上,稍微进行一些简化。

[1] 什么是 Promise/A+规范

定义: 一个可靠的可共同协作的JavaScript Promise开放标准。

简述:PromiseA/+规范,详细列出了 then 方法的执行过程,所有遵循 Promises/A+ 规范实现的 promise 均可以本标准作为参照基础来实施 then 方法。尽管 Promise/A+ 组织有时可能会修订本规范,但主要是为了处理一些特殊的边界情况,且这些改动都是微小且向下兼容的。如果我们要进行大规模不兼容的更新,我们一定会在事先进行谨慎地考虑、详尽的探讨和严格的测试。

从历史上说,本规范实际上是把之前 Promise/A 规范 中的建议明确成为了行为标准:我们一方面扩展了原有规范约定俗成的行为,一方面删减了原规范的一些特例情况和有问题的部分。

最后,核心的 Promises/A+ 规范不涉及如何创建、解决和拒绝 promise,而是专注于提供一个通用的 then 方法。上述对于 promises 的操作方法将来在其他规范中可能会提及。

(1)术语

[1]Promise

promise 是一个拥有 then 方法的对象或函数,其行为符合本规范;

[2]thenable

是一个定义了 then 方法的对象或函数,文中译作“拥有 then 方法”;

[3]值(value)

指任何 JavaScript 的合法值(包括 undefined , thenable 和 promise);

[4]异常(exception)

是使用 throw 语句抛出的一个值。

[5]据因(reason)

表示一个 promise 的拒绝原因。

(2)状态

一个 Promise 的当前状态必须为以下三种状态中的一种:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)。

[1]等待态(Pending)

处于等待态时,promise 需满足以下条件:

  • 可以迁移至执行态或拒绝态
[2]执行态(Fulfilled)

处于执行态时,promise 需满足以下条件:

  • 不能迁移至其他任何状态
  • 必须拥有一个不可变的终值
[3]拒绝态(Rejected)

处于拒绝态时,promise 需满足以下条件:

  • 不能迁移至其他任何状态
  • 必须拥有一个不可变的据因

这里的不可变指的是恒等(即可用 === 判断相等),而不是意味着更深层次的不可变(译者注:盖指当 value 或 reason 不是基本值时,只要求其引用地址相等,但属性值可被更改)。

(3)Then 方法

一个 promise 必须提供一个 then 方法以访问其当前值、终值和据因。

promise 的 then 方法接受两个参数:

1
promise.then(onFulfilled, onRejected)
[1] 参数(可选)

onFulfilled 和 onRejected 都是可选参数。

  • 如果 onFulfilled 不是函数,其必须被忽略
  • 如果 onRejected 不是函数,其必须被忽略

onFulfilled 特性:

如果 onFulfilled 是函数:

  • 当 promise 执行结束后其必须被调用,其第一个参数为 promise 的终值
  • 在 promise 执行结束前其不可被调用
  • 其调用次数不可超过一次

onRejected 特性

如果 onRejected 是函数:

  • 当 promise 被拒绝执行后其必须被调用,其第一个参数为 promise 的据因
  • 在 promise 被拒绝执行前其不可被调用
  • 其调用次数不可超过一次

调用时机

  • onFulfilled 和 onRejected 只有在执行环境堆栈仅包含平台代码时才可被调用 注1

调用要求

  • onFulfilled 和 onRejected 必须被作为函数调用(即没有 this 值 注2

多次调用

then 方法可以被同一个 promise 调用多次

  • 当 promise 成功执行时,所有 onFulfilled 需按照其注册顺序依次回调
  • 当 promise 被拒绝执行时,所有的 onRejected 需按照其注册顺序依次回调

返回

then 方法必须返回一个 promise 对象 注3

1
promise2 = promise1.then(onFulfilled, onRejected);
  • 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:[Resolve]
  • 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
  • 如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
  • 如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因

译者注:理解上面的“返回”部分非常重要,即:不论 promise1 被 reject 还是被 resolve 时 promise2 都会被 resolve,只有出现异常时才会被 rejected。

(4)Promise 解决过程

Promise 解决过程是一个抽象的操作,其需输入一个 promise 和一个值,我们表示为 [Resolve],如果 x 有 then 方法且看上去像一个 Promise ,解决程序即尝试使 promise 接受 x 的状态;否则其用 x 的值来执行 promise 。

这种 thenable 的特性使得 Promise 的实现更具有通用性:只要其暴露出一个遵循 Promise/A+ 协议的 then 方法即可;这同时也使遵循 Promise/A+ 规范的实现可以与那些不太规范但可用的实现能良好共存。

运行 [Resolve] 需遵循以下步骤:

[1]x 与 promise 相等

如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise

[2]x 为 Promise

如果 x 为 Promise ,则使 promise 接受 x 的状态 注4

  • 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝
  • 如果 x 处于执行态,用相同的值执行 promise
  • 如果 x 处于拒绝态,用相同的据因拒绝 promise
[3]x 为对象或函数

如果 x 为对象或者函数:

  • 把 x.then 赋值给 then 注5
  • 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
  • 如果 then 是函数,将 x 作为函数的作用域 this 调用之。传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise:
    • 如果 resolvePromise 以值 y 为参数被调用,则运行 [Resolve]
    • 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
    • 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
    • 如果调用 then 方法抛出了异常 e:
      • 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
      • 否则以 e 为据因拒绝 promise
    • 如果 then 不是函数,以 x 为参数执行 promise
  • 如果 x 不为对象或者函数,以 x 为参数执行 promise

如果一个 promise 被一个循环的 thenable 链中的对象解决,而 [Resolve] 的递归性质又使得其被再次调用,根据上述的算法将会陷入无限递归之中。算法虽不强制要求,但也鼓励施者检测这样的递归是否存在,若检测到存在则以一个可识别的 TypeError 为据因来拒绝 promise 注6

(5)注释

  • 注1 这里的平台代码指的是引擎、环境以及 promise 的实施代码。实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。这个事件队列可以采用“宏任务(macro-task)”机制或者“微任务(micro-task)”机制来实现。由于 promise 的实施代码本身就是平台代码(译者注:即都是 JavaScript),故代码自身在处理在处理程序时可能已经包含一个任务调度队列。

    译者注:这里提及了 macrotask 和 microtask 两个概念,这表示异步任务的两种分类。在挂起任务时,JS 引擎会将所有任务按照类别分到这两个队列中,首先在 macrotask 的队列(这个队列也被叫做 task queue)中取出第一个任务,执行完毕后取出 microtask 队列中的所有任务顺序执行;之后再取 macrotask 任务,周而复始,直至两个队列的任务都取完。

    两个类别的具体分类如下:

    • macro-task: script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering
    • micro-task: process.nextTick, Promises(这里指浏览器实现的原生 Promise), Object.observe, MutationObserver
      详见 stackoverflow 解答 或 这篇博客
  • 注2 也就是说在严格模式(strict)中,函数 this 的值为 undefined ;在非严格模式中其为全局对象。

  • 注3 代码实现在满足所有要求的情况下可以允许 promise2 === promise1 。每个实现都要文档说明其是否允许以及在何种条件下允许 promise2 === promise1 。

  • 注4 总体来说,如果 x 符合当前实现,我们才认为它是真正的 promise 。这一规则允许那些特例实现接受符合已知要求的 Promises 状态。

  • 注5 这步我们先是存储了一个指向 x.then 的引用,然后测试并调用该引用,以避免多次访问 x.then 属性。这种预防措施确保了该属性的一致性,因为其值可能在检索调用时被改变。

  • 注6 实现不应该对 thenable 链的深度设限,并假定超出本限制的递归就是无限循环。只有真正的循环递归才应能导致 TypeError 异常;如果一条无限长的链上 thenable 均不相同,那么递归下去永远是正确的行为。

3.Promise/A+规范和Promise/A规范的不同

这部分主要是翻译了promisesaplus中的文档:

(1)省略的部分

以下Promises/A的部分已经被省略:

[1]省略Progress handling

在实践中,它已被证明是不明确的,目前在社区内的Promise实现中通常都没有具体实现。

[2]省略互操作性相关的Api

互操作性相关的Api被认为超出了最小必要api的范围

[3]不强制要求 promise1 !== promise2

由于可能出现

1
var promise2 = promise1.then(onFulfilled, onRejected)

因此不强制要求promise1 !== promise2

(2)澄清的部分

Promises/A+使用了与Promises/A不同的术语,来表达他们实际上已经在promise中实现了。

具体的是:

[1] Promise状态 规定为 “pending”, “fulfilled”, and “rejected”
[2] 当Promise状态为fulfilled,拥有value,当Promise状态为rejected,拥有reason
[3] 以便于更精确的讨论鸭子类型在互操作实现中的必要性,Promises/A+规范介绍了thenable与promise的不同

(3)附加的部分

Promises/A+ 增加了以下规范:

[1] 在onFulfilled 或 onRejected 返回的thenable,包含了处理程序的细节
[2] 在onRejected状态下的拒因必须是使用 throw 语句抛出的一个值
[3] 要确保 onFulfilled 和 onRejected 方法异步执行
[4] onFulfilled 和 onRejected 必须被作为函数调用
[5] Promises/A+ 严格定义了onFulfilled或者onRejected的调用,以便于随后调用then在相同的Promise中

4.ECMAscript规范下的Promise

ES6的Promise是Promise/A+规范的实现,并且在规范实现基础上额外增加了一些Promise.all和Promise.race之类的简便方法。

Share

白话分析promise(一) 概念及使用

promise

图片来源https://objcer.com/2017/05/07/About-Promise/

Promise是前端目前不论面试还是工作中都应该十分了解的知识,本文希望全面并且简单的能够把promise的概念、使用、原理讲清楚,所以分了为了三篇。

这第一篇使用基本就是一些我自己的概念理解,以及许多部分搬运阮一峰大大的文章,将一部分最核心的篇幅,重新学习,并且记录了一下。

一、什么是promise

文档解读:

1
2
3
Promises/A+ 规范中表示为一个异步操作的最终结果,ECMAscript 规范定义为延时或异步计算最终结果的占位符。

严谨来讲,Promise 是一种封装和组合未来值得易于复用机制,实现关注点分离、异步流程控制、异常冒泡、串行/并行控制等。

白话解读:

  1. Promise是异步编程的一种解决方案
  2. Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果
  3. 从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

二、Promise使用

1. 基本用法

(1) 创造了一个Promise实例
1
2
3
4
5
6
7
8
9
const promise = new Promise(function(resolve, reject) {
// ... some code

if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
(2) 指定回调函数
1
2
3
4
5
promise.then(function(value) {
// success
}, function(error) {
// failure
});

then方法可以接受两个回调函数作为参数。

[1]第一个回调函数是Promise对象的状态变为resolved时调用,

[2]第二个回调函数是Promise对象的状态变为rejected时调用。

[3]其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。

[4]Promise 新建后就会立即执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});

promise.then(function() {
console.log('resolved.');
});

console.log('Hi!');

// Promise
// Hi!
// resolved

[5]调用resolve或reject并不会终结 Promise 的参数函数的执行。

1
2
3
4
5
6
7
8
new Promise((resolve, reject) => {
resolve(1);
console.log(2);
}).then(r => {
console.log(r);
});
// 2
// 1

上面代码中,调用resolve(1)以后,后面的console.log(2)还是会执行,并且会首先打印出来。这是因为立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。

一般来说,调用resolve或reject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。

1
2
3
4
5
new Promise((resolve, reject) => {
return resolve(1);
// 后面的语句不会执行
console.log(2);
})
(3) promise嵌套

果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数。reject函数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值以外,还可能是另一个 Promise 实例,比如像下面这样。

1
2
3
4
5
6
7
8
const p1 = new Promise(function (resolve, reject) {
// ...
});

const p2 = new Promise(function (resolve, reject) {
// ...
resolve(p1);
})

这时p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行。

2. api讲解

(1)Promise.prototype.then()

它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。

then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

1
2
3
4
5
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// ...
});
(2)Promise.prototype.catch()

Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。

1
2
3
4
5
6
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});

如果 Promise 状态已经变成resolved,再抛出错误是无效的。

1
2
3
4
5
6
7
8
const promise = new Promise(function(resolve, reject) {
resolve('ok');
throw new Error('test');
});
promise
.then(function(value) { console.log(value) })
.catch(function(error) { console.log(error) });
// ok
(3)Promise.prototype.finally()

finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。

1
2
3
4
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

上面代码中,不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
promise
.finally(() => {
// 语句
});

// 等同于
promise
.then(
result => {
// 语句
return result;
},
error => {
// 语句
throw error;
}
);

实现:

1
2
3
4
5
6
7
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};

从上面的实现还可以看到,finally方法总是会返回原来的值。

1
2
3
4
5
6
7
8
9
10
11
// resolve 的值是 undefined
Promise.resolve(2).then(() => {}, () => {})

// resolve 的值是 2
Promise.resolve(2).finally(() => {})

// reject 的值是 undefined
Promise.reject(3).then(() => {}, () => {})

// reject 的值是 3
Promise.reject(3).finally(() => {})
(4)Promise.all()

Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

1
const p = Promise.all([p1, p2, p3]);

p1、p2、p3都是 Promise 实例,如果不是,就会先调用Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。)

p的状态由p1、p2、p3决定,分成两种情况。

[1]只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

[2]只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

(5)Promise.race()

Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

1
const p = Promise.race([p1, p2, p3]);
(6)Promise.resolve();

Promise.resolve方法的参数分成四种情况。

[1]参数是一个 Promise 实例

如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。

[2]参数是一个thenable对象

thenable对象指的是具有then方法的对象,比如下面这个对象。

1
2
3
4
5
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};

Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。

1
2
3
4
5
6
7
8
9
10
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};

let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});

[3]参数不是具有then方法的对象,或根本就不是对象

如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。

1
2
3
4
5
6
const p = Promise.resolve('Hello');

p.then(function (s){
console.log(s)
});
// Hello

上面代码生成一个新的 Promise 对象的实例p。由于字符串Hello不属于异步操作(判断方法是字符串对象不具有 then 方法),返回 Promise 实例的状态从一生成就是resolved,所以回调函数会立即执行。Promise.resolve方法的参数,会同时传给回调函数。

[4]不带有任何参数

1
2
3
4
5
const p = Promise.resolve();

p.then(function () {
// ...
});

上面代码的变量p就是一个 Promise 对象。

需要注意的是,立即resolve的 Promise 对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。

1
2
3
4
5
6
7
8
9
10
11
12
13
setTimeout(function () {
console.log('three');
}, 0);

Promise.resolve().then(function () {
console.log('two');
});

console.log('one');

// one
// two
// three

上面代码中,setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.resolve()在本轮“事件循环”结束时执行,console.log(‘one’)则是立即执行,因此最先输出。

(7)Promise.reject()

Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。

1
2
3
4
5
6
7
8
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))

p.then(null, function (s) {
console.log(s)
});
// 出错了

上面代码生成一个 Promise 对象的实例p,状态为rejected,回调函数会立即执行。

注意,Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。这一点与Promise.resolve方法不一致。

(8)Promise.try()

[1] 无法或不想区分,数f是同步函数还是异步操作

1
Promise.resolve().then(f)

[2] 让同步函数同步执行,异步函数异步执行,并且让它们具有统一的 API

(1) async函数来写

1
2
3
4
5
const f = () => console.log('now');
(async () => f())();
console.log('next');
// now
// next

上面代码中,第二行是一个立即执行的匿名函数,会立即执行里面的async函数,因此如果f是同步的,就会得到同步的结果;如果f是异步的,就可以用then指定下一步,就像下面的写法。

1
2
(async () => f())()
.then(...)

需要注意的是,async () => f()会吃掉f()抛出的错误。所以,如果想捕获错误,要使用promise.catch方法。

1
2
3
(async () => f())()
.then(...)
.catch(...)

(2) 第二种写法是使用new Promise()

1
2
3
4
5
6
7
8
9
const f = () => console.log('now');
(
() => new Promise(
resolve => resolve(f())
)
)();
console.log('next');
// now
// next

上面代码也是使用立即执行的匿名函数,执行new Promise()。这种情况下,同步函数也是同步执行的。

(3) Promise.try

事实上,Promise.try存在已久,Promise 库Bluebird、Q和when,早就提供了这个方法。

由于Promise.try为所有操作提供了统一的处理机制,所以如果想用then方法管理流程,最好都用Promise.try包装一下。这样有许多好处,其中一点就是可以更好地管理异常。

1
2
3
4
5
6
7
try {
database.users.get({id: userId})
.then(...)
.catch(...)
} catch (e) {
// ...
}

上面这样的写法就很笨拙了,这时就可以统一用promise.catch()捕获所有同步和异步的错误。

1
2
3
Promise.try(database.users.get({id: userId}))
.then(...)
.catch(...)

事实上,Promise.try就是模拟try代码块,就像promise.catch模拟的是catch代码块。

Share

6月第一周趣问

这周开始想不定期更新一个新板块,就是把平时开发或者听到的有趣的面试题汇总起来,这周听到组里一前端同事面试中遇到了几个有趣的问题先行更新下:

1.div居中,内部文字左对齐

光看标题无法理解这题目的意思,详细描述一下的该题:

一个div容器,里面的字体少了居中,多了的话够一行的沾满一行,剩下的靠左,以下是图示:

1
2
3
4
|  xxx  |

|xxxxxxxx|
|xxx |

主要思路其实就是2个容器,子容器设置为inline-block,并且内容居左,父容器居中,以下是代码实现:

1
2
3
4
5
6
7
8
9
.container {
text-align: center;
}

.text {
display: inline-block;
text-align: left;
word-break: break-all;
}

2.普通函数和剪头函数的区别

(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

普通函数:

1
2
3
4
5
6
7
8
9
10
11
function foo2() {
console.log(this); // foo
setTimeout(function () {
console.log(this); // window
console.log('id:', this.id); // 21
}, 100);
}

var id = 21;

foo2.call({ id: 42 });

箭头函数:

1
2
3
4
5
6
7
8
9
10
11
function foo1() {
console.log(this); // foo
setTimeout(() => {
console.log(this); // foo
console.log('id:', this.id); // 42
}, 100);
}

var id = 21;

foo1.call({ id: 42 });

(2)箭头函数不会创建自己的this,而是默认绑定函数的宿主对象(或者绑定函数的对象)

箭头函数里面根本没有自己的this,而是引用外层的this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function foo() {
return () => {
return () => {
return () => {
console.log('id:', this.id);
};
};
};
}

var f = foo.call({id: 1});

var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
var t3 = f()().call({id: 4}); // id: 1

上面代码之中,只有一个this,就是函数foo的this,所以t1、t2、t3都输出同样的结果。因为所有的内层函数都是箭头函数,都没有自己的this,它们的this其实都是最外层foo函数的this。

(3)如果有对象嵌套的情况,则this绑定到最近的一层对象上

(4)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

(5)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

(6)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

(7)除了this,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:arguments、super、new.target

1
2
3
4
5
6
7
8
function foo() {
setTimeout(() => {
console.log('args:', arguments);
}, 100);
}

foo(2, 4, 6, 8)
// args: [2, 4, 6, 8]

上面代码中,箭头函数内部的变量arguments,其实是函数foo的arguments变量。

(8) 无法使用bind、call、apply改变指向

1
2
3
4
5
6
(function() {
return [
(() => this.x).bind({ x: 'inner' })()
];
}).call({ x: 'outer' });
// ['outer']

3.尾递归

这个我准备写一个单独的文章:

白话讲解尾递归

4.react组件多继承

(1)Mixins

(2)HOC

5.git revert

(1)revert 详解

git revert 撤销 某次操作,此次操作之前和之后的commit和history都会保留,并且把这次撤销
作为一次最新的提交。

1
2
3
git revert HEAD                # 撤销前一次 commit
git revert HEAD^ # 撤销前前一次 commit
git revert commit (比如:bca867bcbae6e1942ef89423506777fb337fb651)# 撤销指定的版本,撤销也会作为一次提交进行保存。

git revert是提交一个新的版本,将需要revert的版本的内容再反向修改回去,
版本会递增,不影响之前提交的内容


(2)git revert 和 git reset的区别

[1] git revert是用一次新的commit来回滚之前的commit,git reset是直接删除指定的commit。

[2] 在回滚这一操作上看,效果差不多。但是在日后继续merge以前的老版本时有区别。因为git revert是用一次逆向的commit“中和”之前的提交,因此日后合并老的branch时,导致这部分改变不会再次出现,但是git reset是之间把某些commit在某个branch上删除,因而和老的branch再次merge时,这些被回滚的commit应该还会被引入。

[3] git reset 是把HEAD向后移动了一下,而git revert是HEAD继续前进,只是新的commit的内容和要revert的内容正好相反,能够抵消要被revert的内容。

Share

结合代码白话理解javascript中的观察者模式与发布订阅

1 概念解释:

(1)观察者设计模式

标准解释:
1
观察者模式: 是一种设计模式,在软件设计中往往是一个对象,维护一个依赖列表,当任何状态发生改变自动通知它们。

来源:维基百科

图示:
1
2
3
4
5
╭─────────────╮  Fire Event  ╭──────────────╮
│ │─────────────>│ │
│ Subject │ │ Observer │
│ │<─────────────│ │
╰─────────────╯ Subscribe ╰──────────────╯
白话讲解:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
假设你想去xx公司应聘前端工程师,但xx公司hr告知你目前没有岗位空缺,你向留下了联系方式,让其在有岗位空缺时,通知你。当然公司还会通知同样已留下联系方式的其他候选者。

Observers(观察者): 候选人

Subject(被观察者): xx公司

Fire Event (事件): xx公司有了新前端空缺

Subscribe(订阅): 候选人将联系方式留给了xx公司

Notify (通知) : xx公司向候选人致电

观察者模式运行流程:

Observers -> Subscribe -> Subject -> Fire Event -> Notify -> Observers

(2)发布订阅模式

标准解释:
1
2
3
4
5

发布订阅模式属于广义上的观察者模式,发布-订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在。同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(如果有的话)存在。


发布订阅模式是最常用的一种观察者模式的实现,并且从解耦和重用角度来看,更优于典型的观察者模式

来源:维基百科

图示:
1
2
3
4
5
╭─────────────╮                 ╭───────────────╮   Fire Event   ╭──────────────╮
│ │ Publish Event │ │───────────────>│ │
│ Publisher │────────────────>│ Event Channel │ │ Subscriber │
│ │ │ │<───────────────│ │
╰─────────────╯ ╰───────────────╯ Subscribe ╰──────────────╯
白话讲解:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- 你想成为一个前端工程师,但目前没有明确目标,你可以联系了猎头让其有前端相关岗位时通知你。

- 猎头可能与多家公司都有合作,这些公司发布职位会告知猎头

- 这个过程xx公司,只需要根据自身情况,只需要根据自己实际的岗位情况,告知猎头自己需要什么岗位的候选人,无需知道有哪些具体候选人,候选人在订阅过程中,只是向猎头公司订阅了有前端相关岗位时通知自己


Publisher(发布者): xx公司

Publish Event(发布事件): 发布职位

Event Channel (事件通道): 猎头公司

Fire Event(事件): xx公司发布职位后,猎头公司根据职位发送联系相应岗位候选人

Subscriber(订阅者): 候选人

Subscribe(订阅): 候选人将联系方式留给hr



运行流程:

Subscriber -> Subscribe -> Event Channel

Publisher -> Publish Event -> Event Channel -> Fire Event -> Subscriber

2 观察者模式和发布订阅模式有什么不同:

发布订阅模式属于广义上的观察者模式。发布订阅模式是最常用的一种观察者模式的实现,并且从解耦和重用角度来看,更优于典型的观察者模式。

区别:

  • 发布订阅模式多了个事件通道

在观察者模式中,观察者需要直接订阅目标事件;在目标发出内容改变的事件后,直接接收事件并作出响应

  • 在发布订阅模式中,发布者和订阅者之间多了一个发布通道;一方面从发布者接收事件,另一方面向订阅者发布事件;订阅者需要从事件通道订阅事件

以此避免发布者和订阅者之间产生依赖关系。

3. 观察者模式代码实现

使用观察者模式的好处:

  • 支持简单的广播通信,自动通知所有已经订阅过的对象。
  • 页面载入后目标对象很容易与观察者存在一种动态关联,增加了灵活性。
  • 目标对象与观察者之间的抽象耦合关系能够单独扩展以及重用。

最简单观察者模式在js中的运用

1
2
3
document.body.addEventListener('click', function () {
console.log('body 被观察')
});

观察者模式实现数据打包下载功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// 观察者模式 实现下载任务

/**
* 观察者
* @param id
* @constructor
*/

function DownloadTask(id) {
this.id = id;
this.loaded = false;
this.url = null;
}

DownloadTask.prototype.finish = function(url) {
this.loaded = true;
this.url = url;
console.log('Task ' + this.id + ' load data from ' + url);
};

/**
* 被观察者
* @constructor
*/

function DataHub() {
this.downloadTasks = []; // 观察者列表
}

DataHub.prototype.addDownloadTask = function(downloadTask) {
this.downloadTasks.push(downloadTask);
};

DataHub.prototype.removeDownloadTask = function(downloadTask) {
this.downloadTasks.filter(function (item) {
if (item === downloadTask) {

}
})
// this.downloadTasks.remove(downloadTask);
};

DataHub.prototype.notify = function(url) {
const downloadTaskCount = this.downloadTasks.length;
for (var i = 0; i < downloadTaskCount; i++) {
this.downloadTasks[i].finish(url);
}
};

// 使用

var d = new DataHub();

var task1 = new DownloadTask(1);
var task2 = new DownloadTask(2);

d.addDownloadTask(task1);
d.addDownloadTask(task2);

d.removeDownloadTask(task1);

d.notify('http://somedomain.someaddress');

4. 发布订阅代码实现

  • 发布订阅模式属于广义上的观察者模式
1
发布订阅模式是最常用的一种观察者模式的实现,并且从解耦和重用角度来看,更优于典型的观察者模式
  • 发布订阅模式多了个事件通道
1
在观察者模式中,观察者需要直接订阅目标事件;在目标发出内容改变的事件后,直接接收事件并作出响应
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 发布者
function Observer () {
this.fnList = []; // 订阅者列表
}

Observer.prototype = {
subscribe: function (fn) { // 增加订阅者
this.fnList.push(fn);
},

unsubscribe: function (fn) { // 移除订阅者
this.fnList = this.fnList.filter(function (el) {
if (el !== fn){
return el;
}
})
},

notify: function (p, thisObj) { //发布消息函数
var scope = thisObj || window;
this.fnList.forEach(
function (el) {
el.call(scope, o);
}
);
console.log(this.fnList);
}

};

var o = new Observer;

var f1 = function (data) {
console.log('Robbin: ' + data + ', 赶紧干活了!');
};

var f2 = function (data) {
console.log('Randall: ' + data + ', 找他加点工资去!');
};


o.subscribe(f1);
o.subscribe(f2);

o.notify("Tom回来了!");

//退订f1
o.unsubscribe(f1);
//再来验证
o.notify("Tom回来了!");

发布定远模式实现数据打包下载功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 发布者

function DataHub() {}

DataHub.prototype.notify = function(url, callback) {
callback(url);
}


// 事件通道

function DownloadManager() {
this.events = {};
this.uId = -1;
}

DownloadManager.prototype.publish = function(eventType, url) {
if (!this.events[eventType]) {
return false;
}
var subscribers = this.events[eventType],
count = subscribers ? subscribers.length : 0;
while (count--) {
var subscriber = subscribers[count];
subscriber.handler(eventType, subscriber.taskId, url);
}
}

DownloadManager.prototype.subscribe = function(eventType, handler) {
if (!this.events[eventType]) {
this.events[eventType] = [];
}
var taskId = (++this.uId).toString();
this.events[eventType].push({
taskId: taskId,
handler: handler
});

return taskId;
}

// 创建一个数据中心
var dataHub = new DataHub();

// 创建一个下载事件管理器
var downloadManager = new DownloadManager();

// 创建一个下载器
var dataLoader = function(eventType, taskId, url) {
console.log('Task ' + taskId + ' load data from ' + url);
}

// 用户来请求数据了
var downloadTask1 = downloadManager.subscribe('dataReady', dataLoader);

// 数据打包完成了

dataHub.notify('http://somedomain.someaddress', function(url){
downloadManager.publish('dataReady', url);
});
Share

js复习之基础概念以及script

一、js复习之前置概念

1.js历史及以前名词

  • ECMA(European Computer Manufactures Associatio)

    1
    欧洲计算机制造联合会
  • TC39(Technical Committee #39)

    1
    ECMA指派负责“标准化一种通用、跨平台、供应商中立的脚本语言的语法和语意”的39号技术委员会
  • ECMA-262

    1
    基于以javascript1.1为蓝本的新脚本语言标准
  • ECMAScript

    1
    由ECMA-262标准定义的脚本语言(通常都被人们用来表达相同的含义)

2.js实现

虽然JavasScript和ECMAScript都用来表达相同含义,但一个完整的浏览器端js应实现3个不同的部分

  • 核心(ECMAScript)
  • 文档对象模型(DOM)
  • 浏览器对象模型 (BOM)

(1) ECMAScript

[1] ECMAScript环境

  • Web浏览器只是ECMAScript实现可能的宿主环境之一,宿主环境不仅提供基本的ECMA实现,同时也提供该语言的扩展,以便语言和环境对接交互。
  • 其他宿主环境包括Node和Adobe Flash
  • ECMAScript就是对实现该标准规定的各方面内容的语言环境 。JavaScript实现了ECMAScript, Adobe ActionScript也实现了ECMAScript。

[2] ECMAScript的实现

根据EMCA-262对ECMAScript兼容的定义,想要成为ECMAScript的实现,则该实现必须做到:

(1)支持ECMA262描述的所有类型、值、对象、属性、函数以及程序句法、语义

1
包括:语法、类型、语句、关键字、保留字、操作符、对象

(2)全面支持Unicode字符标准

[3] ECMAScript兼容

此外,兼容实现还可以进行下列扩展。

(1)添加ECMA-262没有描述的“更多类型、值、对象、属性和函数”。ECMA-262所说的这些新增特性,主要是指标该标准没有规定的新对象和对象的新属性

(2)支持ECMA-262定义的“程序和正则表达式语法”。(也就是说,可以修改和扩展正则表达式语法)

[4] ECMAScript的版本

(1) ECMAScript 262
1
1997年,基于javascript 1.1
(2) ECMAScript 2
1
1998年,基于javascript 1.1和
(3) ECMAScript 3
1
1999年
(4) ECMAScript 4 (未完成)
(5) ECMAScript 5.1 (或仅 ES5) (目前主流浏览器大多支持这个标准)
(7) ECMAScript 6 es2015

新增特性

  • let const
  • Symbol
  • Set Map
  • Iterator for…of
  • class
  • 箭头函数
  • extend
  • promise
  • decorator
  • Module
(8) ECMAScript 7(有种说法是es6.1) es2016
  • Array.prototype.includes
  • Exponentiation Operator
    只新增了数组实例的includes方法和指数运算符
(9) ECMAScript 8 es2017
  • Object.values()
  • Object.entries()
  • padStart()
  • padEnd()
  • Object.getOwnPropertyDescriptors()
  • 函数参数列表结尾允许逗号
  • Async/Await

文档说(es8 es2017 实验阶段)

  • property initializers
(9) ECMAScript 9 es2018
  • 可观察性(Observables),响应式架构的关键 (Dojo 2 的 可观察特性)

  • 全局性,一种用来一致地引用全局对象的标准 (Dojo 2 中的全局特性)

  • trimStart/trimEnd, 类似于 padStart/padEnd (估计是批量空格填充什么的,回看特性2,译者注)

  • import, 一种 ES 模块标准中没有的动态 import 机制

  • 类和属性装饰器 (已经在 TypeScript 中实现)

  • 异步迭代 (Asynchronous iteration)

  • 单指令多数据 (SIMD,Single Instruction Multiple Data), 它能够使得渲染和使用像 WebGL, Canvas 之类的动画加速

[5] 浏览器对ECMAScript支持情况

  • ECMAScript 3
1
chrome 1+ Firefox 3.0x Sarfari3.x Opera7.2+ ie7 Netscap 6
  • ECMAScript 5
1
IE8部分支持 IE9支持 Firefox4.0+
  • ECMAScript 6
1
支持情况可查询:http://kangax.github.io/compat-table/es6/

[6] ECMAScript更新流程

任何人都可以向标准委员会(又称 TC39 委员会)提案,要求修改语言标准。

一种新的语法从提案到变成正式标准,需要经历五个阶段。每个阶段的变动都需要由 TC39 委员会批准。

  • Stage 0 - Strawman(展示阶段)
  • Stage 1 - Proposal(征求意见阶段)
  • Stage 2 - Draft(草案阶段)
  • Stage 3 - Candidate(候选人阶段)
  • Stage 4 - Finished(定案阶段)

一个提案只要能进入 Stage 2,就差不多肯定会包括在以后的正式标准里面。ECMAScript 当前的所有提案,可以在 TC39 的官方网站Github.com/tc39/ecma262查看。

(2) DOM文档对象模型

[1] 概念

文档对象模型是针对XML但经过扩展用于HTML的应用程序编程接口。数结构

[2] DOM级别

(0) DOM 0

当阅读与DOM有关的材料时,可能会遇到参考0级DOM的情况。需要注意的是并没有标准被称为0级DOM,它仅是DOM历史上一个参考点(0级DOM被认为是在Internet Explorer 4.0 与Netscape Navigator4.0支持的最早的DHTML)。

(1) DOM 1

映射文档结构为目标

DOM 1由两个模块组成:DOM核心(DOM Core)和 DOM HTML

  • DOM Core: DOM核心规定的是如何映射基于XML的文档结构,以便简化对文档中任意部分的访问和操作。

  • DOM HTML: DOM核心的基础上加以扩展,添加了针对HTML的对象和方法。

(2) DOM 2

  • DOM Views (DOM视图):定义了跟踪不同文档(例如:应用CSS之前和之后的文档)视图的接口;
  • DOM Events(DOM事件):定义了事件和事件处理的接口
  • DOM Style (DOM样式):定义了基于CSS为元素应用样式的接口;
  • DOM Traversal and Range (DOM遍历和范围):定义了遍历和操作文档树的接口。

(3) DOM 3

  • 引入了以统一方式加载和保存文档的方法——在DOM加载和保存(DOM Load and Save)模块中定义;
  • 新增了验证文档的方法——在DOM验证(DOM Validation)模块定义。
  • DOM3级也对DOM核心进行了扩展,开始支持XML1.0规范,涉及XML Infoset、Xpath和 XML Base。

(4) 其他DOM标准

除了DOM核心和DOM HTML接口之外,另外几种语言还发布只针对自己的DOM标准。下面列出的语言都是基于XML的,每种语言的DOM标准都添加了与特定语言相关的新方法和新接口

W3C:

  • SVG (Scalable Vector Graphic, 可伸缩矢量图) 1.0
  • MathML (Mathematical Markup Language,数学标记语言) 1.0
  • SMIL (Synchronized Multimedia Integration Language, 同步多媒体集成语言)。

Mozillas:

  • XUL: (XML User Interface Language, XML用户界面语言)。

[3] DOM支持

  • Chrome 1+

    1
    Dom: 1级 2级
  • Firefox

    1
    Dom: 1级 2级 3级
  • ie2 ~ ie4.x

    1
    DOM: -
  • ie5

    1
    DOM: 1级 (最小限度)
  • ie5.5 ~ ie8

    1
    DOM: 1级 (几乎全部)
  • ie9+

    1
    DOM: 1级 2级 3级

(3) BOM浏览器对象模型

BOM(Browser Object Model)浏览器对象模型:

  • HTML5之前没有标准,HTML5把很多BOM功能写入正式规范
  • 从根上BOM只处理浏览器窗口和框架
  • 但是习惯上对浏览器的的Javascript扩展算作BOM的一部分,包括

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    > 弹出新浏览器窗口的功能

    > 移动、缩放和关闭浏览器窗口的功能

    > 提供浏览器详细信息的navigator对象

    > 提供浏览器所加载页面的详细信息的location对象

    > 提供用户显示器分辨率详细信息的screen对象

    > 对cookies的支持

    > 像XMLHttpRequest 和 IE的 ActiveXObject这样的自定义对象

2.script

(1) script六个属性

async(可选)

1
立即下载,但不妨碍页面中的其他操作,比如下载其他资源或等待加载其他脚本 只对外部脚本文件有效。

charset(可选)

1
指定src属性的代码字符集,通常会被大多数浏览器忽略,可以用来解决乱码问题。

defer (可选)

1
表示脚本可以延迟到文档完全被解析显示之后再执行。只对外部脚本有效

language (已弃用)

1
表示编写代码使用的脚本语言(如:Javascript、Javascript1.2或VBScript),已废弃。

src (可选)

1
表示包含要执行代码的外部文件。

type (可选)(不是必须默认值text/javascirpt)

1
2
3
4
5
6
> 可以看作language的替代属性;表示编写代码使用的脚本语言的内容类型(也称为MIME类型)。
> text/javascirpt text/ecmascirpt都已经不被推荐使用。

> 实际上服务端传递javascirpt文件时使用的MIME类型通常是application/x-javascirpt,但是type设置该值可能会导致被忽略。

> 另外,在非ie下可以设置: application/javascript和application/ecmascript(不推荐)

(2) 使用标签的一些注意事项

[1] script闭标签

  • 不要在代码任何地方出现script闭标签
  • 不要出现以下形式,某些浏览器(尤其是ie)的正确解析

    1
    <script type="text/javascript" src="example.js" />

[2] 标签位置

  • body尾部
1
这样在解析包含的javascirpt代码之前,页面内容将完全呈现在浏览器中。从而减少页面空白时间。
1
2
<body>
<!--这里放内容--> <script type="text/javascript" src="example1.js"></script> <script type="text/javascript" src="example2.js"></script> </body>
  • 需要在DOM加载解析之前执行的脚本

    1
    这样意味着必须等到全部javascirpt下载、解析、执行完成以后,才能开始呈现页面内容(浏览器在遇到body标签菜开始呈现内容),会增加页面空白时间
1
<head>
   <title>Example HTML Page</title>
   <script type="text/javascript" src="example1.js"></script>
   <script type="text/javascript" src="example2.js"></script>
</head>

(3) 延迟脚本(defer)

html 4.01 script标签定义了defer属性。

  • 相当于告知浏览器立即下载,但延迟执行
  • 脚本会被延迟到整个页面都解析完毕后再运行
1
2
<!DOCTYPE html>
<html>
<head>
<title>Example HTML Page</title>
<script type="text/javascript" defer="defer" src="example1.js"></script> <script type="text/javascript" defer="defer" src="example2.js"></script>
  </head>
  <body>
<!-- 内容 --> </body> </html>

本例子中,虽然我们把script元素放在了文档的head元素中,但其中包含的脚本将延迟到浏览器解析html的闭标签后再执行。

  • HTML5规范要求它们出现的执行顺序因此:

    1
    2
    3
    4
    5
    example1.js会先于example2,并两者都会先于DOMContentLoaded事件

    但实际中,延迟脚本并不一定按顺序执行,也不一定在DOMContentLoaded事件前执行,因此最好只包含一个延迟脚本

    HTML5会忽略内部脚本使用defer,ie4-ie7支持内部脚本defer,但请尽量拥抱标准。

(4) 异步脚本(async)

async的属性目的是不让页面等待脚本文件之前下载和执行,从而异步加载页面其他内容,建议异步脚本不要在加载期间修改DOM。

  • 与defer不同,标记为async的脚本不保证它们的先后顺序执行,因此两者互不依赖非常重要。
  • 异步脚本一定会在页面的load事件前执行,但可能会在DOMContentLoaded事件触发之前或之后执行
  • 支持异步浏览器firefox 3.6 chrome safari5。

(5) 嵌入脚本和外部脚本

一般建议使用外部脚本

优点如下:

  • 可维护性强
  • 可缓存
  • 适应未来

(6) 文档模式

(1)混杂模式

  • 没有放入<!doctype html> (没有文档类型声明,则所有浏览器都会默认开启混杂模式)

让IE行为与IE5相同

(2)标准模式

1
<!-- HTML 4.01 严格型    -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<!-- XHTML 1.0 严格型    -->
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- HTML 5 -->
<!DOCTYPE html>

使浏览器行为更接近标准行为

(3)准标准模式

  • 准表示模式,则可以通过使用过渡型(Transitional)或框架集型(Frameset)文档类型来触发
1
<!-- HTML 4.01  过渡型   -->
<!DOCTYPE HTML PUBLIC
"-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
8
   <!-- HTML 4.01 框架集型     -->
<!DOCTYPE HTML PUBLIC 9 "-//W3C//DTD HTML 4.01 Frameset//EN"
"http://www.w3.org/TR/html4/frameset.dtd">
  <!-- XHTML 1.0  过渡型    -->
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!-- XHTML 1.0  框架集型    -->
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">

准标准模式与标准模式非常接近,它们差异几乎可以忽略不计。

(7) noscript元素

noscirpt标签只有在下列情况会被显示出来:

  • 浏览器不支持脚本
  • 浏览器支持脚本,但脚本被禁用

作用:

  • 在不支持javascirpt的浏览器中显示替代的内容
Share

改变学习方法

作者:Neo_Huang

1.前言

最近想改变一下自己学习编程以及计算机相关知识的学习方法。

信息大爆炸时代最显著的特征就是:信息越多,有效越少,于是觉得一个科学的学习方法对自己十分重要,故进行如下总结。

2.自控力以及时间管理

(1) 关于学习时间分配

[1] 7:45 至 8:45 (路途学习一小时)

以音频课程与行业新知识为主

[2] 9:00 至 9:25 (review一天工作任务)

这个阶段用note,将每日任务以及目标进行划分

[3] 9:30 至 12:00 (处理工作)

[4] 1:00 至 1:25 (午休)

[5] 1:25 至 2:25 (学习一小时,一道算法题,提高算法能力,不管是否能够完成)

[6] 2:25 至 4:55 (工作时间)

[7] 5:00 至 6:15 (学习一小时)

[8] 8:00 至 9:30 (学习一个半小时)

[9] 10:30 至 11:00 (review一天学习内容)

(2) 关于学习时间集中

(1) 以1小时为单位25分钟为一个工作时间块

(2) 分配可打断时间为10分钟,并且有5分钟可以适当休息

(3) 关于新闻与知识的权衡

(1) 保证每天浏览业界新闻,但不超过1个25分钟

(2) 若浏览业界新闻中,发现有需要深度学习的知识可排入学习计划中

(4) 关于学习目标

(1) 每周制定一周学习计划

(2) 每日9:00 至 9:25阶段制定每日学习目标与计划 (可适当根据周计划来调整)

3.学习方法

具体学习阶段,我自己目标将学习分为三个阶段:

  • 分析知识点,结构阶段
  • 学习、理解阶段
  • 复习阶段

如下根据一些管用的学习技巧,我总结了一下自己准备贯彻的学习方法。

(1)“第一性原理”思考法 (分析知识点,结构阶段)

第一原理的思考方式是用物理学的角度看待世界的方法,也就是说一层层剥开事物的表象,看到里面的本质,然后再从本质一层层往上走。

目前我将其尽量应用到我的学习方法中,具体应用:

[1] 学习知识前分析与拆分问题

应用: 大体浏览后创建思维导图或大纲

[2] 从底层向上解析问题,并尝试理解,但请对问题的底层和上层建立一个上限

通过搜索引擎,以及相关资料在一个时间块(即使25分钟内暂定一个上限下限,避免无尽的仅在探索上下限,而无法开展真正学习,当然可适当在学习中调整上下限)

[3] 深度理解问题,不要尝试记忆,而要尝试去理解;如果你理解了,就自动记住了

在结构完并且安排完自己想要学习的知识后,可以采用费恩曼技巧去学习、理解从而达到记忆问题的效果。

(2)费恩曼技巧 (学习、理解阶段)

其实应该本质上来说:是以大妈级的语言来解释一些专业上的问题,用我们日常随处可见的现象来解释专业上的问题。总之:他是用极其具象的东西来回答高度抽象的东西。

实际应用下来主要四个部分:

1. 确定学习目标(通过:第一性原理)

2. 模拟教学学习法

以教促学,我自己将这个阶段再拆解一下

[1] 说给八十岁的老太太听,说给八岁的小孩子听(简明解释)

[2] 举例子

[3] 假设提问

3. 学习中的回顾(为避免与之后复习混淆)

如果你感觉卡壳了, 就回顾一下学习资料,无论何时你感觉卡壳了, 都要回到原始的学习资料并重新学习让你感到卡壳的那部分, 直到你领会得足够顺畅, 不要仅仅是在头脑中假想, 直接把自己要如何向别人讲解 “费曼技巧” 的过程简要地写下来/说出来。

4. 简化归纳

为了让你的讲解通俗易懂,简化语言表达,最终的目的, 是用你自己的语言, 而不是学习资料中的语言来解释概念。完成这个阶段可以理解成基本学会了,可以进入复习阶段了。

(3)艾宾浩斯记忆法(复习阶段)

信息输入大脑后,遗忘也就随之开始了。遗忘率随时间的流逝而先快后慢,特别是在刚刚识记的短时间里,遗忘最快,这就是著名的艾宾浩斯遗忘曲线。遵循艾宾浩斯遗忘曲线所揭示的记忆规律,对所学知识及时进行复习,这种记忆方法即为艾宾浩斯记忆法。

其实应用来说就是针对遗忘曲线进行回顾和自测,目前针对这个我自己将其分为四回顾四自测:

为避免过度追求学习方法,其实就是进行8轮重复学习,每1轮回顾后进行1次自测,一共8次,历时半个月完成。

  1. 第一个记忆周期:5分钟 (进行一轮回顾)
  2. 第二个记忆周期:30分钟 (进行一轮自测)
  3. 第三个记忆周期:12小时 (进行一轮回顾)
  4. 第四个记忆周期:1天 (进行一轮自测)
  5. 第五个记忆周期:2天 (进行一轮回顾)
  6. 第六个记忆周期:4天 (进行一轮自测)
  7. 第七个记忆周期:7天 (进行一轮回顾)
  8. 第八个记忆周期:15天(进行一轮自测)

总结

(1) 严格按学习计划执行。

(2)将学习时间分成25分钟为1块进行管理,增加集中力。

(3)每天用25分钟收集业界内的新知识,若无法短时间消化的知点,可以排入未来学习计划中

(4)每天使用25分钟安排每日学习计划,每周花一个25分钟制定周学习计划

(5)将学习步骤拆分为如下3种,并根据第一原理、费曼技巧以及艾宾浩斯记忆法执行

  • 分析知识点,结构阶段
  • 学习、理解阶段
  • 复习阶段
Share

2018年学习大纲

作者:Neo_Huang

学习大纲

1. js

2. 正则

  • 参考1: 《JavaScript正则表达式迷你书(1.1版》

3. html

  • 参考: w3c文档 (暂未找到更好的学习方式)

4. css

  • 参考1: 《精通 CSS》 或 《CSS秘密花园》

5.react、redux、mobx、vue(以及常用框架和库的一些解析)

  • react源码解读以及深入react技术栈
  • redux源码解读
  • mobx源码解读

6. 数据结构、算法、语言

  • 参考1: 《学习javascript数据结构与算法》以及《数据结构与算法分析》
  • 参考2: leetcode

7.webpack、工程化相关

8.node.js、服务端运维

  • 暂未定

9.计算网络浏览器内核

  • 暂未定

10.操作系统和计算基础

  • 暂未定

11.知识总回顾

  • 暂未定
Share