C提高

内存四区

  • 栈区: 由编译器自动分配释放, 主要存放函数的参数值, 局部变量的值, 遵循先进后出原则
  • 堆区: 程序猿malloc申请的内存存放区域
  • 全局区: 存放全局变量和静态变量, 常量, 比如字符串常量
  • 代码区: 存放函数体二进制代码
1
2
3
4
5
6
7
8
9
10
11
12
int gl = 1;
int main(int argc, const char * argv[]) {
char a = 'a';
char b = 'b';
int c = 1;
char *p = "aaaa";
char *p2 = (char*)malloc(sizeof(char) * 100);

printf("a: %d , b: %d, c: %d, &p: %d, &p2: %d, p: %d, p2: %d, gl: %d\n", &a, &b, &c, &p, &p2, p, p2, &gl);
//a: 1606416119 , b: 1606416118, c: 1606416112, &p: 1606416104, &p2: 1606416096, p: 7887, p2: 3186800, gl: 8436
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
char* getStr1() {
char *p = "hello";
return p;
}
char* getStr2() {
char *p = "hello";
return p;
}

int main(int argc, const char * argv[]) {
char *p1 = NULL;
char *p2 = NULL;
p1 = getStr1();
p2 = getStr2();

printf("p1: %d, p2: %d\n", p1, p2);
//p1: 3936, p2: 3943

return 0;
}

memset(<#void *b#>, <#int c#>, <#size_t __len#>)

1
2
3
char buff[100];
//刚申请的内存地址, 里面也许有脏数据, 可以通过memset初始化
memset(buff, 0, sizeof(buff));

指针

arr + 1 与 &arr + 1的区别

1
2
3
4
int arr[10];
printf("arr: %d, arr+1: %d, &arr: %d, $arr+1: %d", arr, arr + 1, &arr, &arr + 1);
//arr: 1606416032, arr+1: 1606416036, &arr: 1606416032, $arr+1: 1606416072
//从上可知 arr+1会增加4字节, &arr+1会增加40字节, 及跳过数组的大小

const char p 与 char const p 的区别

const charp 表示指针指针的内容不能修改
char
const p 表示指针不能被修改

1
2
3
4
5
6
7
8
9
void testConst(const char* p) {
p[0] = 1;//报错
p = 1;
}

void testConst(char* const p) {
p[0] = 1;
p = 1;//报错
}

3中二级指针模型

指针数组 第一种二级指针模型

数组中的每个元素是指针, 指针指向全局区里的字符串常量

1
2
3
4
5
6
7
8
9
10
int main(int argc, const char * argv[]) {
// insert code here...
char * array[] = {"aaaa", "bbbb"};
int len = sizeof(array) / sizeof(array[0]);
for (int i = 0; i < len; i ++) {
printf("array[i] = %s\n", array[i]);
printf("*(array+i) = %s\n\n", *(array+i));
}
return 0;
}

第二种二级指针模型

所有数据都存放在栈区, array步长为4

1
2
3
4
5
6
7
int main(int argc, const char * argv[]) {
char array[3][5] = {"aaaa", "bbbb", "cccc"};
for (int i = 0; i < 3; i++) {
printf("%d-%s \n", i, array[i]);
}
return 0;
}

第三种二级指针模型

数据都存放在堆区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(int argc, const char * argv[]) {
char** p = NULL;
int num = 5;
p = (char**)malloc(sizeof(char*)*num);
for (int i = 0; i < num; i++) {
*(p+i) = (char*)malloc(sizeof(char)*num);
sprintf(p[i], "%d%d%d", i+1, i+2, i+1);
}

for (int i = 0; i < num; i++) {
printf("%d-%s \n", i, p[i]);
}
return 0;
}

数组类型指针

第一种

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main(int argc, const char * argv[]) {
// insert code here...
typedef int (myArray)[5];//定义了一个数组类型
//myArray arr; //int arr[5];
myArray* p = NULL;
int arr[5];
for (int i = 0; i < 5; i ++) {
arr[i] = i + 1;
}
p = &arr;
for (int i = 0; i < 5; i ++) {
printf("(*p)[%d]: %d\n", i, (*p)[i]);
}
return 0;
}

第二种

1
2
3
4
5
6
7
8
9
10
11
12
13
int main(int argc, const char * argv[]) {
typedef int (*arrayTypeP)[5];
arrayTypeP p = NULL;
int arr[5];
for (int i = 0; i < 5; i ++) {
arr[i] = i + 1;
}
p = &arr;
for (int i = 0; i < 5; i ++) {
printf("(*p)[%d]: %d\n", i, (*p)[i]);
}
return 0;
}

第三种

1
2
3
4
5
6
7
8
9
10
11
12
int main(int argc, const char * argv[]) {
int (*p)[5];
int arr[5];
for (int i = 0; i < 5; i ++) {
arr[i] = i + 1;
}
p = &arr;
for (int i = 0; i < 5; i ++) {
printf("(*p)[%d]: %d\n", i, (*p)[i]);
}
return 0;
}

函数指针

用函数指针实现多态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//第一种
typedef void (*myFunc)(int a, int b);
//void likeMultiPoly(myFunc p, int a, int b) {
// 第二种
void likeMultiPoly(void (*p)(int a, int b) , int a, int b) {
p(a, b);
}
void func2(int a, int b) {
printf("func2 a: %d, b: %d\n", a, b);
}
void func3(int a, int b) {
printf("func3 a: %d, b: %d\n", a, b);
}

void func4(int a, int b) {
printf("func4 a: %d, b: %d\n", a, b);
}
int main(int argc, const char * argv[]) {

likeMultiPoly(func2, 1, 2);
likeMultiPoly(func3, 3, 4);
likeMultiPoly(func4, 5, 6);
return 0;
}

结构体

一旦定义好结构体, 其对应的内存也分配完毕, 可以根据偏移量算出各变量的地址, 一旦声明完毕, 就不要轻易修改变量位置

结构体的定义

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
struct Teacher {
char name[32];
int age;
};//struct Teacher tearch;

typedef struct Teacher2 {
char name[32];
int age;
}t;// t tearch;

struct Student {
char name[32];
int age;
}s1, s2;//定义类型 同时定义变量

struct {
char name[32];
int age;
}s3 = {"s3", 18};//定义匿名类型 同时定义变量

int main(int argc, const char * argv[], char ** env) {
struct Teacher t1 = {"t1", 18};
t t2 = {"t2", 18};
return 0;
}

结构体的操作

t1.age t->age .-> 没有操作内存, 都是在cpu中, 相对于t1进行偏移量寻址操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(int argc, const char * argv[], char ** env) {
struct Teacher t1 = {"t1", 18};
t t2 = {"t2", 18};
t* p = NULL;
p = &t2;
printf("t2.age: %d\n", t2.age);

p->age = 20;

printf("t2.age: %d\n", t2.age);
printf("p->name: %s\n", p->name);

return 0;
}

其他

数组首地址与数组地址的区别?

c是数组的首元素的地址 c+1 步长为4字节
&c是数组的地址 &c+1 步长为200*4字节

1
2
3
4
5
6
7
8
9
10
11
12
13
int main(int argc, const char * argv[]) {
int c[200] = {0};//编译时将所有的值设置为0
memset(c, 0, sizeof(c));//运行时, 显示的将所有的值设置为0

printf("c => %d \n c+1 => %d \n &c => %d \n &c+1 => %d ", c, c+1, &c, &c+1);
/**
c => 1606415264
c+1 => 1606415268
&c => 1606415264
&c+1 => 1606416064
**/
return 0;
}

定义数组类型

1
2
3
4
5
6
7
8
9
int main(int argc, const char * argv[]) {
// insert code here...
typedef int (myArray)[5];//定义了一个数组类型
myArray arr; //int arr[5];
for (int i = 0; i < 5; i ++) {
printf("%d\n", arr[i]);
}
return 0;
}

程序默认参数

1
2
3
4
5
6
7
8
9
10
11
12
13
int main(int argc, const char * argv[], char ** env) {
//传递参数个数
printf("argc: %d\n", argc);
//调用程序是 传递是参数, 默认会传递文件路径
for (int i = 0; i < argc; i++) {
printf("%s\n", argv[i]);
}
//打印环境变量
while (*env ++) {
printf("%s\n", *env);
}
return 0;
}

字符串结束标志

字符串结束标志位 ‘\0’, NULL, 0
本质就是0

Models And Data

ORM

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
Math.guid = function(){
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
}).toUpperCase();
};
var Model = {
//记录实例对象
records: {},
inherited: function(){},
created: function(){
//让records不共享
this.records = {};
},
find: function(id){
var record = this.records[id];
if ( !record ) throw("Unknown record");
//每次返回都重新复制一个对象 防止修改影响到records里的数据
return record.dup();
},
//实例所具有的方法
prototype: {
newRecord: true,
init: function(atts) {
if (atts) this.load(atts);
},
load: function(attributes){
for(var name in attributes)
this[name] = attributes[name];
},
create: function(){
if ( !this.id ) this.id = Math.guid();
this.newRecord = false;
this.parent.records[this.id] = this.dup();
},
dup: function(){
return $.extend(true, {}, this);
},
destroy: function(){
delete this.parent.records[this.id];
},
update: function(){
this.parent.records[this.id] = this.dup();
},
save: function(){
this.newRecord ? this.create() : this.update();
}
},
create: function(){
var object = Object.create(this);
object.parent = this;
object.prototype = object.fn = Object.create(this.prototype);
object.created();
this.inherited(object);
return object;
},
init: function(){
var instance = Object.create(this.prototype);
instance.parent = this;
// console.log("init: ", instance.init)
instance.init.apply(instance, arguments);
return instance;
},
include: function(o){
var included = o.included;
$.extend(this.prototype, o);
if (included) included(this);
},
extend: function(o){
var extended = o.extended;
$.extend(this, o);
if (extended) extended(this);
}
};

//获取一个User model
var User = Model.create();

//获取一个user1实例
var user1 = User.init();
user1.id = 1;
user1.name = 'hello';
user1.save();

//获取一个user2实例
var user2 = User.init({
id: 2,
name: 'world',
});
user2.save();

var Goods = Model.create();
var goods1 = Goods.init({
id:1,
name: 'computer'
})
goods1.save();
var goods2 = Goods.init({
id:2,
name: 'mobile'
})
goods2.save()

3d变换

1
2
3
4
5
6
7
//以知半径r和角度phi, theta求x, y, z
// phi是方位面(水平面)内的角度,范围0~360度
// theta是俯仰面(竖直面)内的角度,范围0~180度
// 极坐标系的xyz 对应浏览器里
var x = r * Math.sin(theta) * Math.sin(phi);
var y = r * Math.cos(theta);
var z = r * Math.sin(theta) * Math.cos(phi);

git

1
2
3
4
5
6
7
8
9
10
11
12
git difftool //使用对比工具
git log -p -2//查看最近2次提交的详细信息
git log -stat //可以看到变更的文件
git log --since=2.weeks//2周内的日志
git log --author zhichen//指定作者
git log -S function_name //找出有对function_name的修改日志
git remote -v//git 地址
git push origin --tags//推送所有tag
git branch -vv//查看各分支head情况
git push origin --delete serverfix//删除线上分支
git rebase master branchName; gitmerge branchName;//将branchName分支变基为master 在合并到master 这样功能和合并分支一样, 但是提交历史看起来像是线性开发而不是并行开发再合并的
git reset --hard HEAD; //取消本地操作 恢复成上一次提交记录

MVCandClasses

What Is MVC?

The Model

主要存储对象数据的地方

因为是面向对象编程, 因此我们任何数据被model对象封装后, 都可以直接调用其方法, 而不依赖全局方法, 这样可以减少很多全局方法冲突

1
2
3
4
5
6
7
//使用全局变量的方法
var user = users["foo"];
destroyUser(user);

//建议封装为如下
var user = User.find("foo");
user.destroy();

The View

主要是展示页面, 和其他逻辑解耦

1
2
3
4
5
6
7
8
// helper.js
var helper = {};
helper.formatDate = function(){ /* ... */ };

// template.html
<div>
${ helper.formatDate(this.date) }
</div>

The Controller

controller是model和view的中间粘合剂, 它从view中获取events和input, 再从model中获取相应数据, 返回给view展示

1
2
3
4
5
6
7
8
// Use a anonymous function to enscapulate scope 
(Controller.users = function($){
var nameClick = function(){
/* ... */
};
// Attach event listeners on page load $(function(){
$("#view .name").click(nameClick); });
})(jQuery);

Toward Modularity, Creating Classes

js中的class是用原型链模拟的

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
var Class = function(parent) {
var klass = function() {
this.init.apply(this, arguments);
}
if (parent) {
var subClass = function() {};
subClass.prototype = parent.prototype;
klass.prototype = new subClass();
}

klass.fn = klass.prototype;

klass.fn.init = function() {}

klass.fn.parent = klass;

klass.fn._super = klass.__proto__;

// Adding a proxy function
klass.proxy = function(func) {
var self = this;
return(function() {
return func.apply(self, arguments);
});
}
// Add the function on instances too
klass.fn.proxy = klass.proxy;

klass.extend = function(obj) {
for (var i in obj) {
klass[i] = obj[i];
}
}

klass.include = function(obj) {
for (var i in obj) {
klass.prototype[i] = obj[i];
}
}

return klass;
}
var Animal = new Class;
Animal.include({
init: function() {
console.log('I am animal')
},
breath: function(){
console.log('breath');
}
});
var animal = new Animal;
animal.breath(); //I am animal //breath
var Cat = new Class(Animal)
Cat.prototype.init = function() {
console.log('I am Cat')
}
// Usage
var tommy = new Cat;
tommy.breath();//I am Cat //breath

bind polyfill

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
if (!Function.prototype.bind) {
Function.prototype.bind = function(context) {
//self 就是bind的调用者 context是调用者想绑定上下文
var self = this,
slice = [].prototype.slice,
//获取额外要传递的参数
args = slice.call(arguments, 1),
//用来做间接继承的
nop = function() {};
//将bind的调用者的原型 赋值给nop
nop.prototype = self.prototype;

//bound 会覆盖bind调用者
var bound = function() {
//这里是函数被调用时 会生效的逻辑
//通过apply将上下文 指向 nop(因为继承了bind调用者的原型)或者context
//然后函数调用时传递的参数合并到之前传递进来的参数数组里
return self.apply(this instanceof nop ? this : (context || {}), args.concat(slice(arguments)))
}

//将bind调用者的原型赋值给bound的原型, 这样bound也拥有了bind调用者的属性和方法, 而且具有改变上下文指向的能力
bound.prototype = new nop();

return bound;
}
}

node.js模块机制

模块编译

module的定义

1
2
3
4
5
6
7
8
9
10
11
12
function Module(id, parent) { 
this.id = id;
this.exports = {};
this.parent = parent;
if (parent && parent.children) {
parent.children.push(this);
}
this.filename = null;
this.loaded = false; t
his.children = [];
}
var module = new Module()

node会对引入的不同扩展类型的文件进行不同方式解析

  • .js文件. 通过fs模块同步读取文件后编译执行
  • .node文件. 这是C/C++编写的扩展文件, 通过dlopen()方法加载最后编译生成的文件
  • .json文件. 通过JSON.parse获取结果
  • 其他扩展类型. 它们都被当做js文件引入
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    Module._extensions['.json'] = function(module, filename) {
    var content = NativeModule.require('fs').readFileSync(filename, 'utf8');
    try {
    module.exports = JSON.parse(stripBOM(content));
    } catch (err) {
    err.message = filename + ': ' + err.message;
    throw err;
    }
    }

    //最后Module._extensions 会被赋值给require, 这样require可以在引入文件的时候, 根据不同类型进行解析
    require.extensions = Module._extensions

    //如果想对特殊的扩展进行处理, 可进行如下操作
    require.extensions['.ext'] = function() {
    //...do something
    }

node中的module, require等变量从何而来?

其实node对获取数据通过字符的形式拼接如下, 然后再调用vm原生模块的runInThisContext()方法(类似eval, 只是具有明确的上下文, 不污染全局), 返回一个function对象, 最后将exports, require, module, __filename, __dirname通过参数传入, 这样每个模块就可以独立作用域了

1
2
3
4
5
(function (exports, require, module, __filename, __dirname) {
var math = require('math');
exports.area = function (radius) {
return Math.PI * radius * radius; };
})(exports, require, module, __filename, __dirname)

为什么需要同时传入exports和module?

因为exports = module.exports, exports只是一个引用, 当赋值一个对象时, 这个引用就会被覆盖掉, 因此只能使用module.exports

解读React源码

15.0版本源码结构

  • addons:包含一系列的工具方法插件,如 PureRenderMixin、CSSTransitionGroup、Fragment、 LinkedStateMixin 等。
  • isomorphic:包含一系列同构方法
  • shared:包含一些公用或常用方法,如 Transaction、CallbackQueue 等
  • test:包含一些测试方法等
  • core/tests:包含一些边界错误的测试用例。
  • renderers:是 React 代码的核心部分,它包含了大部分功能实现,此处对其进行单独分析。
    • renderers 分为 dom 和 shared 目录
      • dom:包含 client、server 和 shared
        • client:包含 DOM 操作方法(如 findDOMNode、setInnerHTML、setTextContent 等)以 及事件方法,结构如图 3-2 所示。这里的事件方法主要是一些非底层的实用性事件方法, 如 事 件 监 听 ( ReactEventListener )、 常 用 事 件 方 法 ( TapEventPlugin 、 EnterLeave- EventPlugin)以及一些合成事件(SyntheticEvents 等)。
        • server:主要包含服务端渲染的实现和方法(如 ReactServerRendering、ReactServer- RenderingTransaction 等)。
        • shared:包含文本组件(ReactDOMTextComponent)、标签组件(ReactDOMComponent)、 DOM 属性操作(DOMProperty、DOMPropertyOperations)、CSS 属性操作(CSSProperty、 CSSPropertyOperations)等。
  • shared:包含 event 和 reconciler
    • event:包含一些更为底层的事件方法,如事件插件中心(EventPluginHub)、事件注册(EventPluginRegistry)、事件传播(EventPropagators)以及一些事件通用方法。React 自定义了一套通用事件的插件系统,该系统包含事件监听器、事件发射器、事 件插件中心、点击事件、进/出事件、简单事件、合成事件以及一些事件方法,如图 3-3 所示。
    • reconciler:称为协调器,它是最为核心的部分,包含 React 中自定义组件的实现 (ReactCompositeComponent)、组件生命周期机制、setState 机制(ReactUpdates、ReactUpdateQueue)、DOM diff 算法(ReactMultiChild)等重要的特性方法。

Virtual DOM

构建一套简易 Virtual DOM 模型并不复杂,它只需要具备一个 DOM 标签所需的基本 元素即可:

  • 标签名
  • 节点属性,包含样式、属性、事件等
  • 子节点
  • 标识 id
1
2
3
4
5
6
7
8
9
10
11
12
13
{
// 标签名
tagName: 'div',
// 属性
properties: {
// 样式
style: {}
},
// 子节点
children: [],
// 唯一标识
key: 1
}

Virtual DOM 中的节点称为 ReactNode,它分为3种类型 ReactElement、ReactFragment 和 ReactText。其中,ReactElement 又分为 ReactComponentElement 和 ReactDOMElement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type ReactNode = ReactElement | ReactFragment | ReactText;
type ReactElement = ReactComponentElement | ReactDOMElement;
type ReactDOMElement = {
type : string,
props : {
children : ReactNodeList,
className : string,
etc.
},
key : string | boolean | number | null,
ref : string | null
};
type ReactComponentElement<TProps> = {
type : ReactClass<TProps>,
props : TProps,
key : string | boolean | number | null,
ref : string | null
};
type ReactFragment = Array<ReactNode | ReactEmpty>;
type ReactNodeList = ReactNode | ReactEmpty;
type ReactText = string | number;
type ReactEmpty = null | undefined | boolean;

创建React元素

1
2
3
4
5
6
7
8
9
const Nav, Profile;
// 输入(JSX):
const app = <Nav color="blue"><Profile>click</Profile></Nav>;
// 输出(JavaScript):
const app = React.createElement(
Nav,
{color:"blue"},
React.createElement(Profile, null, "click")
);

源码路径: /v15.0.0/src/isomorphic/classic/element/ReactElement.js

初始化组件入口

  • 当 node 为空时,说明 node 不存在,则初始化空组件 ReactEmptyComponent.create(instan- tiateReactComponent)。
  • 当 node 类型为对象时,即是 DOM 标签组件或自定义组件,那么如果 element 类型为字 符 串 时 , 则 初 始 化 DOM 标 签 组 件 ReactNativeComponent.createInternalComponent (element),否则初始化自定义组件 ReactCompositeComponentWrapper()。
  • 当 node 类型为字符串或数字时,则初始化文本组件 ReactNativeComponent.create InstanceForText(node)。
  • 如果是其他情况,则不作处理。

源 码 路 径 : /v15.0.0/src/renderers/shared/ reconciler/instantiateReactComponent.js

DOM 标签组件

当开发者 使用 React 时,此时的

并不是原生
标签,它其实是 React 生成的 Virtual DOM 对象, 只不过标签名称相同罢了

ReactDOMComponent 针对 Virtual DOM 标签的处理主要分为以下两个部分:

  • 属性的更新,包括更新样式、更新属性、处理事件等
  • 子节点的更新,包括更新内容、更新子节点,此部分涉及 diff 算法。
  1. 更新属性
    当执行 mountComponent 方法时,ReactDOMComponent 首先会生成标记和标签,通过 this. createOpenTagMarkupAndPutListeners(transaction) 来处理 DOM 节点的属性和事件。
  • 如果存在事件,则针对当前的节点添加事件代理,即调用 enqueuePutListener(this, propKey, propValue, transaction)。
  • 如果存在样式,首先会对样式进行合并操作 Object.assign({}, props.style),然后通过 CSSPropertyOperations.createMarkupForStyles(propValue, this) 创建样式。
  • 通过 DOMPropertyOperations.createMarkupForProperty(propKey, propValue) 创建属性。
  • 通过 DOMPropertyOperations.createMarkupForID(this._domID) 创建唯一标识。

_createOpenTagMarkupAndPutListeners 方法的源码如下(源码路径:/v15.0.0/src/renderers/ dom/shared/ReactDOMComponent.js):

详解React生命周期

使用createClass创建自定义组件

createClass 是创建自定义组件的入口方法,负责管理生命周期中的 getDefaultProps。该方法法在整个生命周期中只执行一次,这样所有实例初始化的 props 将会被共享。通过 createClass 创建自定义组件,利用原型继承 ReactClassComponent 父类,按顺序合并mixin,设置初始化 defaultProps,返回构造函数。当使用 ES6 classes 编写 React 组件时,class MyComponent extends React.Component 其实就 是调用内部方法 createClass 创建组件

源码路径(src/isomorphic/classic/ class/ReactClass.js#L802)

阶段一:MOUNTING

mountComponent 负责管理生命周期中的 getInitialState、componentWillMount、render 和componentDidMount。
由于 getDefaultProps 是通过构造函数进行管理的,所以也是整个生命周期中最先开始执行 的。而 mountComponent 只能望洋兴叹,无法调用到 getDefaultProps。这就解释了为何 getDefaultProps只执行一次
由于通过 ReactCompositeComponentBase 返回的是一个虚拟节点,所以需要利用 instantiateReactComponent 去得到实例,再使用 mountComponent 拿到结果作为当前自定义元素的结果。

其实,mountComponent 本质上是通过递归渲染内容的,由于递归的特性,父组件的 componentWillMount 在其子组件的 componentWillMount 之前调用,而父组件的 componentDidMount 在其子组件的 componentDidMount 之后调用。

updateComponent 本质上也是通过递归渲染内容的,由于递归的特性,父组件的 componentWillUpdate 是在其子组件的 componentWillUpdate 之前调用的,而父组件的 componentDidUpdate 也是在其子组件的 componentDidUpdate 之后调用的

禁止在 shouldComponentUpdate 和 componentWillUpdate 中调用 setState,这会造成循环 调用,直至耗光浏览器内存后崩溃。

mountComponent源码: /v15.0.0/src/renderers/shared/reconciler/ReactCompositeComponent.js

setState 异步更新

setState 通过一个队列机制实现 state 更新。 当执行 setState 时,会将需要更新的 state 合并 后放入状态队列,而不会立刻更新 this.state,队列机制可以高效地批量更新 state。
当调用 setState 时,实际上会执行 enqueueSetState 方法,并对 partialState 以及_pendingStateQueue 更新队列进行合并操作,最终通过 enqueueUpdate 执行 state 更新。
而 performUpdateIfNecessary 方法会获取 _pendingElement、_pendingStateQueue、_pendingForceUpdate,并调用 receiveComponent 和 updateComponent 方法进行组件更新。
如 果 在 shouldComponentUpdate 或 componentWillUpdate 方 法 中 调 用 setState , 此 时 this._pendingStateQueue != null,则 performUpdateIfNecessary 方法就会调用 updateComponent 方法进行组件更新,但 updateComponent 方法又会调用 shouldComponentUpdate 和 componentWill- Update 方法,因此造成循环调用,使得浏览器内存占满后崩溃

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
class Demo extends React.component {
constructor(props) {
super(props);
this.state = {
a: 0
}
}
componentDidMount() {
this.setState({
a: this.state.a + 1
}, () => {
console.log("a1 : ", this.state.a)
this.setState({
a: this.state.a + 1
})
console.log("a2: ", this.state.a)
})
this.setState({
a: this.state.a + 1
}, () => {
console.log("a3: ", this.state.a)

})
console.log("a4: ", this.state.a)
setTimeout(() => {
console.log("a5: ", this.state.a)
this.setState({
a: this.state.a + 1
}, () => {
console.log("a6 : ", this.state.a)
this.setState({
a: this.state.a + 1
})
console.log("a7: ", this.state.a)
})
console.log("a8: ", this.state.a);
this.setState({
a: this.state.a + 1
}, () => {
console.log("a9: ", this.state.a);

})
console.log("a10: ", this.state.a);
})
}
}
//上面结果
/**
* a4: 0
* a1: 0
* a2: 1
* a3: 1
* a5: 2
* a6: 3
* a7: 3
* a8: 4
* a9: 5
* a10: 5
* /

vitrul diff

源码: /v15.0.0/src/renderers/shared/reconciler/ReactMultiChild.js

React Patch 方法

所谓 Patch,简而言之就是将 tree diff 计算出来的 DOM 差异队列更新到真实的 DOM 节点上,最终让浏览器能够渲染出更新的数据。而且,React 并不是计算出一个差异就去执 行一次 Patch,而是计算出全部差异并放入差异队列后,再一次性地去执行 Patch 方法完成真实 DOM 的更新。

Patch 方 法 的 源 码 如 下 ( 源 码 路 径 : /v15.0.0/src/renderers/dom/client/utils/DOMChildrenOperations.js)

Performance

Web Worker

JavaScript是一门单线程语言, 我们可以通过浏览器的Work, 启动多线程来提高性能来处理io密集型计算, 大数据处理等; 但是主进程和子线程是隔离的, 只能通过监听事件和postMessage的方式进行传递消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//在主线程启动线程 注意有跨域问题 而且必须是js可执行脚本
var w1 = new Worker( "http://127.0.0.1:8080/test.js" );
//监听事件, 就可以收到子线程的发送消息
w1.addEventListener("message", function(e) {
console.log("main process receive: ", e.data)
})
//对子线程发送信息
w1.postMessage("hello world")

// test.js
console.log("test.js....", navigator, location, JSON)
addEventListener( "message", function(evt){
// evt.data
console.log("receive: ", evt.data)
postMessage( "message has received" );
} );
importScripts("test2.js")

Work 中可以使用如下全局变量

  • navigator
  • location
  • JSON
  • importScripts //同步阻塞引入其他脚本

Shared Workers

通浏览器同时打开多个tab(同一个页面), 就会同时打开多个work, 此时我们可以用共享work来减少性能资源消耗

1
2
3
4
5
6
7
8
var w1 = new SharedWorker( "http://127.0.0.1:8080/mySharedWorker.js" );

// 通过port对象作为唯一标识
w1.port.addEventListener( "message", handleMessages );
w1.port.postMessage( "something cool" );

//port connect initialized
w1.port.start();

在shared worker 内部给来的请求分配一个端口

1
2
3
4
5
6
7
8
9
10
11
12
13
// inside the mySharedWorker.js
addEventListener("connect", function (event) {
//分配端口给这个链接
var port = event.ports[0];

port.addEventListener("message", function (event) {
// ....
port.postMessage( .. );
})

// initialize the port connection
port.start();
})

SIMD

SIMD 打算把 CPU 级的并行数学运算映射到 JavaScript API,以获得高性能的数据并行运 算,比如在大数据集上的数字处理。 现在还在研发中, 也许会在es7出现

asm.js

asm.js 优化了垃圾收集和强制类型转换等部分, 所以适合数学运算(如游戏中的图形处理)

asm 代码风格

1
2
3
4
5
6
7
function test() {
"use asm"
var a = 45;
var b = a | 0; //因为js是32位的, 但现在的计算机一般都是64位的, 通过`| 0`后, 计算机知道这是32位的变量, 减少了对高位的追踪计算

var heap = new ArrayBuffer( 0x10000 ); // 从堆里申请64k内存
}

Generators

迭代器

1
2
3
4
5
6
7
8
9
10
function *foo(x) {
var y = x * (yield function() {return "hello world";});
return y;
}
var it = foo( 6 ); //将6作为参数调用foo方法 并返回一个迭代器对象
// start `foo(..)`
var res1 = it.next(); //开始执行函数, 直到碰到yield 就停住了, 等待下一步指令; 并返回一个迭代器对象值为 {done: false, value: ƒ}, done表示迭代器还没有结束, value表示yield后面的内容; 一般浏览器都会忽略掉第一个next传入的参数
res1.value(); //上面yield后面的内容是一个函数, 所以调用后, 返回"hello world"
var res2 = it.next( 7 ); //将7 作为yield的返回值, 并继续执行至下一个yield或是结束
res2.value; //因此 6 * 7 = 42
1
2
3
4
5
6
7
8
9
10
function* test() {
yield 1;
yield 2;
yield 3;
}

var it = test();
for (var v of it) {
console.log(v)
}

for (var v of it) 会自动执行迭代器的next方法, 直到返回的对象done: true

1
2
3
for (var v of it) {}
//可以转化为如下
for (var ret; (ret = it.next()) && !ret.done; ) {}
1
2
3
4
5
6
7
8
var arr = [1, 3, 5, 7];
for (var v of arr) {
console.log(v);//1 3 5 7
}
// 等价于如下
var it = arr[Symbol.iterator]()
it.next();//{value: 1, done: false}
it.next();//{value: 3, done: false}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function* test() {
try {
var nextVal = 1;
while (true) {
nextVal += 3;
yield nextVal;
}
} finally {
console.log('finally...')
}
}

var it = test();
for (var v of it) {
console.log(v)
if (v > 30 ) {
var result = it.return("hello world")
console.log("result: ", result)
}
}
/**
* 输出结果: 1 4 .... finally... result: {value: "hello world", done: true}
*/

当我们调用迭代器it的return方法时, 迭代器会将it.next的结果done设置为true, 来终端迭代器迭代下去, 然后会执行迭代器里的finally方法(如果有), 最后返回的value的值等于我们return传递进去的值

自动执行迭代器

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
function ajax(delay) {
return new Promise(function(resolve, reject) {
//延迟返回 模拟异步
setTimeout(() => {
resolve("ajax data");
}, delay)
})
}

function *test(arg) {
var ajaxRes = yield ajax(arg);
return yield ajaxRes;
}
/**
* 模拟co库写的一个迭代器递归执行方法
**/
function co(gen) {
//获取初始化参数
var args = [].slice.call(arguments, 1);
//使用当前作用域初始化gen获取迭代器对象it, 下面it会作为一个闭包对象一直被递归调用
var it = gen.apply(this, args);
//返回一个处理好的promise
return Promise.resolve().then(function handleNext(value) {
//将上一次promise返回的结果作为参数传递给迭代器, 第一次value为undefined, 一般浏览器会忽略
var next = it.next(value);
console.log("next: ", next);

//函数自运行
return (function handleResult(next) {
//判断迭代器是否完成 如果完成返回最后结果
if (next.done) {
return next.value;
} else if (typeof next.value == 'function') {
//处理thunk类型的回调
//返回一个promise
return new Promise(function(resolve, reject) {
//给thunk传递回调函数
next.value(function(err, msg) {
if (err) {
reject(err);
} else {
resolve(msg);
}
})
}).then(function() {
//如果promise返回成功, 就递归调用handleNext来处理it迭代器至完成
handleNext,

//异常处理
function handleErr(err) {
return Promise.resolve(
//处理异常
it.throw(err)
).then(
//继续处理异常处理的结果
handleResult
)
}

})
} else {
//否则就递归调用迭代器
//通过promise处理next.value, 如果next.value是非promise对象就会直接进入then, 否则就等待promise回调then
//这里的next.value一般都是异步处理, 例如ajax操作
return Promise.resolve(next.value).then(
//如果promise返回成功, 就递归调用handleNext来处理it迭代器至完成
handleNext,

//异常处理
function handleErr(err) {
return Promise.resolve(
//处理异常
it.throw(err)
).then(
//继续处理异常处理的结果
handleResult
)
}
);
}
})(next)
})
}
co(test, 3000).then(function(res) {
console.log("res: ",res)
});

上面是一个自动递归处理迭代器的方法, 方法类似co库

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
funcion *foo() {
var r1 = yield ajax('url1');
var r2 = yield ajax('url2');

var r3 = yield ajax(r1+r2);
return r3;
}
co(foo);

//可以优化为下面
funcion *foo() {

var p1 = ajax('url1');
var p2 = ajax('url2');
var r1 = yield p1
var r2 = yield p2

var r3 = yield ajax(r1+r2);
return r3;
}
co(foo);
// 上面那方法可以理解如下
funcion *foo() {
var results = yield Promise.all([ajax('url1'), ajax('url2)]);
var r1 = results[0];
var r2 = results[1];

var r3 = yield ajax(r1+r2);
return r3;
}
co(foo);

上面第一个demo, 按照迭代器的方式, 必须等r1的ajax返回结果后, 才会调用r2的ajax, 这样效率就会很低, 所以应该优化为下面那方法, 这样就可以先直接发送2个ajax请求并发, 处理结果使用迭代器

迭代器的polyfill

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
function *foo(url) {
// STATE *1*
try {
console.log( "requesting:", url );
var TMP1 = request( url );

// STATE *2*
var val = yield TMP1;
console.log( val );
} catch (e) {
// STATE *3*
console.log( "Oops:", err );
return false;
}
}

//polyfill 如下

function foo(url) {
// manage generator state
var state;

// generator-wide variable declarations
var val;

function process(v) {
switch (state) {
case 1:
console.log( "requesting:", url );
return request( url );
case 2:
val = v;
console.log( val );
case 3:
var err = v;
console.log( "Oops:", err );
return false;
}
}

return {
next: function(v) {
// initial state
if (!state) {
state = 1;
return {
done: false,
value: process()
};
// yield resumed successfully
} else if (state == 1) {
state = 2;
return {
done: true,
value: process( v )
};
}
// generator already completed
else {
return {
done: true,
value: undefined
};
}
},
throw: function(e) {
// the only explicit error handling is in
// state *1*
if (state == 1) {
state = 3;
return {
done: true,
value: process( e )
};
}
// otherwise, an error won't be handled,
// so just throw it right back out
else {
throw e;
}
}
}
}

Coercion

Abstract Value Operations

JSON.stringify

1
2
3
4
5
6
7
8
9
JSON.stringify( undefined ); //"undefined"
JSON.stringify( function(){} ); //"undefined"
JSON.stringify( null ); //"null"
JSON.stringify(
[1,undefined,function(){},4]
); // "[1,null,null,4]"
JSON.stringify(
{ a:2, b:function(){} }
);// "{"a":2}"

从上可以看出, 不同类型的值在不同类型中, JSON.stringify的结果表现形式也不一样, 在数组的undefined, function会被转换成null, 在对象里值为undefined, function的属性会被过滤掉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var a = { 
b: 42,
c: o,
d: function(){}
};
// create a circular reference inside `a`
o.e = a;

// would throw an error on the circular reference
// JSON.stringify( a );

// define a custom JSON value serialization
a.toJSON = function() {
// only include the `b` property for serialization
return { b: this.b };
};

JSON.stringify( a ); // "{"b":42}"

如果我们对象引用了自身, 再执行JSON.stringify时会报异常, 我们可以给这个对象定义一个toJSON的方法, 返回一个默认对象, 来避免这种情况

Explicit Coercion

ToNumber

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Nubmer(true);//1
Nubmer(false);//0
Nubmer(undefined);//NaN
Nubmer(null);//0
Nubmer("");//0
Nubmer("false");//NaN
Nubmer([]);//0
Nubmer([""]);//0
Nubmer(["", ""]);//NaN
Nubmer({});//NaN
Nubmer(function(){});//NaN

var num1 = true + 1; // 1 + 1 = 2
var num2 = false + 1; //0 + 1 = 1
var num3 = undefined + 1; //NaN + 1 = NaN
var num4 = null + 1; // 0 + 1 = 1

综上可知, true, false, undefined, null在数学计算中, 强转后的数值不一, 格外需要注意的是undefined和非空字符串会被转换为NaN

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 var a = {
valueOf: function(){
return "42";
},
toString: function(){
return "4221";
}
};
var b = {
toString: function(){
return "42";
}
};
var c = [4,2];
c.toString = function(){
return this.join( "" ); // "42"
};
Number( a ); //42
Number( b ); //42
Number( c ); //42
a + '';//42

如果定义了valueOf, toString, 在强转的时候, 会优先调用valueOf, 如果没有valueOf, 再调用toString

ToBoolean

1
2
3
4
5
6
7
8
9
10
11
12
13
Boolean("");//false
Boolean(NaN);//false
Boolean(undefined);//false
Boolean(null);//false
Boolean(0);//false
Boolean(-0);//false

Boolean(new Boolean( false ));//true
Boolean(new Number( 0 ));//true
Boolean(new String( "" ));//true
Boolean("0");//true
Boolean([]);//true
Boolean({});//true

只有上面几种情况会返回false, 其余都会返回true

1
Boolean(document.all)//false

Boolean(document.all)居然返回false, 似乎和上面的结论矛盾, 其实是因为历史原因, document.all其实已经被废弃, 但是又为了兼容有些代码, 浏览器又不能把他删除, 但又不想让if(document.all){}这样的代码执行, 因此让Boolean(document.all)false, 即使document.all返回的是一个类数组

Explicitly: Parsing Numeric Strings

1
2
3
4
5
6
var a = "42";
var b = "42px";
Number( a ); // 42
parseInt( a ); // 42
Number( b ); // NaN
parseInt( b ); // 42

parseInt只要解析是值, 第一个是数字就能解析, 否则就返回NaN

1
2
3
4
5
6
7
parseInt(1/0, 19);//18
parseInt( 0.000008 );// 0 ("0" from "0.000008")
parseInt( 0.0000008 );// 8 ("8" from "8e-7")
parseInt( false, 16 );// 250 ("fa" from "false")
parseInt( parseInt, 16 );// 15 ("f" from "function..")
parseInt( "0x10" );// 16
parseInt( "103", 2 ); // 2

parseInt()的一些奇怪现象

Implicit Coercion

Implicitly: Strings <–> Numbers

1
2
3
4
5
6
var a = [1,2];
var b = [3,4];
a + b; // "1,23,4"

{} + [];//0 这里{}其实不是表示空对象, 而是表示一个块作用域, 因此没有任务作用, 所以 +[] 转换为数字为0
[] + {};//"[object Object]" 这里{}表示对象被强转成了字符串

以上是一些比较奇怪的现象

1
2
3
4
5
6
 var a = {
valueOf: function() { return 42; },
toString: function() { return 4; }
}
a + ""; // "42"
String( a ); // "4"

以上两者转换是有区别的, a + ""这种形式会优先调用valueOf方法, 而String()直接调用toString方法

Loose Equals Versus Strict Equals

我们经常会说==表示检测两者的值否是相等, ===表示检测两者的值相等的同时再检测类型是否相同, 这种说法不是完全正确, 正确理解应该是, ==会将两值转换成同一类型再进行比较, 而===不会将两值类型做转换而直接进行比较
当一个String类型和一个Number类型进行==比较, String类型会转换为Number

1
2
3
var num = 42;
num == true;//false ==> 42 == Number(true) ==> 42 == 1
num == false;//false ==> 42 == Number(false) ==> 42 == 0

上面的比较结果居然都是false, 据我们上面分析得知, 42被转换为布尔值为true, 但实际上, 当一个Number类型和一个Boolean类型做比较时, Boolean类型会被转换成Number类型

1
2
3
var str = "42"
num == true;//false ==> Number("42") == Number(true) ==> 42 == 1
num == false;//false ==> Number("42") == Number(false) ==> 42 == 0

当2个非数字类型的变量进行比较时, 两者都会转换成Number类型再进行比较

Comparing: nulls to unde neds

1
null == undefined;//true

nullundefined这两者与其他类型比较都是返回false, 只有以上情况, 会返回true,

Comparing: objects to nonobjects

1
2
3
var a = 42;
var b = [42];
a == b; //true

以上结果得知, 所有对象类型与原始标量类型做比较时, 对象类型都会被转换成原始标量类型再进行比较

Edge Cases

一些比较奇怪的bug

1
2
3
4
5
6
Number.prototype.valueOf = function() {
return 3;
};
new Nubmer( 2 ); //2
new Number( 2 ) == 3; // true
new Number( 2 ) == 2; // false

上面是比较奇怪的bug, 返回是2, 但是与2不相等

1
2
3
4
5
6
7
8
9

var i = 2;
Number.prototype.valueOf = function() {
return i++;
};
var a = new Number( 42 );
if (a == 2 && a == 3) {
console.log( "Yep, this happened." );
}

以上if结果是true

1
2
3
4
5
6
7
8
9
10
11
"0" == false; //true 两者类型不同, 都会转换成Number类型再进行比较
"0" == ""; //false 都是字符串类型, 不进行转换直接比较

false == []; //true //转换成数字类型再进行比较
false == {}; //false //转换成数字类型再进行比较

"" == []; //true
"" == {}; //false

0 == []; //true
0 == {}; //false

上面为何 []``{} 比较结果不一样呢? 因为Number([]) === 0 Number({})结果为NaN; String([]) === '' String({}) === [object Object]

1
[] == ![]; //true

非常奇怪, 上面结果是true, 因为上面的转换规则如下 ==> [] == false ==> ==> Number([]) == Number(false) ==> 0 == 0 ==> true

1
2
2 == [2];       // true  Number([2]) ==> 2
"" == [null]; // true String([null]) ==> ""

上面, 只要按照对象类型与原始类型比较时, 对象类型会被转换成原始类型再进行比较

1
0 == "\n" //==> 0 == Number("\n") ==> 0 == 0

Abstract Relational Comparison

1
2
[42] < ["43"]; //true
["42"] < ["043"]; //false

上面第一条是可以理解的, 第二条是因为两者都是字符串数组, 所以就转换成字符串比较, 字符串比较是按位比较的, 他们各自的第一位分别是4与0, 所有["42"] > ["043"]

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

a < b; // false
a == b; // false
a > b; // false

a <= b; // true
a >= b; // true

上面真是个奇怪的现象, 为什么会这样呢? 因为js判断a <= b的时候其实是转换成!(a>b) 判断a>=b的时候其实是转换成!(a<b)