@JunQiu
2018-09-25T14:10:27.000000Z
字数 3509
阅读 1452
summary_2018/08
language_js
pocc(计组)
## 全局上下文
console.log(this) // {}
## 在函数上下文(严格模式/非严格模式)
// 正常模式,node环境下
function f() {
console.log(this === global);
}
f() // true
// !!! there is a Tips
const a = {
a1 : function () {
this.b1='b'
function c1() {
console.log(this === global) // true
}
c1() // 在严格模式下,如果this没有被执行上下文(execution context)定义,那它将保持为undefined。因为c1是被直接调用的,而不是作为对象的属性或方法调用的(如 window.f2())
}
}
Tips:此时this绑定到全局对象,算是js的一个设计缺陷,实际应该绑定到外层对应的对象,可以采取that(约定)变量来代替this,传递(或者用严格模式来注意这个风险),比如还可以使用call、apply和bind方法绑定,而es6箭头函数也可以避免这个问题
// call、apply、bind
1、bind:
function f(){
return this.a;
}
const g = f.bind({a:"azerty"});
console.log(g()); // azerty
const h = g.bind({a:'yoo'}); // 不能对绑定后的结果再次绑定
console.log(h()); // azerty
const o = {a:37, f:f, g:g, h:h};
console.log(o.f(), o.g(), o.h()); // 37, azerty, azerty
2、apply
apply() 方法调用一个函数,其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数。
function f () {
console.log(this.a)
}
f.apply({a:1}) // 1
3、call
call() 方法调用一个函数,其具有一个指定的this值和分别地提供的参数(参数的列表)。
function Product(name, price) {
this.name = name;
this.price = price;
}
function Food(name, price) {
Product.call(this, name, price);
this.category = 'food';
}
new Food('cheese', 5).name
// 严格模式
function f() {
'use strict';
console.log(this === undefined);
}
f() // true
Tips:对构造函数十分有用,当未使用new关键字时,this对象会指向全局变量,而在严格模式下,会指向undefined
## 在对象中调用,指向当前对象(原型链上查找)
Tip:在原型链上查找,即使使用自己继承的原型的方法,this关键字也是指向自己的
const o = {
f: function () {
console.log(this)
return this.a + this.b
}
}
const p = Object.create(o)
p.a = 1
p.b = 4
console.log(p.f()) // { a: 1, b: 4 } 5
mmap内存映射的实现过程,总的来说可以分为三个阶段:
### 进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域
1、进程在用户空间调用库函数mmap,原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
2、在当前进程的虚拟地址空间中,寻找一段空闲的满足要求的连续的虚拟地址
3、为此虚拟区分配一个vm_area_struct结构,接着对这个结构的各个域进行了初始化
4、将新建的虚拟区结构(vm_area_struct)插入进程的虚拟地址区域链表或树中
### 调用内核空间的系统调用函数mmap(不同于用户空间函数),实现文件物理地址和进程虚拟地址的一一映射关系
5、为映射分配了新的虚拟地址区域后,通过待映射的文件指针,在文件描述符表中找到对应的文件描述符,通过文件描述符,链接到内核“已打开文件集”中该文件的文件结构体(struct file),每个文件结构体维护着和这个已打开文件相关各项信息。
6、通过该文件的文件结构体,链接到file_operations模块,调用内核函数mmap,其原型为:int mmap(struct file *filp, struct vm_area_struct *vma),不同于用户空间库函数。
7、内核mmap函数通过虚拟文件系统inode模块定位到文件磁盘物理地址。
8、通过remap_pfn_range函数建立页表,即实现了文件地址和虚拟地址区域的映射关系。此时,这片虚拟地址并没有任何数据关联到主存中。
### 进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝
注:前两个阶段仅在于创建虚拟区间并完成地址映射,但是并没有将任何文件数据的拷贝至主存。真正的文件读取是当进程发起读或写操作时。
9、进程的读或写操作访问虚拟地址空间这一段映射地址,通过查询页表,发现这一段地址并不在物理页面上。因为目前只建立了地址映射,真正的硬盘数据还没有拷贝到内存中,因此引发缺页异常。
10、缺页异常进行一系列判断,确定无非法操作后,内核发起请求调页过程。
11、调页过程先在交换缓存空间(swap cache)中寻找需要访问的内存页,如果没有则调用nopage函数把所缺的页从磁盘装入到主存中。
12、之后进程即可对这片主存进行读或者写的操作,如果写操作改变了其内容,一定时间后系统会自动回写脏页面到对应磁盘地址,也即完成了写入到文件的过程。
注:修改过的脏页面并不会立即更新回文件中,而是有一段时间的延迟,可以调用msync()来强制同步, 这样所写的内容就能立即保存到文件里了。
// 常规文件系统操作(调用read/fread等类函数)中,函数的调用过程:
1、进程发起读文件请求。
2、内核通过查找进程文件符表,定位到内核已打开文件集上的文件信息,从而找到此文件的inode。
3、inode在address_space上查找要请求的文件页是否已经缓存在页缓存中。如果存在,则直接返回这片文件页的内容。
4、如果不存在,则通过inode定位到文件磁盘地址,将数据从磁盘复制到页缓存。之后再次发起读页面过程,进而将页缓存中的数据发给用户进程。
## 总的来说:两次拷贝(内核空间和用户空间)
常规文件操作为了提高读写效率和保护磁盘,使用了页缓存机制。这样造成读文件时需要先将文件页从磁盘拷贝到页缓存中,由于页缓存处在内核空间,不能被用户进程直接寻址,所以还需要将页缓存中数据页再次拷贝到内存对应的用户空间中。这样,通过了两次数据拷贝过程,才能完成进程对文件内容的获取任务。写操作也是一样:数据——流缓存区(用户层带缓存I/O)——内核缓存区(满了一次性写入磁盘)——磁盘
1、对文件的读取操作跨过了页缓存,减少了数据的拷贝次数,用内存读写取代I/O读写,提高了文件读取效率。
2、3种数据结构的高效,其页映射过程也很高效
3、如果更新文件的操作很多,mmap避免两态拷贝的优势就被摊还,最终还是落在了大量的脏页回写及由此引发的随机IO上.
4、对变长文件不适合.(mmap来写文件这种方式没办法增加文件的长度, 因为要映射的长度在调用mmap()的时候就决定了)