JiM-W

keep Moving


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

Javscript中this详解

发表于 2016-11-04 | 分类于 javascript

Javscript中this详解

一 宿主环境和javascript 引擎

  • 宿主环境:一门语言在运行的时候需要一个环境,这个环境就是宿主环境,对于JavaScript来说宿主环境就是我们常说的浏览器,在这个浏览器里面提供了一些借口让javascript引擎可以和宿主环境对接;
  • jacascript引擎才是真正执行代码的地方,常见的javascript引擎包括V8,javascript core,javascript引擎主要做了以下几件事情:一套与宿主环境相互联系的规则,javascript引擎内核,规定了基本javascript语法逻辑和命令,一组内置对象和API
  • javascript不仅可以在浏览器里面运行,还可以在其他宿主环境,比如node.js

二 this详解

1 global this

1.1在浏览器里面,全局范围内,this等价于window对象(其实window对象也是Window构造函数的实例)

1
2
3
4
5
console.log(this === window);//true 全等号还是返回true
this.name = "jhon";
console.log(window.name);//jhon
window.age = 13 ;
console.log(this.age);//13

1.2 在浏览器中,在全局范围内,用var声明一个变量和给this window 设置属性是等价的

1
2
3
var foo = "bar";
console.log(this.foo);//bar
console.log(window.foo);//bar

1.2.1 预解析对顶级对象window的属性的添加

1
2
3
4
5
6
7
console.log(window.exp); //由于预解析 var exp ;会被提升到最上部,声明的对象未被定义的话,默认为undefined
if (exp in window){ // 返回true;
//in 操作符,可以用来判断某个对象是否拥有某个属性,可以追溯对象所有的属性,包括原型上的;
var exp = "example";//会进行预解析,声明exp属性给到window,解析到这一行代码的时候,会进行赋值
//明确预解析:所谓预解析是浏览器的动作,它会在执行javascript代码之前,全局的检索javascript代码,将变量声明以及函数声明提升到函数的额最顶部,所以,变量声明提升的时候,相当于给window对象添加了一个属性
console.log(window.exp);//example
}

1.2.2 用function声明函数和在全局用var 声明变量,或者函数体不用var声明变量,都是在给全局的window添加属性值

1
2
3
4
5
6
7
8
9
<script>
var amyName; // window 对象会多一个amyName 属性
a ;
function anewBuilder(){ //window对象会多一个anerBuilder属性
aaa = 1; //函数执行后 ,window对象又会多一个aaa属性
}
anewBuilder()
console.log(window);
</script>

1.2.3 如果没有用var 声明,那么则不会进行变量提升,即使该变量也是隐式全局变量

1
2
3
4
if (exp in window){ //报错,程序停止运行
exp = "example";//隐式全局变量,不会进行预解析,声明exp属性给到window
console.log(exp);
}

1.3 在函数体内 ,(即使是函数体内的函数嵌套函数),没有var或者let声明的变量或者函数,就是在给全局this window添加属性;如果使用了var 或者 let声明变量,则不能被全局的this访问到;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script>
// var str = "全局的this";
//在函数体,声明变量的时候,如果没有用var
var func = function(){
var str ="这个不能被全局的this访问到";
console.log(this.str);//undefined this代表全局的window,在全局范围内,并没有str赋值
// 如果不是被new调用函数,那么函数体的this都是指代全局范围的this,代表window对象;
func3 = function(){
innerV = "这是函数体内的变量";
};
// func3();
}
func();
this.func3();//和在函数体内执行效果是一样的,func3也是全局this的属性(方法)
//这两个函数必须被执行,才能正确赋值,以下innerV 才能输出;
console.log(this.innerV);//"这是函数体内的变量"
console.log(this.str);//undefined
//在函数体内,不使用var或者let声明变量,也是在给全局的this和window对象设置属性
</script>

2 function this

2.0 对于一个函数,有三种角色,普通函数,直接调用执行;构造函数,通过new调用;函数作为对象;

2.1 如果不是被new调用函数,

  • 普通函数:直接执行函数体,那么函数体的this都是指代全局范围的this,代表window对象;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script>
var foo = "bar"; //变量的声明,相当于给window添加了一个 foo 属性,属性值是一个基础数据类型值
function test (){ //函数的声明,相当于给window添加了一个test属性,属性值是复杂数据类型值
this.foo = "bar is changed";//当函数不是被new操作符调用的时候,this还是指向window对象
}
console.log(this.foo);//bar
test();//直接执行函数,this代表全局的this
//等价于window.test()
console.log(this.foo);//bar is changed
//--------------------------------------------------------------------
var num = 300;
function getNum(){
var num = 200 ;
function getInnerNum(){
console.log(this);
console.log(this.num);//300
console.log(num);//200
}
return getInnerNum;
}
var res = getNum();
res();
</script>
  • 直接执行函数体的时候,如果使用了apply call 则代表改变了函数运行时的this指向,原先this默认指向的是其执行的上下文环境;语法 fn.apply( thisArg[, argsArray] ) 、 fn.call( thisArg[, arg1[, arg2[, …]]] ) 执行之后,this指向传入的第一个参数
1
2
3
4
5
6
7
8
9
10
11
<script>
var foo = "bar";
var obj = {"foo":"objBar"}
function test (){
console.log(this.foo);
}
test();//bar 函数执行的时候this指向执行的上下文环境
test.apply(obj);// objBar 将test函数执行的时候this的指向指向了obj
test.call(obj);// objBar 将test函数执行的时候this的指向指向了obj
</script>

2.2 函数作为构造函数:如果被new操作符调用函数体,那么函数体的this就变成了一个新的值,和global的this没有任何关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
var foo = "bar";
function test (){
this.foo = "bar is changed";
}
console.log(this.foo);//bar
//不使用new操作符调用
// test();
// console.log(this.foo);//bar is changed
//使用new操作调用
new test();//通过new操作符之后,函数体里面的this不再指向全局window,而是指向其新创建的对象
console.log(this.foo);//bar
console.log(new test().foo);//bar is changed
</script>

2.3 接下来解释下new 操作符的作用,看下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script>
function test (){
this.foo = "bar is changed";
this.func = function(){
console.log("new的解析");
}
}
console.log(test);
//function test(){
// this.foo = "bar is changed";
// this.func = function(){
// console.log("new的解析");
// }
// }
console.log(typeof test);
//function
console.log(new test);
//test {foo: "bar is changed",func:()}
console.log(typeof new test());
//object
</script>

2.3.1 解释new的作用,当以 new 操作符调用构造函数的时候,函数内部 发生以下变化:

  • 函数内部会创建一个空的对象,将该对象的引用指向this;所以后期只能通过this给实例添加属性和方法;

  • 属性和方法会被添加到this 引用的对象中;

  • 新创建的对象的引用指向了this,最后返回this

2.3.2 new操作符的作用如下:

1
2
3
4
5
6
7
8
function test (){
var obj = new Object();//实例化一个对象
obj.foo = "bar";
obj.func = function(){
console.log("new的解析");
}
return obj;
}

2.3.3 从原型的角度理解下 new操作符的作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function test (){
this.foo = "bar is changed";
this.func = function(){
console.log("new的解析");
}
}
var
//1 方法new的瞬间 得到一个空的对象
var obj = { } ;
obj.__proto__ = test.prototype;
//2 方法的this指向空对象obj
//3 执行构造方法
{ }.foo = "bar is changed";
{ }.func = function(){
console.log("new的解析");
}
// 返回该对象
test.call(obj)

2.3.4 如果使用构造函数显式的返回基本数据类型(number string boolean null undefined)不会影响输出结果 ,返回引用类型 object 则不会再返回构造函数的实例对象的地址,而是返回该引用的地址;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script>
function anotherTest(){
console.log("this is another test");
}
function test (){
this.foo = "bar";
this.func = function(){
console.log("new的解析");
}
// 返回基本数据类型,不会影响最后的结果
// return 123;
// return "str";
// return null;
// 如果返回的是引用类型,则会改变返回值
// return {'name':'jhon','age':12};
// return [1,2,3]
// return anotherTest ;
//如果返回this,那么还是返回的实例化对象;new操作符默认返回实例化对象
return this;
}
var obj = new test();
console.log(obj);//基本数据类型,仍会返回构造函数的实例化对象,返回引用类型的话,就是返回了引用类型的地址,原来构造函数的实例化的对象指向该引用类型
</script>

2.3.5 new操作符每次调用的时候,在堆 (heap)内存中开辟一块内存空间,存储实例化对象的信息;如果有变量接受该对象的地址,那么 定义变量的时候会在栈(stack) 中开辟了一块空间存储变量名和引用地址 ;不同的变量指向不同的堆内存空间

1
2
3
4
5
6
7
8
9
10
11
function test (name){
this.name = name;
}
var obj1 = new test('Jhon');
var obj2 = new test("Jhon");
console.log(obj1.name);//Jhon
console.log(obj2.name);//Jhon
obj1.name = "kobe";
console.log(obj1.name);//kobe
console.log(obj2.name);//Jhon

2.4 如果函数体是对象的方法,那么此时的函数体里面的this就是指向的调用 该函数的对象;注意区分下面这两种情况

1
2
3
4
5
6
7
8
9
10
11
<script>
var foo = "bar";
console.log(this.foo);//bar
var obj = {
foo:"objBar",
test:function (){ //函数作为对象的方法,被对象调用
console.log(this.foo);//objBar 此时的this指向调用函数的对象
}
}
obj.test();//对象的方法的执行的上下文是对象,所以this指向对象
</script>
1
2
3
4
5
6
7
8
9
10
11
var foo = "bar";
console.log(this.foo);//bar
var obj = {
foo:"objBar",
test:function (){
return function(){
console.log(this.foo);//bar
}
}
}
obj.test()();//注意区分这两种情况:test()返回的是一个函数,函数直接在Window环境下执行

2.5 如果函数体是数组中的元素,那么函数作为数组的元素被调用的时候,指向数组,对于伪数组来书也是指向伪数组

1
2
3
4
5
6
7
8
9
10
11
function fn1 (){
console.log("fn1");
console.log(this);
}
function fn2 (){
console.log("fn2");
console.log(this[2]);
}
var arr = [fn1,fn2,'things'];
arr[0]();//fn1 [function, function, "things"]
arr[1]();//fn2 things
1
2
3
4
5
6
7
8
9
10
11
12
13
function fn (){
console.log(this.length);
}
var obj = {
length : 10 ,
method : function(f){
console.log(this.length);//10
console.log(arguments);
arguments[0]();//1 this.length代表传入参数的个数
arguments[0].call(this);//10
}
}
obj.method(fn);

this指向被调用的直接对象 ,被谁调用指向谁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script>
var foo = "winBar";
var out = {
foo:"outerBar",
outFunc:function(){
console.log(this);//this 代表out对象
console.log(this.foo);//执行后输出 outerBar
},
inner:{
foo:"innerBar",
innerFunc:function(){
console.log(this);//this代表 inner对象
console.log(this.foo);//执行后输出 innerBar
}
}
}
out.outFunc();//outFunc 函数被out这个对象调用,所以其内部的this指向out这个对象
out.inner.innerFunc();//innerFunc函数被inner这个对象调用,所以其内部的this指向inner这个对象
//函数体被谁调用指向谁
</script>

总结:函数直接执行相当于被window调用,所以函数中的this指向window

2.5 javascript 中经常用的也就是事件的绑定,对应的事件处理函数中如果出现了this,那么这个this代表的所绑定的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<input type="button" value="按钮"/>
<script>
document.querySelector("input").onclick = function(){
console.log(this.value);//按钮 这个this指向input标签
}
</script>
</body>
</html>

2.6 setInterval(fn,time) setTimeout(fn,time) 中的this指向window;为什么呢?大家还记得 setInterval setTimeont都是window对象的属性吗?setInterval(fn,time) 等价于 window.setInterval(fn,time)其实是window调用了setInterval方法,所以其指向window ;

1
2
3
4
5
6
7
8
<script>
var foo = "bar";
var setTime = setInterval(function(){
var foo = "changedBar";
console.log(this.foo);//bar
},1000)
console.log(setTime);//setInterval函数返回以个ID值,代表当前定时器,用于后期可以清除该计时器用
</script>

3 prototype this

原型里面的this指向,包括实例化的方法中的this指向:可以这么理解,它指向实例化的那个对象,但是,在找相应的属性的时候,它会顺着原型链一级级向上找,直到找到那个属性,如果没有找到,那么返回undefined;

这个其实也是函数独立调用还是被对象调用的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
function Test(){
this.foo= "bar";
this.logFoo = function(){
console.log(this.foo);//this代表实例化之后的对象
};
}
var test1 = new Test();
console.log(test1);
//Test{foo: "bar" ,logFoo: (),__proto__: Objectconstructor: Test(),__proto__: Object}
//关于new出来的对象可以暂时这么理解,然后该对象的地址赋值给test1;
console.log(test1.foo);//bar
test1.logFoo();//bar
</script>

通过原型添加属性和方法:this会顺着对象的原型链一直往下找,直到找到该属性为止,如果找不到的话,那么返回undefined;原型里的this也是指向实例化的对象,this.foo就是顺着原型链一直找;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
function Test(){
}
Test.prototype.foo = "changedBar";
Test.prototype.logFoo = function(){
console.log(this.foo);
};
var test1 = new Test();
console.log(test1);
// Test{__proto__: Object,constructor: Test(),foo: "changedBar",logFoo: (),__proto__: Object
console.log(test1.foo);//changedBar
test1.logFoo();//changedBar
</script>

4

三 this总结

1 this永远指向函数运行的时候所在的对象,而不是被创建的时候所在的对象;

2 匿名函数或不处于任何对象中的函数指向window

3 如果是call apply with 指定的this是谁,就是谁,如果是普通的函数调用,函数被谁调用,this就指向谁

4 如果不是被new调用函数,那么函数体的this都是指代全局范围的this,代表window对象;

5 函数独立运行的时候,那么this指向其所在运行的环境;函数作为对象的方法被调用的时候,this指向调用该函数的对象

6 如果调用者函数,被某一个对象所拥有,那么该函数在调用时,内部的this指向该对象。

如果函数独立调用,那么该函数内部的this,则指向undefined;但是在非严格模式中,当this指向undefined时,它会被自动指向全局对象。

严格模式下this指向

1
2
3
4
5
6
7
function fn() {
'use strict';
console.log(this);
}
fn(); //undefined // fn是调用者,独立调用
window.fn(); //window // fn是调用者,被window所拥有
1
2
3
4
5
6
'use strict';
var a = 20 ;
function fn(){
console.log(this.a);
}
fn();//报错

Critial Rendering Path

发表于 2016-11-02 | 分类于 render

1 理解关键渲染路径是提高页面性能的关键所在。总体来说,关键渲染路径分为六步。

  • 创建DOM树(Constructing the DOM Tree)
  • 创建CSSOM树(Constructing the CSSOM Tree)
  • 执行脚本(Running JavaScript)
  • 生成渲染树(Creating the Render Tree)
  • 生成布局(Generating the Layout)
  • 绘制(Painting)

创建 DOM 树

DOM(文档对象模型)树是HTML页面完全解析后的一种表示方式。从根元素开始,页面上每个元素或者文本都会创建一个对应的节点。每个节点都包含了这个元素的所有属性,并且嵌套在元素内的元素会被解析成外层元素对应的节点的子节点。

创建CSSOM 树

CSSOM(CSS对象模型)树是对附在DOM结构上的样式的一种表示方式。它与DOM树的呈现方式相似,只是每个节点都带上样式 ,包括明确定义的和隐式继承的。

CSS是一种渲染阻塞资源(render blocking resource),它需要完全被解析完毕之后才能进入生成渲染树的环节。CSS并不像HTML那样能执行部分并显示,因为CSS具有继承属性, 后面定义的样式会覆盖或者修改前面的样式。如果我们只使用样式表中部分解析好的样式,我们可能会得到错误的页面效果。所以,我们只能等待CSS完全解析之后,才能进入关键渲染路径的下一环节。

需要注意的是,只有CSS文件适用于当前设备的时候,才能造成渲染阻塞。标签接受media属性,该属性规定了此处的CSS文件适用于哪种设备。如果我们有个设备属性值为orientation: landscape(横向)的样式,当我们竖着浏览页面的时候,这个CSS资源是不会起作用的,也就不会阻塞渲染的过程了。

因为JavaScript脚本的执行必须等到CSSOM生成之后,所以说CSS也会阻塞脚本(script blocking)。

执行JavaScript

JavaScript是一种解析阻塞资源(parser blocking resource),它能阻塞HTML页面的解析。

当页面解析到<script>标签,不管脚本是內联的还是外联,页面解析都会暂停,转而加载JavaScript文件(外联的话)并且执行JavaScript。这也是为什么如果JavaScript文件有引用HTML文档中的元素,JavaScript文件必须放在那个元素的后面。

为了避免JavaScript文件阻塞页面的解析,我们可以在<script>标签上添加async属性,使得JavaScript文件异步加载。

生成渲染树

渲染树是DOM和CSSOM的结合,是最终能渲染到页面的元素的树形结构表示。也就是说,它包含能在页面中最终呈现的元素,而不包含那些用CSS样式隐藏的元素,比如带有display: none;属性的元素。

生成布局

布局决定了视口的大小,为CSS样式提供了依据,比如百分比的换算或者视口的总像素值。视口大小是由meta标签的name属性为viewport的内容设置所决定的,如果缺少这个标签,默认的视口大小为980px。

绘制

最后,页面上可见的内容就会转化为屏幕上的像素点。

2 绘制过程所需要花费的时间取决于DOM的大小以及元素的CSS样式。有些样式比较耗时,比如一个复杂的渐变背景色比起简单的单色背景需要更多的时间来渲染。

转化: 浏览器从磁盘或网络读取 HTML 的原始字节,浏览器会将这段原始文件按照相应编码规范进行解码(现在一般为 utf-8)。

符号化:根据 W3C 标准转化为对应的符号(一般在尖括号内)。

DOM 构建:HTML 解析器会解析其中的 tag 标签,生成 token ,遇到 CSS 或JS 会发送相应请求。HTML 解析时阻塞主进程的,CSS 一般也是阻塞主进程的(媒体查询时例外),也就是说它们在解析过程中是无法做出响应的。而 JS 手动添加 async 后达到异步加载,根据 token 生成相应 DOM 树。

CSSDOM 构建,添加 CSS 样式生成 CSSDOM 树。

渲染树构建,从 DOM 树的根节点开始,遍历每个可见的节点,给每个可见节点找到相应匹配的 CSSOM 规则,并应用这些规则,连带其内容及计算的样式。

样式计算,浏览器会将所有的相对位置转换成绝对位置等一系列的样式计算。

布局,浏览器将元素进行定位、布局。

绘制,绘制元素样式,颜色、背景、大小、边框等。

合成,将各层合成到一起、显示在屏幕上。

其他优化:

  • 使用 requestAnimationFrame,将 setTimeout 换成requestAnimationFrame,因为 setTimeout 时间控制可能造成在一帧的中间,目前各浏览器对 requestAnimationFrame 的支持已经比较好了。
  • 使用 Web Workers,将复杂计算的 JS 采用 Web Workers 进行处理。
  • 减少垃圾回收,垃圾回收是一个容易被忽略的问题,因为垃圾回收的时间是不受控制的,它可能在一个动画的中途,阻塞动画的执行,更理想的情况是在循环中复用对象

3 浏览器计算 DOM 元素的几何信息的过程:元素大小和在页面中的位置。每个元素都有一个显式或隐式的大小信息,决定于其 CSS 属性的设置、或是元素本身内容的大小、或者是其父元素的大小。在Blink/WebKit 内核的浏览器和 IE 中,这个过程称为 Layout。在基于 Gecko 的浏览器(比如 Firefox)中,这个过程称为Reflow。

3.1 避免触发布局

目前,transform 和 opacity 只会引起合成,不会引起布局和重新绘制。整个流程中比较耗费性能的布局和绘制流程将直接跳过,性能显然是很好的。其他的 CSS 属性改变引起的流程会有所不同,有些属性也会跳过布局,具体可以查看 CSS Triggers。所以,优化的第一步就是尽可能避免触发布局

3.2 为什么 DOM 很慢

谈到这里需要对浏览器利用 HTML/CSS/JavaScript 等资源呈现出精彩的页面的过程进行简单说明。浏览器在收到 HTML 文档之后会对文档进行解析开始构建 DOM (Document Object Model) 树,进而在文档中发现样式表,开始解析 CSS 来构建 CSSOM(CSS Object Model)树,这两者都构建完成后,开始构建渲染树

在每次修改了 DOM 或者其样式之后都要进行 DOM树的构建,CSSOM 的重新计算,进而得到新的渲染树。浏览器会利用新的渲染树对页面进行重排和重绘,以及图层的合并。通常浏览器会批量进行重排和重绘,以提高性能。但当我们试图通过 JavaScript 获取某个节点的尺寸信息的时候,为了获得当前真实的信息,浏览器会立刻进行一次重排。

3.3 避免强制性同步布局

在 JavaScript 中读取到的布局信息都是上一帧的信息,如果在 JavaScript 中修改了页面的布局,比如给某个元素添加了一个类,然后再读取布局信息。这个时候为了获得真实的布局信息,浏览器需要强制性对页面进行布局。因此应该避免这样做。

3.4 批量操作 DOM

在必须要进行频繁的 DOM 操作时,可以使用 fastdom 这样的工具,它的思路是将对页面的读取和改写放进队列,在页面重绘的时候批量执行,先进行读取后改写。因为如果将读取与改写交织在一起可能引起多次页面的重排。而利用 fastdom 就可以避免这样的情况发生。

虽然有了 fastdom 这样的工具,但有的时候还是不能从根本上解决问题,比如我最近遇到的一个情况,与页面简单的一次交互(轻轻滚动页面)就执行了几千次 DOM 操作,这个时候核心要解决的是减少 DOM 操作的次数。这个时候就要从代码层面考虑,看看是否有不必要的读取。

3.5 使用 requestAnimationFrame 来更新页面

我们希望在每一帧刚开始的时候对页面进行更改,目前只有使用 requestAnimationFrame 能够保证这一点。使用setTimeout 或者 setInterval 来触发更新页面的函数,该函数可能在一帧的中间或者结束的时间点上调用,进而导致该帧后面需要进行的事情没有完成,引发丢帧。

3.6 优化 CSS

CSS 选择器在匹配的时候是由右至左进行的,因此最后一个选择器常被称为关键选择器,因为最后一个选择越特殊,需要进行匹配的次数越少。要千万避免使用 *(通用选择器)作为关键选择器。因为它能匹配到所有元素,进而倒数第二个选择器还会和所有元素进行一次匹配。这导致效率很低下。

总结

我们可以利用Chrome开发者工具下的Timeline去观察整个关键路径渲染的过程。

ES6 解构赋值

发表于 2016-11-01 | 分类于 javascript ES6

解构赋值

先记住一点,无论是数组的解构赋值还是对象的解构赋值,根本是赋值操作;

  • 数组的解构赋值根据变量的位置对应赋值
  • 对象的解构赋值根据匹配的模式进行赋值
  • 解构赋值必须符合语法
1
2
let {a,b};//报错
let [a,b];//报错

解构可以避免在对象赋值时产生中间变量:

1 数组的解构赋值(解构赋值的时候,必须符合解构赋值的格式,数据必须是可迭代的结构)

数组的元素是按次序排列的,变量的取值由它的位置决定

1
2
3
let [a,b,c] = [1,2,3]
//等价于
let a = 1, b = 2 , c = 3 ;

如果左边变量多余右边值,多余的变量将被赋值为undefined

1
2
let [a,b,c] = [1,2]
console.log(c) ;//undefined

如果左边变量少于右边值,右边多余的值将会被忽略

1
let [a,b,c] = [1,2,3,4]

“链式”赋值:其实核心还是 赋值运算符的运算过程: 返回 = 右边的运算数,从右向左进行运算

1
2
3
4
let arr,first,second ;
arr = [first,second] = [1,2,3,4];
//arr // [1,2,3,4]
//first 1 second 2

变量值的”优先级”: 右边undefined < 左边默认值 < 右边除了undefined之外的其他值

1
2
3
4
let [a = 1 ,b=2 ,c] = [undefined,2222,3]; //右侧undefined不会赋值给a,右侧2222会赋值给b
console.log(a);//1
console.log(b);//2222
console.log(c);//3

解构赋值的原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let [a = b,b=5,c] = [1];
console.log(a);//1
console.log(b);//5
console.log(c);//undefined
//等价于 let a = 1, b = 5 , c = undefined ;
//以下情况会报错
let [a = b,b=5,c] = [];//直接报错
console.log(a);//
console.log(b);//
console.log(c);//
//等价于 let a = b , b = 5 , c = undefined ;//let声明的变量使用之前必须先被声明
--------------------------------------------
// let a = b ;//b is not defined
// let b = 5 ;
// let c ;
//对于let声明变量,变量的使用必须在声明之前才可以使用

看下var声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var [a = b,b=5,c] = [];//这个不会报错,所以需要理解数据结构赋值的原理
console.log(a);//undefined
console.log(b);//5
console.log(c);//undefined
//--------------------------------------------------
//左边进行声明
// var a = b ;//这个相当于对 b 进行了变量提升,但是没有运行到b=5的时候,a的值被赋予undefined;
// var b = 5 ;
// var c ;
// console.log(a);//undefined
//然后右边进行赋值,原来有默认值的不会发生变化
// ------------------------------------------------------
// var a = b ;//b is not defined
//
// var c ;
// console.log(a);//
// ----------------------------------------------------------
1
2
3
4
5
6
7
//如果等号的右边不是数组或者严格地说,不是可遍历的结构,那么将会报错。如下
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};

2 对象的解构赋值

对象的属性没有次序,变量必须与属性同名,才能取到正确的值

对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者

1
2
3
4
5
let {foo,bar} = {foo:"aaa",bar:"bbb"};
let {bar,foo} = {foo:"aaa",bar:"bbb"};
//foo aaa
//bar bbb
//这点和数组的结构赋值不同,数组的结构赋值是按照元素的位置进行解构赋值,而对象的解构赋值是根据无序的属性名进行匹配,然后在进行赋值

如果匹配不到对应的属性名,那么将会被赋值为undefined

1
2
let {baz} = {foo1:'aaa',foo2:'bbb'}
console.log(baz);//undefined
1
2
let {baz} = {}
console.log(baz);//undefined

对象的解构赋值,根本是给属性的属性值进行的赋值

1
2
let{foo} = {foo:'aaa',bar:'bbb'};
console.log(foo);//aaa
1
2
3
let{foo:baz} = {foo:'aaa',bar:'bbb'};//采用这种写法时,变量的声明和赋值是一体的
console.log(baz);//aaa
console.log(foo);//foo is not defined

从上面两段代码可以看出来,其实对象的解构赋值,

  • 根本还是对象的属性的简洁语法表示
  • 解构赋值是先进行变量的声明
1
2
let { foo,bar} = { foo: "aaa", bar: "bbb" };
let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };

如果是采用以下赋值形式{ } 必须用( ) 括起来,否则浏览器会认为那是一个代码块,会报错

1
2
({foo:baz} = {foo:'aaa'});
console.log(baz);//aaa

为了说明对象的解构赋值的匹配模式的,看下面的代码

1
2
3
4
let{foo:baz} = {};
console.log(baz);//undefined
console.log(foo);//foo is not defined
//这个时候,我们应该能理解 let{foo:baz} 声明的变量是baz ,并不是foo,foo是匹配的模式,匹配的根据

体会下下面的两段代码

1
2
3
4
5
6
console.log(name);//报错 name is not defined
let {heheh:name} = {heheh:'aaa'};
console.log(name);
//这个也证明了 let {heheh:name} 声明的是 : 后面的变量
//而let{foo ,bar } 其实是分为两步,第一对象的简洁写法{foo,bar} ==> {foo:foo,bar:bar}
//所以let声明的变量还是 : 冒号后面的变量,然后在进行解构赋值操作
1
2
3
console.log(name);//aaa
({heheh:name} = {heheh:'aaa'});
console.log(name);//aaa

对象的解构赋值是可以嵌套的,同样对于匹配模式(属性名)还是要有深刻的认识

1
2
3
4
5
({p:[x,{y:z}]} = {p:['hello',{}]} );
console.log(x);//hello
console.log(z);//undefined
console.log(y);//y is not defined
// p 和 y 都是匹配模式

undefined再取属性的时候会报错

1
2
3
let _temp = {bar:"baz"};
console.log(_temp.foo);//undefined
console.log(_temp.foo.anything);// Cannot read property 'anything' of undefined

如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,那么将会报错。

1
2
let {foo:{bar:baz}} = {hehe:{bar:'value'}}
//上面代码中,等号左边对象的foo属性,对应一个子对象。该子对象的bar属性,解构时会报错。原因很简单,因为foo这时等于undefined,再取子属性就会报错,请看下面的代码

NodeJs Module System

发表于 2016-10-27 | 分类于 nodejs

1 Node.js 模块系统

模块是Node.js 应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个 Node.js 文件就是一个模块,这个文件可能是JavaScript 代码、JSON 或者编译过的C/C++ 扩展。

模块之间的通信方式 : require引入 module.exports 和 exports 暴露某个模块的对外接口

2 创建一个模块

2.1 首先来看下node本身模块是什么

2.1.1 b.js文件

1
2
console.log(module);
console.log(module.exports);

执行命令 node b.js 输出如下

1
2
3
4
5
6
7
8
9
10
11
12
Module {
id: '.',
exports: {},
parent: null, //
filename: 'F:\\workspace\\01-node\\b.js', //代表文件的路径
loaded: false,
children: [],
paths:
[ 'F:\\workspace\\01-node\\node_modules',
'F:\\workspace\\node_modules',
'F:\\node_modules' ] }
{} //这个是module.exports 本身是一个空对象

a.js 文件 require(package) 返回值是module.exports

1
2
var res = require('./b.js');
console.log(res);

执行命令 node a.js 输出如下

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
Module {
id: 'F:\\workspace\\01-node\\b.js',
exports: {},
parent:
Module {
id: '.',
exports: {},
parent: null,
filename: 'F:\\workspace\\01-node\\a.js',
loaded: false,
children: [ [Circular] ],
paths:
[ 'F:\\workspace\\01-node\\node_modules',
'F:\\workspace\\node_modules',
'F:\\node_modules' ] },
filename: 'F:\\workspace\\01-node\\b.js',
loaded: false,
children: [], //被该模块引用的模块对象。
paths:
[ 'F:\\workspace\\01-node\\node_modules',
'F:\\workspace\\node_modules',
'F:\\node_modules' ] }
{} //以上是b.js输出
{} //这个是a.js输出

由此可得知require返回的是所引的package的module.exports;

所以我们可以通过改变exports的值

2.1.2返回一个构造函数

b.js

1
2
3
4
module.exports = function(){
}
console.log(module.exports);

执行 node b.js

1
[Function]

执行 node a.js

1
2
[Function] //这个是b.js的输出 console.log(module.exports);
[Function] //这个是a.js的输出 console.log(res);

2.1.3 或者直接给module.exports对象添加属性

b.js

1
2
module.exports.prop = function(){}
console.log(module.exports);

执行node b.js

1
{ prop: [Function] }

此时a.js通过require引用的时候返回的就是这个对象

以下实例便于理解:

2.2 exports.prop 形式创建模块

1
2
3
4
//sayHello.js
exports.sayhi = function(){ //exports对象可以定义sayhello.js将该模块的接口
console.log("this is a module we made");
}

引入创建的模块

1
2
3
4
//main.js
var hello = require('./sayHello');
console.log(hello) ;//{ sayhi: [Function] }
hello.sayhi();

以上实例中,代码 require(‘./sayhello’) 引入了当前目录下的sayhello.js文件(./ 为当前目录,node.js默认后缀为js)。Node.js 提供了exports 和 require 两个对象,其中 exports 是模块公开的接口,require 用于从外部获取一个模块的接口,即所获取模块的 exports 对象。

2.3 module.exports = function(){ }

如果希望模块根导出为一个函数(比如构造函数)或一次导出一个完整的对象而不是每次都创建一个属性,可以把它赋值给 module.exports 而不是 exports。

1
2
3
4
5
6
//square.js
module.exports = (width) => {
return{
area : () => width*width
}
}
1
2
3
4
5
6
//main.js
const square = require('./square');
console.log(square);//[Function]
var res = square(2);//{ area: [Function: area] }
console.log(res);
console.log(res.area());
1
2
3
4
5
6
7
const square = require('./square');
//可以理解为
const square = (width) => {
return{
area : () => width*width
}
}

返回一个构造函数

1
2
3
4
5
6
7
8
9
//myevents.js
module.exports = function myEvents(){
this.on = function(){
console.log("通过on绑定事件");
};
this.emit = function(){
console.log("通过emit触发事件");
};
}
1
2
3
4
5
6
7
//main.js
const Event = require('./myevents');
console.log(Event);
var event = new Event();
console.log(event);
console.log(event.on);
console.log(event.emit);
1
2
3
4
5
6
7
8
9
10
const Event = require('./myevents');
//可以理解为
const Event = function myEvents(){
this.on = function(){
console.log("通过on绑定事件");
};
this.emit = function(){
console.log("通过emit触发事件");
};
}

2.4 require(module) 的内部实现,就是返回所引用模块的module.exports

module.exports 和 exports 的区别 :

exports 变量是在模块的文件级别作用域内有效的,它在模块 被执行 前被赋于 module.exports的值。

它有一个快捷方式,以便 module.exports.prop= … 可以被更简洁地写成 exports.prop= …。 注意,就像任何变量,如果一个新的值被赋值给 exports,它就不再绑定到 module.exports;

之所以有这个简洁的写法,根本原因的node做了一个工作 exports在模块执行前被赋值 module.exports

2.4.1 b.js

1
2
3
4
console.log(module);
exports.name = "Jhon";
//可以被导出到引用的模块
console.log(module);

执行 node b.js 终端输出如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Module {
id: '.',
exports: {},
parent: null,
filename: 'F:\\workspace\\01-node\\10b.js',
loaded: false,
children: [],
paths:
[ 'F:\\workspace\\01-node\\node_modules',
'F:\\workspace\\node_modules',
'F:\\node_modules' ] }
Module {
id: '.',
exports: { name: 'Jhon' },
parent: null,
filename: 'F:\\workspace\\01-node\\10b.js',
loaded: false,
children: [],
paths:
[ 'F:\\workspace\\01-node\\node_modules',
'F:\\workspace\\node_modules',
'F:\\node_modules' ] }

a.js

1
var res = require('./b.js');//可以得到 b 模块定义的数据 { name: 'Jhon' }

2.4.2 b.js

1
2
3
4
console.log(module);
exports = {name:"Jhon"};//此时exports不再和module.exports指向同一块内存地址,最后导出的module.exports 还是未初始化的时候的一个空对象
//不会导出到引用的模块,只在模块内有效
console.log(module);

执行 node b.js 注意exports的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Module {
id: '.',
exports: {},
parent: null,
filename: 'F:\\workspace\\01-node\\10b.js',
loaded: false,
children: [],
paths:
[ 'F:\\workspace\\01-node\\node_modules',
'F:\\workspace\\node_modules',
'F:\\node_modules' ] }
Module {
id: '.',
exports: {},
parent: null,
filename: 'F:\\workspace\\01-node\\10b.js',
loaded: false,
children: [],
paths:
[ 'F:\\workspace\\01-node\\node_modules',
'F:\\workspace\\node_modules',
'F:\\node_modules' ] }

a.js

1
var res = require('./b.js');//此时得到 b 模块到处的内容 为 { }

2.4.3 b.js

1
2
3
console.log(module);
module.exports = {name:"Jhon"};
console.log(module);

执行 node b.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Module {
id: '.',
exports: {},
parent: null,
filename: 'F:\\workspace\\01-node\\10b.js',
loaded: false,
children: [],
paths:
[ 'F:\\workspace\\01-node\\node_modules',
'F:\\workspace\\node_modules',
'F:\\node_modules' ] }
Module {
id: '.',
exports: { name: 'Jhon' },
parent: null,
filename: 'F:\\workspace\\01-node\\10b.js',
loaded: false,
children: [],
paths:
[ 'F:\\workspace\\01-node\\node_modules',
'F:\\workspace\\node_modules',
'F:\\node_modules' ] }

a.js

1
var res = require('./b.js');//可以得到 b 模块定义的数据 { name: 'Jhon' }

2.4.4 根据以上的实验结果,可以大概得知require函数的内部实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//require()实现
function require(...) {
var module = { exports: {} };
((module, exports) => {
// 你的模块代码在这。在这个例子中,定义了一个函数。
function some_func() {};
exports = some_func;// exports在文件执行前被赋值为 module.exports,这里在重新赋值其他数据,
// 此时,exports 不再是一个 module.exports 的快捷方式,
// 且这个模块依然导出一个空的默认对象。
module.exports = some_func;
// 此时,该模块导出 some_func,而不是默认对象。
})(module, module.exports);
return module.exports; //require函数导出的是模块的 module.exports,而不是exports
}

2.5 require加载模块的查找规则

2.5.1 nodejs模块载入策略

Node.js的模块分为两类,一类为原生(核心)模块,一类为文件模块。原生模块在Node.js源代码编译的时候编译进了二进制执行文件,加载的速度最快。另一类文件模块是动态加载的,加载速度比原生模块慢。但是Node.js对原生模块和文件模块都进行了缓存,于是在第二次require时,是不会有重复开销的。其中原生模块都被定义在lib这个目录下面,文件模块则不定性

文件模块又分为以下三类模块,这三类文件以后缀名来区分,nodejs会根据后缀名来决定如何加载文件模块

  • .js。通过fs模块同步读取js文件并编译执行。
  • .node。通过C/C++进行编写的Addon。通过dlopen方法进行加载。
  • .json。读取文件,调用JSON.parse解析加载。

我们有没有考虑过这样一个情况,为什么一个文件没有声明require module却可以直接使用这些变量值呢?其实nodejs做了以下工作:

假如有一个 circle.js

1
2
3
4
5
6
7
var PI = Math.PI;
exports.area = function (r) {
return PI * r * r;
};
exports.circumference = function (r) {
return 2 * PI * r;
};

app.js

1
2
var circle = require('./circle.js');
console.log( 'The area of a circle of radius 4 is ' + circle.area(4));

当我们在终端执行命令 node app.js 的时候,nodejs会对app.js进行编译包装,包装之后其实就是

1
2
3
4
(function (exports, require, module, __filename, __dirname) {
var circle = require('./circle.js');
console.log('The area of a circle of radius 4 is ' + circle.area(4));
});

这就是为什么我们直接在终端输入命令 node filename.js的时候,可以直接调用 exports require module等

接下来我们来看下require加载文件的查找策略:

从文件模块缓存中加载

尽管原生模块与文件模块的优先级不同,但是都不会优先于从文件模块的缓存中加载已经存在的模块。

从原生模块加载

原生模块的优先级仅次于文件模块缓存的优先级。require方法在解析文件名之后,优先检查模块是否在原生模块列表中。以http模块为例,尽管在目录下存在一个http/http.js/http.node/http.json文件,require(“http”)都不会从这些文件中加载,而是从原生模块中加载。

原生模块也有一个缓存区,同样也是优先从缓存区加载。如果缓存区没有被加载过,则调用原生模块的加载方式进行加载和执行。

从文件加载

当文件模块缓存中不存在,而且不是原生模块的时候,Node.js会解析require方法传入的参数,并从文件系统中加载实际的文件,加载过程中的包装和编译细节在前一节中已经介绍过,这里我们将详细描述查找文件模块的过程,其中,也有一些细节值得知晓。

require方法接受以下几种参数的传递:

  • http、fs、path等,原生模块。
  • ./mod或../mod,相对路径的文件模块。
  • /pathtomodule/mod,绝对路径的文件模块。
  • mod,非原生模块的文件模块。

eval JSONparse

发表于 2016-10-27 | 分类于 javascript json

js 的 eval( ) 和 JSON.parse( ) 的用法和区别:

1 eval(string):这个全局函数可以用来接受一个字符串作为javascript代码去执行,也就是说,传入的参数可以作为脚本代码进行执行;比如创建变量,创建对象,执行函数等

  • 如果传入的值 是一个字符串,那么则将字符串作为代码执行,如果有返回值,则返回该值,如果没有返回值,则返回undefined
  • 如果传入的值 不是一个字符串,那么则直接返回传入的值
  • 需要特别注意的是对象的声明语法 { } 如果直接将{ } 声明的对象作为值传入,则直接返回该对象,如果是 ‘{ }’ 作为参数传入eval( )则会直接报错;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script>
eval("var a=1");//声明一个变量a并赋值1。传入参数是一个字符串,则将该字符串作为代码执行
console.log( eval("var a=1") );//undefined 因为没有返回值,所以结果是undefined
eval("2+3");//执行加运算,并返回运算值。
console.log( eval("2+3") );//5
eval("{b:2}");//声明一个对象。
eval("mytest()");//执行mytest()函数。
console.log( eval( {b:2,"nema":"jhon"}) );//Object {b: 2, nema: "jhon"},传入参数不是一个字符串,那么直接返回传入的值
console.log( eval('{"b":2,"nema":"jhon"}') );//会直接报错
//如果想要将字符串 '{"b":2,"nema":"jhon"}' 转化为常用的对象输出,则需要将其加上小括号,转化为表达式,才能正确输出对象,以下几种写法都是正确的
var code2 = '{"b":2,"nema":"jhon"}';
// console.log( eval('{"b":2,"nema":"jhon"}') );//浏览器报错
console.log( eval('('+code2+')') ); //Object {b: 2, nema: "jhon"}
console.log( eval('('+'{b:2,"nema":"jhon"}'+')') ); //Object {b: 2, nema: "jhon"}
console.log( eval('({b:2,"nema":"jhon"})') );//Object {b: 2, nema: "jhon"}
</script>

2 eval(string)函数的作用域:关键记住,val()函数并不会创建一个新的作用域,并且它的作用域就是它所在的作用域。这在所有主流浏览器都是如此,但是有时候需要将eval()函数的作用域设置为全局,当然可以将eval()在全局作用域中使用,但是往往实际应用中,需要在局部作用域使用具有全局作用域的此函数,这个时候可以用window.eval()的方式实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>
function test (){
eval("var num = 4");
console.log(num);//4
}
test();
// console.log(num);//num is not defined
function test1 (){
window.eval("var num = 4");
console.log(num);//4
}
test1();
console.log(num);//4
</script>

3 eval()可以将字符串解析为对象,同样JSON.parse()也可以将字符串解析为对象

1
2
3
4
5
var data = '{"b":2,"nema":"jhon"}'; //这是一个字符串
//第一种方法,通过eval()将其解析成对象
var objData1 = eval( "("+data+")" ); ////Object {b: 2, nema: "jhon"}
//第二种方法
var objData2 = JSONparse(data);//Object {b: 2, nema: "jhon"}

它们有什么区别和不同呢?

  • JSON.parse()之可以解析json格式的数据,并且会对要解析的字符串进行格式检查,如果格式不正确则不进行解析,而eval()则可以解析任何字符串,eval是不安全的。

  • JSON.parse( ‘ {“name”:”Jhon”,”age”:13 } ‘ ) 解析的结果是一个对象 {“name”:”Jhon”,”age”:13 }

  • JSON.parse( ‘ [{“name”:”Jhon”,”age”:13 } , {“name”:”Jhon”,”age”:13 }] ‘ ) 解析的结果是一个数组,里面有两个对象

    [{“name”:”Jhon”,”age”:13 } , {“name”:”Jhon”,”age”:13 }]

    总结来说:JSON.parse()可以将字符串类型的数组,转化为数组,将字符串类型的对象,转化为对象

  • eval( )里面可以传入非字符串的数据类型,会直接返回该类型值,比如直接传递数组 [ {“name”:”jhon”} ] 或者对象 {“name”:”jhon”} ,或者基本数据类型和复杂数据类型,但是JSON.parse( ),只能接受字符串类型的 ; 如果传入数组

    [ {“name”:”jhon”} ] 或者对象 {“name”:”jhon”} 会报错;

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
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<script>
//从后台返回的数据一般是字符串类型的,需要将其解析成javascirpt对象
var data = '[{"name":"jhon"},{"age":16}]';
// var data = [{"name":"jhon"},{"age":16}];
var objData1 = eval(data);
var objData2 = JSON.parse(data);
console.log("this is objData1"+objData1);
console.log(objData1);
console.log("this is objData2"+objData2);
console.log(objData2);
var num = 3 ;
console.log(eval(num));//3
console.log(eval(true));//true
console.log(eval(null));//null
console.log(eval(undefined));//undefined
console.log(eval({"name":"jhon"}));//Object {name: "jhon"} 对象
console.log(eval([{"name":"jhon"}]));//[Object] 数组
console.log(JSON.parse(num));//3
console.log(JSON.parse(true));//true
console.log(JSON.parse(null));//null
// console.log(JSON.parse(undefined));//报错
// console.log(JSON.parse({"name":"jhon"}));//报错
console.log(JSON.parse('{"name":"jhon"}'));Object {name: "jhon"} 对象
//console.log(JSON.parse([{"name":"jhon"}]));//报错
console.log(JSON.parse('[{"name":"jhon"}]'));//[Object] 数组
console.log(JSON.parse('[1,2,3]')); //[1,2,3]
</script>
</body>
</html>

NodeJs Buffer

发表于 2016-10-22 | 分类于 nodejs

1 Buffer类的定义:本质上是创建了一块专门存放二进制数据的缓存区

JavaScript 语言自身只有字符串数据类型,没有二进制数据类型。

但在处理像TCP流或文件流时,必须使用到二进制数据。因此在 Node.js中,定义了一个 Buffer 类,该类用来创建一个专门存放二进制数据的缓存区。

在 Node.js 中,Buffer 类是随 Node 内核一起发布的核心库。Buffer 库为 Node.js 带来了一种存储原始数据的方法,可以让 Node.js 处理二进制数据,每当需要在 Node.js 中处理I/O操作中移动的数据时,就有可能使用 Buffer 库。原始数据存储在 Buffer 类的实例中。一个 Buffer 类似于一个整数数组,但它对应于 V8 堆内存之外的一块原始内存。

2 如何创建Buffer实例

1
var buf = new Buffer(arg) //1 必须传入参数 2 传入的参数类型 string, Buffer, ArrayBuffer, Array, or array-like object.
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
var buf1 = new Buffer(10);//这个是指定Buffer实例的长度,区分 var buf1 = new Buffer('10');
console.log(buf1);
console.log(buf1.length);
console.log(buf1.toString());
var buf2 = new Buffer([10, 40, 30]);//数组的长度 作为Buffer实例的长度
console.log(buf2);
console.log(buf2.length);
//buf2.write('hahhahahahhah')
console.log(buf2.toString());
var buf3 = new Buffer({length:12});//类数组对象 的长度作为Buffer的长度
console.log(buf3);
console.log(buf3.length);
console.log(buf3.toString());
var buf4 = new Buffer('this is it yeah'); //直接将字符串写入Buffer实例,长度等于字符串的长度
console.log(buf4);
console.log(buf4.length);
console.log(buf4.toString());
var buf5 = new Buffer(buf4);//将Buffer实例直接作为参数传入
console.log(buf5);
console.log(buf5.length);
console.log(buf5.toString());

3 向缓冲区写入数据以及读取数据

1
2
3
4
5
6
7
8
buf.write(string[, offset[, length]][, encoding])
参数描述如下:
string - 写入缓冲区的字符串。
offset - 缓冲区开始写入的索引值,默认为 0 。
length - 写入的字节数,默认为 buffer.length
encoding - 使用的编码。默认为 'utf8' 。
返回值
返回实际写入的大小。如果 buffer 空间不足, 则只会写入部分字符串。
1
2
3
4
5
6
7
buf.toString([encoding[, start[, end]]])
参数描述如下:
encoding - 使用的编码。默认为 'utf8' 。
start - 指定开始读取的索引位置,默认为 0。
end - 结束位置,默认为缓冲区的末尾。
返回值
解码缓冲区数据并使用指定的编码返回字符串。
1
buf.length; 返回Buffer实例实际所占据的内存的长度
1
buf.toJSON();将Buffer实例转化为JSON对象
1
2
buf.slice(start,end);默认start 0 end 为buf.length
返回一个新的缓冲区,它和旧缓冲区指向同一块内存,但是从索引 start 到 end 的位置剪切。
1
Buffer.concat(list[, totalLength]) 返回一个多个成员合并的新 Buffer 对象。
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
//console.log(Buffer) 可以尝试着输入下Buffer类,看下其属性和方法
var buf = new Buffer('this is it yeah');
console.log(buf);
console.log(buf.length);//15
console.log(buf.toString());
var wLen = buf.write('changed?');
console.log(buf.toString());//changed?it yeah
console.log(wLen);//8
console.log(buf.toJSON());
/*{
type: 'Buffer',
data: [ 99, 104, 97, 110, 103, 101, 100, 63, 105, 116, 32, 121, 101, 97, 104 ]
}
*/
var sliceBuf = buf.slice(2,5);
console.log(sliceBuf);
console.log(sliceBuf.length);//3
console.log(sliceBuf.toString());//ang
var con = Buffer.concat ([buf,sliceBuf]);
console.log(con);
console.log(con.length);//18
console.log(con.toString());//changed?it yeahang

js event compare with jQuery event

发表于 2016-10-22 | 分类于 javascript event

js jQuery绑定事件的比较:

一 事件分类

1
2
3
4
5
6
7
8
9
1 window 事件:onload onunload onafterprint onbeforeprint onerror onresize
2 From表单 事件(仅在表单元素中有效):onblur onfocus onreset onsubmit onselect onchange oninput
3 键盘事件 : onkeydown onkeyup onkeyup
4 鼠标事件: onclick onmouseout onmouseleave onmouseenter onmousedown onmouseup onscroll(当元素滚动条被滚动的时候) ondrag ondragstart ondragend ondragenter ondragover ondragleave ondrop
//键盘事件和鼠标事件在base, bdo, br, frame, frameset, head, html, iframe, meta, param, script, style, title 元素无效;
5 触摸事件 :touchstart touchend touchmove
6 媒介事件 :
7 自定义事件:
8 所有的事件都是成元素的一个属性,原始值为null;输出一个DOM元素可以查看其所有的属性;

二 绑定事件

  • JS绑定事件:(onclick之类的本身就是一属性,通过给属性设置函数,可以使其具有功能,而addEventListener是一个函数,通过函数体的内容给元素绑定事件)
1
2
3
4
5
ele.on+事件类型 = function(){ }; ele.addEventListener("事件类型",function(){ } );
ele.onclick = function(){ }; ele.addEventListener("click",function(){ } );
ele.onmouseout = function(){ }; ele.attachEvent("onclick",function(){ } );(注意有on)
window.onlaod = function(){ }; window.addEventListener("load",function(){ } );
ele.ondragstart = function(){ }; ele.addEventListener("dragstart",function(){ } );

注意: 触摸事件只能通过addEventListener来绑定,过渡结束事件(自定义事件)

1
ele.addEventListener("touchstart",function(){ } );
  • JS移除事件(该事件通过什么方式绑定的就要通过什么方式移除)
1
2
3
4
5
6
7
8
9
10
11
12
// 1注册事件
my$("btn1").onclick = fn;//(onclick指向一个函数体)
//1移除事件
my$("btn1").onclick = null;//(将指向设置为null,则可以清除事件)
// 2 注册事件
my$("btn1").addEventListener("click",fn,false);
// 2 移除事件
my$("btn1").removeEventListener("click",fn,false);
// 3 注册事件 谷歌和火狐不支持
my$("btn1").attachEvent("onclick",fn);
// 3 移除事件
my$("btn1").detachEvent("onclick",fn);
  • jQuery绑定事件和移除事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
--ready(fn):当DOM元素载入就绪就可以执行的一个函数
data 是传递给jQuery事件对象参数event.data的数据,fn是绑定在该事件上的处理程序,events和type是要绑定的事件类型
--$("selector").on(events,data,fn) 可以一次绑定多个事件
$("selector").off(events,data,fn) 移除on绑定的事件 如果不写任何参数,则移除匹配元素的所有事件
$("selector").bind(type,data,fn) 可以一次绑定多个事件,可以绑定自定义事件;
$("selector").unbind(type,data,fn) 移除bind绑定的事件 如果不写任何参数,则移除匹配元素的所有事件
--$("selector").mouseover(), $("selector").mouseover(fn) 如果不写参数,将触发选定元素的该事件,如果写 了函数体,则将为选定的元素绑定事件
mouseout mousemove mouseenter mouseleave mouseup mousedown click keyup keydown change blur
都是一样的特性,如果不传函数体,就是触发该事件;如果传了函数体,就是绑定该事件的处理程序.
--$("selector").hover(fn(in)/fn(oout) 可以给选中的元素绑定hover事件,鼠标悬浮在上面的时候,执行 fn(in),鼠标离开元素的时候执行fn(out);
//事件委派 使用 delegate() 方法的事件处理程序适用于当前或未来的元素(比如由脚本创建的新元素)。
--$("selector1").delegate("selector2","type",fn) selector1必须是selector2的父元素,事件的执行程序绑定在selector2(子元素选择器)上 。

三 js 和 jQuery 绑定事件的区别

  • 两者绑定的事件符合事件的传播过程,比如冒泡,捕获
  • 两者的事件对象参数基本一致,不同点在于jQuery绑定的事件,事件对象参数多了两个参数,一个是event.originalEvent 一个是event.data

四 自定义事件底层实现原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 绑定事件
function addEvent(obj,type,fn){
obj.listener = obj.listener || {};
obj.listener[type] = obj.listener[type] || [];
obj.listener[type].push(fn);
}
// 触发事件
function fireEvent(obj,type){
var arr = obj.listener[type];
for (var i = 0; i < arr.length; i++) {
arr[i]();
}
}

URL operation

发表于 2016-10-22 | 分类于 javascript URL

如何操作查询字符串,返回一个查询字符串包含信息的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
var url ='http://www.myvirtual.com?username=Jhon&age=12';//将查询字符串转换为js对象
function queryParam(url){
var newUrl = url.slice(url.indexOf('?')+1);
var urlArr = newUrl.split('&');
var ret = {}
for(var i = 0 ; i < urlArr.length ; i++){
var arr = urlArr[i].split('=');
ret[arr[0]] = arr[1];
}
return ret ;
}
queryParam(url);

drag Event

发表于 2016-10-22 | 分类于 html

拖拉事件

拖拉指的是,用户在某个对象上按下鼠标键不放,拖动它到另一个位置,然后释放鼠标键,将该对象放在那里。

拖拉的对象有好几种,包括Element节点、图片、链接、选中的文字等等。在HTML网页中,除了Element节点默认不可以拖拉,其他(图片、链接、选中的文字)都是可以直接拖拉的。为了让Element节点可拖拉,可以将该节点的draggable属性设为true。注意draggable 是元素的属性,并不是元素的样式属性,设置在CSS样式表或者

RegExp Review

发表于 2016-10-22 | 分类于 javascript

先来看下正则中的特殊字符

特别字符 说明
$ 匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则 $ 也匹配 ‘\n’ 或 ‘\r’。要匹配 $ 字符本身,请使用 \$。
( ) 标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用。要匹配这些字符,请使用 \( 和 \)。
* 匹配前面的子表达式零次或多次。要匹配 字符,请使用 \。
+ 匹配前面的子表达式一次或多次。要匹配 + 字符,请使用 +。
. 匹配除换行符 \n之外的任何单字符。要匹配 .,请使用 \。
[ ] 标记一个中括号表达式的开始。要匹配 [,请使用 [。
? 匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。要匹配 ? 字符,请使用 \?。
\ 将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如, ‘n’ 匹配字符‘n’。’\n’ 匹配换行符。序列 ‘\‘ 匹配 “\”,而 ‘(‘ 则匹配 “(”。
^ 匹配输入字符串的开始位置,除非在方括号表达式中使用,此时它表示不接受该字符集合。要匹配 ^ 字符本身,请使用 \^。
{ } 标记限定符表达式的开始。要匹配 {,请使用 {。
\ 指明两项之间的一个选择。要匹配 \ ,请使用 \ 。

正则中的限定符

字符 描述
* 匹配前面的子表达式零次或多次。例如,zo 能匹配 “z” 以及 “zoo”。 等价于{0,}。
+ 匹配前面的子表达式一次或多次。例如,’zo+’ 能匹配 “zo” 以及 “zoo”,但不能匹配 “z”。+ 等价于 {1,}。
? 匹配前面的子表达式零次或一次。例如,”do(es)?” 可以匹配 “do” 或 “does” 中的”do” 。? 等价于 {0,1}。
{n} n 是一个非负整数。匹配确定的 n 次。例如,’o{2}’ 不能匹配 “Bob” 中的 ‘o’,但是能匹配 “food” 中的两个 o。
{n,} n 是一个非负整数。至少匹配n 次。例如,’o{2,}’ 不能匹配 “Bob” 中的 ‘o’,但能匹配 “foooood” 中的所有 o。’o{1,}’ 等价于 ‘o+’。’o{0,}’ 则等价于 ‘o*’。
{n,m} m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,”o{1,3}” 将匹配 “fooooood” 中的前三个 o。’o{0,1}’ 等价于 ‘o?’。请注意在逗号和两个数之间不能有空格。

正则中的操作符优先级

操作符 描述
\ 转义符
(), (?:), (?=), [ ] 圆括号和方括号
*, +, ?, {n}, {n,}, {n,m} 限定符
^, $, \anymetacharacter 位置和顺序
\ “或”操作

1 单词字符包括 0-9 a-z A-Z _ 下划线

元字符 \w 查找单词字符 \W 查找非单词字符

1
2
3
4
5
6
var reg = /\byou/g; // \b可以匹配单词的边界,单词中以you开头的you将会被匹配到
var result ;
while((result = reg.exec(str)) != null){
console.log(result);
console.log(reg.lastIndex);
}

2 \b 元字符匹配单词边界 在单词边界匹配的位置,单词字符后面或前面不与另一个单词字符直接相邻

\B 元字符匹配非单词边界 通常用于查找不处在单词的开头或结尾的匹配

1
2
3
4
5
6
7
8
9
var str = 'hello youthereyou yourighthere ';
var reg2 = /\Bthere/g ;//用来匹配 不在单词开头的there
var result ;
while((result = reg2.exec(str)) != null){
console.log(result);
console.log(reg2.lastIndex);
}
//---------------------------------------------
var reg2 = /there\B/g ;//匹配不在单词结尾的there

3 . 元字符用于查找单个字符,除了换行和行结束符。

1
2
3
4
5
6
7
8
9
10
//exec() 方法用于检索字符串中的正则表达式的匹配。如果找不到,则返回null
var str = 'that is hot';
var reg = /h.t/g;
var ret ;
while( (ret = reg.exec(str) ) != null){
console.log(ret);// Array[1][0:"hat"index:1,input:"that is hot",length:1]
//Array[1][0:"hot"index:8,input:"that is hot",length:1]
console.log(reg.lastIndex);// 4 11 exec()方法 RegExpObject 的 lastIndex 属性设置为匹配文本的最后一个字符的下一个位置
}
1
2
3
4
5
//match() 方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。返回匹配结果的数组,如果找不到返回null
var str = 'that is hot';
var reg = /h.t/g;
var ret = str.match(reg);
console.log(ret1);//["hat", "hot"]

4 \s

1…8910…20
JiM-W

JiM-W

keep fighting!

195 日志
52 分类
90 标签
© 2017 JiM-W
由 Hexo 强力驱动
主题 - NexT.Pisces