Linux常用命令.md

文件文件夹操作

复制

  • cp [源文件或目录] [目标文件或目录]
  • cp /TEST/test1/test01 /TEST/test2 假设复制源文件test1下的test01文件,目标目录为test2

  • cp -r /TEST/test1/. /TEST/test2 把test1中的文件夹及文件夹中所有文件复制到test2中

移动

  • mv [源文件或目录] [将要移动到的目录]
  • mv mongo.conf /etc 将mongo.conf文件移动到/etc目录下
  • mv -r /usr/local/mongodb /etc/usr/local路径下的mongodb文件夹移动到/etc目录下

删除

  • rm file 删除文件
  • rm -r folder 删除文件夹
  • rm -f file 强制删除

新建

  • touch file 新建空白文件
  • mkdir folder 在当前目录下生成a目录

压缩及解压缩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-c: 建立压缩档案
-x:解压
-t:查看内容
-r:向压缩归档文件末尾追加文件
-u:更新原压缩包中的文件
这五个是独立的命令,压缩解压都要用到其中一个,可以和别的命令连用但只能用其中一个。

下面的参数是根据需要在压缩或解压档案时可选的。
-z:有gzip属性的
-j:有bz2属性的
-Z:有compress属性的
-v:显示所有过程
-O:将文件解开到标准输出

参数-f是必须的
-f: 使用档案名字,切记,这个参数是最后一个参数,后面只能接档案名。
  • tar --cvf jpg.tar *.jpg //将目录里所有jpg文件打包成tar.jpg
  • tar --czf jpg.tar.gz *.jpg //将目录里所有jpg文件打包成jpg.tar后,并且将其用gzip压缩,生成一个gzip压缩过的包,命名为jpg.tar.gz
  • tar --cjf jpg.tar.bz2 *.jpg //将目录里所有jpg文件打包成jpg.tar后,并且将其用bzip2压缩,生成一个bzip2压缩过的包,命名为jpg.tar.bz2
  • tar --cZf jpg.tar.Z *.jpg //将目录里所有jpg文件打包成jpg.tar后,并且将其用compress压缩,生成一个umcompress压缩过的包,命名为jpg.tar.Z
  • tar --xvf file.tar //解压 tar包
  • tar -xzvf file.tar.gz //解压tar.gz
  • tar -xjvf file.tar.bz2 //解压 tar.bz2tar –xZvf file.tar.Z //解压tar.Z
  • tar -tf aaa.tar.gz在不解压的情况下查看压缩包的内容

文件读写及权限

  • vim mongo.conf 编辑文件
  • q! 不保存退出
  • qw! 强制保存退出
  • cat mongo.conf 查看文件内容
  • ls -l 文件名 查看linux文件的权限
  • ls -la 文件夹名称(所在目录) 查看linux文件夹的权限
  • ll 查看当前目录下各文件和目录的属性
  • sudo chmod -(代表类型)×××(所有者)×××(组用户)×××(其他用户) 文件名

    1
    2
    3
    4
    r=4 表示可读取,w=2 表示可写入,x=1 表示可执行
    若要rwx属性则4+2+1=7;
    若要rw-属性则4+2=6;
    若要r-x属性则4+1=7。
  • sudo chmod 777 file 每个人都有读和写以及执行的权限

端口以及进程状态检查

  • lsof -i:8080 查看8080端口哪个进程在使用
  • netstat -apn|grep mongo 查看mongo进程的端口ip使用
  • ps aux | less : 查看系统中所有进程
  • netstat -apn|grep a 检查a服务的监听等信息
  • telnet xxx.xxx.xxx.xxx port 端口测试

常规

  • history 查看历史执行命令

小技巧

  • ctrl+a 光标快速移动到行首
  • ctrl+e 光标快速移动到行尾
  • ctrl+u 快速删除光标到行首的命令
  • ctrl+k 快速删除光标到行尾的命令
  • 按一下Esc,放手在按一次右ctrl,可以调用上次命令的参数
  • lsb_release -a 查看系统版本

本机更换SSH公钥密钥后如何再通过ssh方式登录服务器.md

通常我们在够买vps的时候,或者第一次配置ssh登录的时候,就会将本机的ssh公钥添加的云平台上去,在初始化vps时,云平台会将添加的ssh公钥添加到用户的/user/.ssh/authorized_keys文件中,例如,如果用户是root用户,则公钥位于/root/.ssh/authorized_keys文件。这样,在vps中具有你本机的公钥,在本机使用下面命令就可以连接,大多数平台都是这样。

1
ssh root@yourVpsIp

但是又一次我作死了,由于谷歌云我使用ssh连接的时候用户弄错了,一直提示我没用次用户的公钥,不知道什么原因,我头脑一热,重新生成了本机的ssh公钥密钥,然后将谷歌云的上的公钥删掉,换成新生成的公钥。之久就怎么也连不上去了,以前使用浏览器的ssh连接方式也连不上了。开始了无尽的作死之路。

主要的因为,在vps初始化的时候,将平台的公钥添加的用户的ssh目录下,但是后面如果修改了平台的ssh公钥,vps用户中的authorized_keys并不会同步,只有将authorized_keys内容同步为新的公钥即可。但是谷歌云的话,由于将之前的公钥删除,怎么都连接不上去,都没法操作vps,所以只有重置vps,我选择了关停这台vps,重新购买了一台,将之前的静态IP重新绑定到新的vps。然后顺利连接。

上面的方法比较极端,下面就来介绍一个不用清空vps的方法。

由于我重新生成了本机的ssh公钥密钥,所有我的github,coding平台重新添加ssh公钥就可以了,这个和vps不一样,是可以同步的。但是我的digitalOcean服务器就没有那么好的运气了。也遇到了之前谷歌云一样的问题。登录时提示在vps中没有该用户的公钥。

解决方案:

1、在digitalOcean平台重新添加新的公钥

2、使用账号密码通过网页版控制台登录。
cat /root/.ssh/authorized_keys,发现是之前的ssh公钥,并没有的更新,准备手动修改。但是这个网页版做的很不好,没法复制粘贴命令,所以也没法将公钥贴进去,不可能一个一个手敲吧,然后也不能使用上传。这就比较麻烦了将次,后面想到使用文件下载,将文件传输到vps,替换原文件。一开始准备使用百度网盘链接,但是链接地址也是比较长,手敲也不现实,后面想到使用git下载。
3、使用git仓库下载。
在本地创建一个authorized_keys文件,将公钥复制粘贴进去,然后创建一个git仓库,将authorized_keys文件上传到仓库。然后再vps中安装git apt-get install git,使用git克隆这个仓库到指定的文件路径

1
git clone http://github.com/Xia-Ao/仓库名

这样vps就得到了authorized_keys,将root用户下的文件替换掉就可以了。
然后使用ssh root@yourVpsIP登录。

JavaScript继承实现的几种方法.md

JavaScript继承的实现

继承的实现基于原型链,参考js从原型到原型链

下面聊一聊JS继承实现的几种方法:

先定义一个父类Animal,有一个属性,一个实例方法,一个原型方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/** 初始化一个父类 **/
// 定义一个动物类
function Animal(name) {
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function () {
console.log(this.name + '正在睡觉!');
}
}

// 原型方法
Animal.prototype.eat = function (food) {
console.log(this.name + '正在吃:' + food);
};

1、原型链继承

将父类的实例作为子类的原型son.prototype = new father()

1
2
3
4
5
6
7
8
9
10
11
12
13
/** 1、原型链继承 - 将父类的实例作为子类的原型**/
function Cat() {
}

Cat.prototype = new Animal();
Cat.prototype.name = 'cat';

var cat = new Cat();
console.log(cat.name); //cat
cat.sleep(); //cat正在睡觉!
cat.eat('fish'); //cat正在吃:fish
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true

特点:

  1. 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
  2. 父类新增原型方法/原型属性,子类都能访问到
  3. 简单,易于实现

缺点:

  1. 可以在Cat构造函数中,为Cat实例增加实例属性。如果要新增原型属性和方法,则必须放在new Animal()这样的语句之后执行不能放到构造器中
  2. 无法实现多继承
  3. 来自原型对象的引用属性是所有实例共享的
  4. 创建子类实例时,无法向父类构造函数传参

2、构造函数继承

使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
这里用到了Animal.call(this);,就是讲Animal的属性和方法等创建到了cat对象里面,但是Animal的原型方法不能创建到cat对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
/** 2、构造函数 **/
function Cat(name) {
Animal.call(this);
this.name = name || 'Tom';
}

// Test Code
var cat = new Cat();
console.log(cat.name); //Tom
console.log(cat.sleep()); //Tom正在睡觉!
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
console.log(cat.eat('fish')); //报错Uncaught TypeError: cat.eat is not a function

特点:

  1. 解决了1中,子类实例共享父类引用属性的问题
  2. 创建子类实例时,可以向父类传递参数
  3. 可以实现多继承(call多个父类对象)

缺点:

  1. 实例并不是父类的实例,只是子类的实例
  2. 只能继承父类的实例属性和方法,不能继承原型属性/方法
  3. 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

3、实例继承

为父类实例添加新特性,作为子类实例返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/** 3、实例继承 **/
function Cat(name) {
var instance = new Animal();
instance.name = name || 'Tom';
return instance;
}

// Test Code
var cat = new Cat('jack');
console.log(cat.name); //jack
console.log(cat.sleep()); //jack正在睡觉!
console.log(cat.eat('fish')); //jack正在吃:fish
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // false

特点:

  1. 不限制调用方式,不管是new 子类()还是子类(),返回的对象具有相同的效果

缺点:

  1. 实例是父类的实例,不是子类的实例
  2. 不支持多继承

4、拷贝继承

这是没有什么特点,就是遍历对象,拷贝继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/** 4、拷贝继承 **/
function Cat(name) {
var animal = new Animal();
for (var p in animal) {
Cat.prototype[p] = animal[p];
}
Cat.prototype.name = name || 'Tom';
}

// Test Code
var cat = new Cat();
console.log(cat.name); //Tom
console.log(cat.sleep()); //Tom正在睡觉!
console.log(cat.eat('fish')); //Tom正在吃:fish
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true

特点:

  1. 支持多继承

缺点:

  1. 效率较低,内存占用高(因为要拷贝父类的属性)
  2. 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)

5、组合继承

结合原型继承和构造函数继承两种方法,通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/** 5、组合继承 **/
function Cat(name) {
Animal.call(this);
this.name = name || 'Tom';
}

Cat.prototype = new Animal();
Cat.prototype.constructor = Cat; //要修改函数的执行

// Test Code
var cat = new Cat();
console.log(cat.name); //Tom
console.log(cat.sleep()); //Tom正在睡觉!
console.log(cat.eat('fish')); //Tom正在吃:fish
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true

特点:

  1. 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
  2. 既是子类的实例,也是父类的实例
  3. 不存在引用属性共享问题
  4. 可传参
  5. 函数可复用

缺点:

  1. 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

6、寄生组合继承

通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/** 6、寄生组合继承 **/
function Cat(name) {
Animal.call(this);
this.name = name || 'Tom';
}

(function () {
// 创建一个没有实例方法的类
var Super = function () {
};
Super.prototype = Animal.prototype;
//将实例作为子类的原型
Cat.prototype = new Super();
})();

// Test Code
var cat = new Cat();
console.log(cat.name); //Tom
console.log(cat.sleep()); //Tom正在睡觉!
console.log(cat.eat()); //Tom正在吃:fish
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true

完美方法,但是复杂。

7、使用ES6中的extends实现继承

extends继承肯定是最好的继承方法,优先推荐使用.

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
/** 7、extends继承 **/
class Animal {
constructor(name) {
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function () {
console.log(this.name + '正在睡觉!');
}
}

eat(food) {
console.log(this.name + '正在吃:' + food);
}
}

class Cat extends Animal {
constructor() {
super('jack');
}
}

var cat = new Cat();
console.log(cat.name); //jack
cat.sleep(); //jack正在睡觉!
cat.eat('fish'); //jack正在吃:fish
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true

在实际应用中。

参考:JS实现继承的几种方式

Ajax介绍.md

关于Ajax请求这部分内容,有一篇博文江讲解的十分详细

原文地址:路易斯-Ajax知识体系大梳理

个人总结几个知识点

1、ajax请求,浏览器线程处理过程。


这个过程跨越了解一下浏览器重绘和回流

2、XMLHttpRequest属性来源于继承

1
xhr << XMLHttpRequest.prototype << XMLHttpRequestEventTarget.prototype << EventTarget.prototype << Object.prototype

ajax实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function loadXMLDoc()
{
var xml;
if (window.XMLHttpRequest)
{
// IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
xml=new XMLHttpRequest();
}
else
{
// IE6, IE5 浏览器执行代码
xml=new ActiveXObject("Microsoft.XMLHTTP");
}
xml.onreadystatechange=function()
{
if ((xml.readyState==4 && xml.status==200) || xml.status==304)
{
document.getElementById("myDiv").innerHTML=xml.responseText;
}
}
xml.open("GET","/try/ajax/ajax_info.txt",true);
xml.send();
}

3、XMLHttpRequest属性内容

一个xhr实例对象拥有10个普通属性+9个方法.
readyState

onreadystatechange
status
statusText
onloadstart
onprogress
onload
onloadend
timeout
ontimeout

4、jquery封装ajax方法

$.ajax是jquery对原生ajax的一次封装. 通过封装ajax, jquery抹平了不同版本浏览器异步http的差异性, 取而代之的是高度统一的api. jquery作为js类库时代的先驱, 对前端发展有着深远的影响. 了解并熟悉其ajax方法, 不可谓不重要.

5、Axios

  • Axios支持node, jquery并不支持.
  • Axios基于promise语法, jq3.0才开始全面支持.
  • Axios短小精悍, 更加适合http场景, jquery大而全, 加载较慢.
  • vue作者尤大放弃推荐vue-resource, 转向推荐Axios.

6、 CORS跨域资源共享

CORS是一个W3C(World Wide Web)标准, 全称是跨域资源共享(Cross-origin resource sharing).它允许浏览器向跨域服务器, 发出异步http请求, 从而克服了ajax受同源策略的限制. 实际上, 浏览器不会拦截不合法的跨域请求, 而是拦截了他们的响应, 因此即使请求不合法, 很多时候, 服务器依然收到了请求.(Chrome和Firefox下https网站不允许发送http异步请求除外)
CORS请求分为两种, ① 简单请求; ② 非简单请求.

满足如下两个条件便是简单请求, 反之则为非简单请求.(CORS请求部分摘自阮一峰老师博客)

1) 请求是以下三种之一:

  • HEAD
  • GET
  • POST

2) http头域不超出以下几种字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type字段限三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain

对于简单请求, 浏览器将发送一次http请求, 同时在Request头域中增加 Origin 字段, 用来标示请求发起的源, 服务器根据这个源采取不同的响应策略. 若服务器认为该请求合法, 那么需要往返回的 HTTP Response 中添加 Access-Control-_ 等字段.
对于非简单请求, 比如Method为POST且Content-Type值为 application/json 的请求或者Method为 PUT 或 DELETE 的请求, 浏览器将发送两次http请求. 第一次为preflight预检(Method: OPTIONS),主要验证来源是否合法. 值得注意的是:OPTION请求响应头同样需要包含 Access-Control-_ 字段等. 第二次才是真正的HTTP请求. 所以服务器必须处理OPTIONS应答(通常需要返回20X的状态码, 否则xhr.onerror事件将被触发).

4、跨越解决方案

跨域解决方案可以参考另一篇有关同源策略到前端跨域
ajax的跨域主要有CROS、 使用代理、JSONP、webSocket这几种方案,具体的都在上面同源策略到前端跨域

5、ajax文件上传

6、ajax请求二进制文件

7、ajax缓存处理

js中的http缓存没有开关, 受制于浏览器http缓存策略. 原生xhr请求中, 可通过如下设置关闭缓存.

1
2
3
xhr.setRequestHeader("If-Modified-Since","0");
xhr.setRequestHeader("Cache-Control","no-cache");
//或者 URL 参数后加上 "?timestamp=" + new Date().getTime()

jquery的http缓存是否开启可通过在settings中指定cache.

1
2
3
4
5
6
7
$.ajax({
url : 'url',
dataType : "xml",
cache: true,//true表示缓存开启, false表示缓存不开启
success : function(xml, status){
}
});
1
$.ajaxSetup({cache:false}); //全局关闭ajax缓存.

除此之外, 调试过程中出现的浏览器缓存尤为可恶. 建议开启隐私浏览器或者勾选☑️控制台的 Disable cache 选项.

8、 ajax错误处理

前面已经提过, 通常只要是ajax请求收到了http状态码, 便不会进入到错误捕获里.(Chrome中407响应头除外)

实际上, $.ajax 方法略有区别, jquery的ajax方法还会在类型解析出错时触发error回调. 最常见的便是: dataType设置为json, 但是返回的data并非json格式, 此时 $.ajax 的error回调便会触发.

9、ajax调试技巧

使用node-server配置服务器调试。如何搭建node-server参考他的另一篇node-server

JavaScript闭包.md

闭包

阮一峰学习闭包

闭包的概念

高程上关于闭包的定义:闭包是指有权访问另一个函数作用域中的变量的函数

简单理解就是:定义在一个函数内部的函数

在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

创建闭包的方式:在一个函数内部创建另一个函数

1
2
3
4
5
6
7
8
9
 function f1(){
    var n=999;
    function f2(){
      alert(n);
    }
    return f2;
  }
  var result=f1();
  result(); // 999

其中f2( )就是一个闭包能够访问它上一级函数f1()中的变量n。

闭包关于作用域链的图示:

闭包的用途

1、读取其他函数内部的变量

2、让这些变量的始终保持在内存中,这是一个双刃剑,可以方便的访问内部的变量,即使函数执行完成,其内部变量还保存在内存中可以访问,但是也会带来一系列的问题,外部函数的作用域链和变量对象不能销毁,占用内存。

来看一个典型的例子

1
2
3
4
5
6
7
8
9
10
var result = new Array();    
function createFunctions(){
for (var i=0; i < 10; i++){
result[i] = function(){
console.log(i);
};
}
}

result[6](); //10

从表面上看,似乎每一个函数都返回10 ,但实际上,每个函数都返回10, 因为每个函数的作用域链中都保存着父级函数的活动对象,他们的引用的都是同一个变量i,当父级函数return result 后,变量i的值变成10 ,每个函数引用保存变量i的同一个变量对象,因为此时i=10,所以引用变量指向的值都变成10。

解决这个问题最方便的一个方法是将闭包函数立即执行,每次引用都变成实际i对应的值。每次循环立即执行后销毁引用变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
var result = new Array();    
function createFunctions(){
for (var i=0; i < 10; i++){
result[i] = function(){
var c = i;
return function () {
console.log(c);
};
}();
}
}

result[6](); //6

闭包的注意点

1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

闭包中的this

匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window

闭包的this对象一般也是指向window。具体使用用要看具体的情况,有没有实例化对象,有没有使用apply() call() 等来改变this对象。

面试中问到关于函数闭包的问题

f(a)(b);

要求返回a+b的结果。

面试的时候面试官写出这样的一个表达式,猛一看这个心想坏了,这是什么表达式,我都没见过,一脸懵逼,一开始以为则是一个立即执行函数,仔细一看不对,这是一个函数执行之后再跟一个()表示执行,还好面试官提示一下,后面的括号就是表示执行函数,那前面的f(a)表示执行结果返回的是一个函数。

接下来就是写这个函数:两种思路,一种我自己想的,一种网上的,当然是晚上的比较好

1、使用闭包
1
2
3
4
5
6
7
function f(x) {
return function(y){
return x+y
}
}
console.log(f(2)); //返回一个函数
console.log(f(2)(3)); //5

这种方法明显的好,使用闭包,函数里面封装一个函数。最为关键的点在于,闭包就是能够读取其他函数内部变量的函数,就相当于把第一次执行时传入的参数x定义为了一个变量,因为在闭包函数中有对x的引用,所以在函数f执行完成之后,内存中x变量的值并没有被清空,在第二次执行的时候可以正常访问到x,并返回x+y。

其实这个问题在阮一峰-关于闭包的学习中有这个表达式,当时自己看的时候只注意到闭包的作用域,没有注意到函数执行表达式。

2、我的方法

我的方法就比较笨, 将闭包特有的性质单独拿出来实现。

1
2
3
4
5
6
7
8
9
10
11
let temp;
function f() {
if (temp) {
return temp * arguments[0]
} else {
temp = arguments[0]
return f;
}
}
console.log(f(2)); //返回一个函数
console.log(f(2)(3)); //5

数据结构之神奇的树.md

基础的就不说了,可以参考JavaScript数据结构与算法。

树的几个操作几点,rootnode.keynode.leftnode.right, 树的操作一般都会用到递归调用。

中序遍历

先序遍历

后序遍历

DOM遍历.md

DOM遍历

DOM的遍历分为先序遍历,中序遍历, 后序遍历,是三种不同的顺序的遍历方法

三种方法的区别以及原理请参考:

https://www.jianshu.com/p/456af5480cee

http://blog.csdn.net/u013468917/article/details/69556547

先序:考察到一个节点后,即刻输出该节点的值,并继续遍历其左右子树。(根左右)

中序:考察到一个节点后,将其暂存,遍历完左子树后,再输出该节点的值,然后遍历右子树。(左根右)

后序:考察到一个节点后,将其暂存,遍历完左右子树后,再输出该节点的值。(左右根)

这里只说一下用来遍历的方法:这篇博文写了五种先序遍历的方法,优先使用DOM中提供的两个专门用来遍历的方法。

这两个方法在《JavaScript高程》中有详细的介绍:可以参考第12章12.3

NodeIterator

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 使用DOM2的"Traversal"模块提供的NodeIterator先序遍历DOM树
* @param node 根节点
*/
function traversalUsingNodeIterator(node){
var iterator = document.createNodeIterator(node, NodeFilter.SHOW_ELEMENT,null,false);
var node = iterator.nextNode();
while(node != null){
console.log(node.tagName);
node = iterator.nextNode();
}
}

TreeWalker

NodeIterator更高级的一个版本,主要使用的nextNode()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 使用DOM2的"Traversal"模块提供的TreeWalker先序遍历DOM树
* @param node 根节点
*/
function traversalUsingTreeWalker(node){
var treeWalker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT,null,false);
if(node && node.nodeType === 1){
console.log(node.tagName);
}
var node = treeWalker.nextNode();
while(node != null){
console.log(node.tagName);
node = treeWalker.nextNode();
}
}

使用DOM扩展的Element Traversal API,递归遍历DOM树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 使用DOM扩展的Traversal API提供的新的接口先序遍历DOM树
* @param node 根节点
*/
function traversalUsingTraversalAPI(node){
if(node && node.nodeType === 1){
console.log(node.tagName);
}
var i = 0,len = node.childElementCount, child = node.firstElementChild;
for(; i < len ; i++){
traversalUsingTraversalAPI(child);
child = child.nextElementSibling;
}
}

JavaScript遍历方法对比以及适用对象.md

遍历方法

1、for循环

语法:

1
for\(let i=0; i&lt;length; i++\){ 代码}

说明:因为String对象具有length属性,所以可以直接使用String.length进行遍历

适用对象:Array,String,obj没有length,不可以使用for循环

2、for in

语法:

1
for( let index in对象){ 在此执行代码}

说明:for in 遍历的是key值

使用对象:Array String Object

3、for of

语法:

1
for(let value of 对象){ 在此执行代码}

说明:for of遍历的value值
只要具有遍历器接口Iterator,都可以使用for of 遍历,比如NodeList Set Map Array String 类数组

使用对象:Array String

for in 与for of 区别
  1. 推荐在循环对象属性的时候,使用for...in,在遍历数组的时候的时候使用for...of
  2. for...in循环出的是key,for...of循环出的是value
  3. 注意,for...of是ES6新引入的特性。修复了ES5引入的for...in的不足
  4. for...of不能循环普通的对象,需要通过和Object.keys()搭配使用

4、forEach

语法:

1
array.forEach(function(currentValue, index, arr), thisArg)

说明:arr当前元素所属的数组对象。

使用对象:Array 只有array对象有

其他数组遍历、map from filter some every等

再加上forEach,这些都是针对于Array进行遍历的,

String遍历

一种变换的方法,使用split将字符串切割为字符串数组,再对数组进行遍历。

Object遍历

除了前面写到的for in遍历Object对象,还有以下几种方法对Object进行遍历

2、Object.keys

语法:

1
2
3
4
Object.keys(obj)   //个表示给定对象的所有可枚举属性的字符串数组。
// 类数组对象
var obj = { 0: "a", 1: "b", 2: "c"};
console.log(Object.getOwnPropertyNames(obj).sort()); // ["0", "1", "2"]

说明:Object.keys返回一个由key值组成的可枚举属性的字符串数组,再对数组进行遍历既可以遍历Object

3、Object.getOwnPropertyNames

语法:

1
Object.getOwnPropertyNames(obj)  //在给定对象上找到的属性对应的字符串数组。

说明:返回一个数组,该数组对元素是 obj自身拥有的枚举或不可枚举属性名称字符串

4、Object.values()

语法:

Object.values(obj)  //一个包含对象自身的所有可枚举属性值的数组。、
//示例  随机键值的类数组对象
var an_obj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.values(an_obj)); // ['b', 'c', 'a']

说明:Object.values()返回一个数组,其元素是在对象上找到的可枚举属性值。属性的顺序与通过手动循环对象的属性值所给出的顺序相同。

5、Object.entries()

语法:

Object.entries(obj)  //给定对象自身可枚举属性的键值对数组。
//示例
const obj = { foo: 'bar', baz: 42 };
console.log(Object.entries(obj)); // [ ['foo', 'bar'], ['baz', 42] ]

说明:Object.entries()返回一个数组,其元素是与直接在object上找到的可枚举属性键值对相对应的数组。属性的顺序与通过手动循环对象的属性值所给出的顺序相同。

参考:JavaScript如何遍历Object

JS数据类型、运算符容易踩的几个坑.md

7种基本数据类型

  • 6种原始类型
    • null 表示缺少的标识,指示变量未指向任何对象。
    • undefined 是全局对象的一个属性
    • Boolean
    • Number
    • String
    • Symbol(ES6中新定义)
  • Object

另一种区分方式

1、值类型:即5种基本类型(string,number,boolean,null,undefined);

2、引用类型:即数组、函数、对象共三种。这三种类型的处理与值类型会有很大的差别。

JS中常用的内置对象

  • date时间对象
  • string字符串对象
  • Math数学对象,
  • Number数值对象,
  • Array数组对象,
  • function函数对象,
  • arguments函数参数集合,
  • Boolean布尔对象,
  • Error错误对象,
  • Object基础对象

记住以下结果

null与undefined区别

javaScript设计者最初是这样设计的:null是一个表示”无”的对象,转为数值时为0;undefined是一个表示”无”的原始值,转为数值时为NaN

后来在使用的过程中这样区分
null表示”没有对象”,即该处不应该有值。典型用法是:

(1) 作为函数的参数,表示该函数的参数不是对象。
(2) 作为对象原型链的终点。

undefined表示”缺少值”,就是此处应该有一个值,但是还没有定义。典型用法是:

(1)变量被声明了,但没有赋值时,就等于undefined。
(2) 调用函数时,应该提供的参数没有提供,该参数等于undefined。
(3)对象没有赋值的属性,该属性的值为undefined。
(4)函数没有返回值时,默认返回undefined。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typeof null        // object (因为一些以前的原因而不是'null')
typeof undefined // "undefined"
null === undefined // false
null == undefined // true
null === null // true
undefined === undefined// true
!null //true
!undefined //true
isNaN(1 + null) // false null转化为Number时为0
isNaN(1 + undefined) // true undefined转化为Number时为NaN
1+null //1
1+undefined //NaN

//但是要记住,null能转化为Number进行计算
null == 0 //false
undefined == 0 // false

null与Object

1
2
null==Object  //false
null===Object //false
1
2
3
4
5
[]==![]  //true
[]==false //true

![] //false
!{} //false

严格相等===

两个被比较的值在比较前都不进行隐式转换

非严格相等 ==

在比较前将两个被比较的值转换为相同类型。在转换后(等式的一边或两边都可能被转换),最终的比较方式等同于全等操作符
=== 的比较方式。

关于

Object.is(A,B)

区分和严格相等===之间的区别

1
2
3
4
5
6
7
8
Object.is(-0 , +0)  //false
-0 === +0 //true

Object.is(NaN , NaN) //true
NaN === NaN //false

[1,2] == '1,2' //true
[1,2] === '1,2' //false


ToNumber

1
2
Number('')  // 0
Number('a123') //NaN
input result
null +0
undefined NaN
Boolean +0 / 1
Number 不转换
String
Symbol 报错
Object ToNumber(ToPrimitive( input , Number))

ToPrimitive()

关于隐式转换参考这篇文章:js隐式装箱-ToPrimitive

默认用法:ToPrimitive(input [,
PreferredType]),(ToPrimitive方法可被修改)

  • 如果是Date求原值,则PreferredType是String,其他均为Number。
  • PreferredType是String,则先调用toString(),结果不是原始值的话再调用valueOf(),还不是原始值的话则抛出错误;
  • PreferredType是Number,则先调用valueOf()再调用toString()。

+ -操作符

在加法的过程中,首先把等号左右两边进行了求原值ToPrimitive()操作,然后如果两个原值只要有一个是String类型,就把两个原值都进行转化字符串ToString()操作,进行字符串拼接;否则把两个原值都进行转化数字ToNumber()操作,进行数字相加。

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
+[]  //0
+[0] //0
+{} //NaN

[] + [] // ""
// PreferredType是Number,则先调用valueOf()再调用toString()。 []==>[]==>""

{} + {} //"[object Object][object Object]"
//{} PreferredType是Number,则先调用valueOf()再调用toString() {}==>{}==>"[object Object]"

[] + {} // "[object Object]"
//[]==>[] ==>""
//{} PreferredType是Number,则先调用valueOf()再调用toString() {}==>{}==>"[object Object]"
// 然后字符串拼接

{} + [] // 0
// 原因在于{}在前,被当成{};直接执行了,变成了 +[],[]的原值是"",+[]需要进行toNumber转换变成了0 ,最后结果为0

++[[ ]][+[ ]]+[+[ ]]
//这个就有意思了,一开始对[][]理解错了,以为是二维数组,二维数组是name[i][j],这个才是对二维数组的调用,[][]是对前面那个一维数组的调用,数组名就是第一个[].
拆分:
>> ++[[]][+[]] + [+[]] 很明显 我们知道+[]的结果是0
>> ++[[]][0] + [0] 这里我们就看到是对数组[[]]的第0位进行操作,即[]
>> ++[] + [0]
>> ([]+1) + [0]
>> (''+1) + [0]
>> '1' + [0]
>>'10'

下列表达式输出什么

1
2
3
4
5
6
console.log(1 + "2" + "2");
console.log(1 + +"2" + "2");
console.log(1 + -"1" + "2");
console.log(+"1" + "1" + "2");
console.log( "A" - "B" + "2");
console.log( "A" - "B" + 2);
1
2
3
4
5
6
'122'
'32'
'02'
'112'
'NaN2'
NaN

运算过程注意一下几点:

  • 多个数字和数字字符串混合运算时,跟操作数的位置有关
1
2
console.log(2 + 1 + '3'); // '33'
console.log('3' + 2 + 1); //'321'
  • 数字字符串之前存在数字中的正负号(+/-)时,会被转换成数字,同样,可以在数字前添加
    ‘’,将数字转为字符串
1
2
3
4
console.log(typeof '3'); // string
console.log(typeof +'3'); //number
console.log(typeof 3); // number
console.log(typeof (''+3)); //string
  • 对于运算结果不能转换成数字的,将返回 NaN
1
2
consol.log('a' * 'sd'); //NaN
console.log('A' - 'B'); // NaN

给基本类型数据添加属性,不报错,但取值时是undefined

1
2
3
4
5
6
var a = 10;
a.pro = 10;
console.log(a.pro + a); //undefined
var s = 'hello';
s.pro = 'world';
console.log(s.pro + s); //'undefinedhello'

a.pro和s.pro都为undefined。给基本类型数据加属性不报错,但是引用的话返回undefined,10+undefined返回NaN,而undefined和string相加时转变成了字符串。

A && B 与 A || B

A && B
与运算,会做boolean转换,当A为false时返回A的结果,当A为true时返回B的结果,
A||B 或运算,当A为true时返回A的结果,当A为false时返回B的结果。

instanceof运算符详解

这里有一篇博文,详细介绍的instanceof运算符的历史由来以及定义

先说typeof运算符,判断数据类型,对于基本类型,返回基本类型,对于引用类型的,不管哪一种引用类型,都返回’object’

instanceof 运算符通常用来判断一个实例是否属于某种类型,使用方法为foo instanceof Foo返回一个Boolean值
因为这个原因,更多的时候在继承关系中,用来判断实例是否属于一个父类型

instanceof如何实现这样的功能,语言规范里面有详细的介绍,这里截取js表达的方式

1
2
3
4
5
6
7
8
9
10
11
12
>function instance_of(L, R) {//L 表示左表达式,R 表示右表达式
var O = R.prototype;// 取 R 的显示原型
L = L.__proto__;// 取 L 的隐式原型
while (true) {
if (L === null)
return false;
if (O === L)// 这里重点:当 O 严格等于 L 时,返回 true
return true;
L = L.__proto__;
}
}
>

由规范得到,instanceof将前面表达式的__proto__与后面的表达式的prototype进行严格相等===比较,
如果相等返回true,如果不相等,则继续循环获取前面表达式的__proto__,直到 Object.__proto__===null为止返回false。

1
2
3
4
5
6
7
8
9
console.log(Object instanceof Object);//true 
console.log(Function instanceof Function);//true
console.log(Number instanceof Number);//false
console.log(String instanceof String);//false

console.log(Function instanceof Object);//true

console.log(Foo instanceof Function);//true
console.log(Foo instanceof Foo);//false