GithubHelp home page GithubHelp logo

blog's Introduction

Blog

自己的一些学习笔记

Go!

blog's People

Contributors

scoutyin avatar

Stargazers

 avatar  avatar  avatar  avatar

Watchers

 avatar

blog's Issues

JavaScript中几种创建对象的模式

1、工厂模式

  工厂模式解决了创建多个相似对象的问题,但却没有解决对象识别的问题,也就是说不知道对象的类型;
function createPerson (name, age, job) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function () {
        alert(this.name);
    };
    return o;
}

var person1 = createPerson('Xiaoming', 23, 'Front-end');
var person2 = createPerson('Laowang', 23, 'Back-end');

2、构造函数模式

 构造函数模式解决了对象识别的问题,但是每个方法都要在实例上重新创建一遍,每个实例身上的属性和方法都是独有的一份,在内存资源上是一种浪费;
 function createPerson(name,age){
     this.name = name;
     this.age = age;
     this.say = function(){
        console.log(this.name + '---' + this.age)
    }
 }
 var person1 = new createPerson('jack',18);
 var person2 = new createPerson('tom',28);
console.log(person1.say ===person2.say)   //false

3、原型模式

好处是可以让所有对象实例共享它所包含的属性和方法:

function Person () {}
Person.prototype = {
    name: 'Jack',
    age: 23,
    sayName: function () {
       alert(this.name);
    }
}

但是其最大的问题也是由其共享的本性所导致的,比如:

function Person () {}
Person.prototype = {
    name: 'Jack',
    age: 23,
    friends: ['Wang', 'Zhang', 'Li'],
    sayName: function () {
       alert(this.name);
    }
}
var person1 = new Person();
var person2 = new Person();
person1.friends.push('Yin');
console.log(person1.friends)  //["Wang", "Zhang", "Li", "Yin"]
console.log(person2.friends)  //["Wang", "Zhang", "Li", "Yin"]
  • isPrototypeOf()判断是否存在原型关系;
  • Object.getPrototypeOf()返回一个对象的原型;
  • hasOwnProperty()检测一个属性是存在于实例中,还是存在于原型中,只在给定属性存在于对象实例中才会返回true;

这种原型写法会重写原型对象,导致Person.prototype.constructor指向了Object而非之前的Person,可以向下面这样将其设置回原来的值:

function Person () {}
Person.prototype = {
    constructor: Person,
    name: 'Jack',
    age: 23,
    sayName: function () {
       alert(this.name);
    }
}

但是这样会导致constructor属性的[[Enumerable]]特性被设为true,而默认情况下原生的constructor属性是不可枚举的,因此可以使用Object.defineProperty()

Object.defineProperty(Person.prototype, constructor, {
      enumerable: false,
      value: Person
})

还有一点很重要,重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系,也就是说重写原型对象其实是重新开辟了一块内存用于存放新的原型对象,而在重写原型之前创建的实例的[[prototype]]指针指向的是之前的内存地址,而不受重写后的原型对象影响。

4、组合使用构造函数模式和原型模式

 构造函数模式用于定义实例属性,原型模式用于定义方法和共用的属性;该方法使用最广泛,认同度最高;
function Person (name, age, job) {
     this.name = name;
     this.age = age;
     this.job = job;
     this.friends = ['Jack', 'Greg'];
}
Person.prototype = {
    constructor: Person,
    sayName: function () {
       alert(this.name);
    }
}

5、动态原型模式

function Person (name, age, job) {
     this.name = name;
     this.age = age;
     this.job = job;
     // 方法
     if (typeof this.sayName !== 'function') {
         Person.prototype.sayName = function () {
             alert(this.name);
         }
}

这里对原型所做的修改,能够立即在所有实例中得到反映;

6、寄生构造函数模式

 其返回的对象与构造函数或者与构造函数的原型属性之间没有关系,因此不能用`instanceof`操作符来确定对象类型,一般在可以使用其他模式的情况下不要使用这种模式;
function Person (name, age, job) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function () {
        alert(this.name);
    };
    return o;
}
function SpecialArray () {
   //创建数组
    var values = new Array();
   // 添加值
    values.push.apply(values, arguments);
   // 添加方法
    values.toPipedString = function () {
        return this.join('|');
    }
   //返回数组
    return values;
}

7、稳妥构造函数模式

稳妥对象模式适合在一些安全的环境中(这些环境中会禁止使用this和new),或者在防止数据被其他应用程序改动时使用。稳妥构造函数遵循与计生构造函数类似的模式,担忧两点不同:一是新创建的对象的实例方法不引用this;二是不使用new操作符调用构造函数。

function Person (name, age, job) {
    var o = new Object();
    o.sayName = function () {
        alert(name);
    };
    return o;
}
var friend = Person('Jack', 23, 'Front-end');
friend.sayName();    // 'Jack'

变量friend保存的是一个稳妥对象,而除了调用sayName()方法外,没有别的方式可以访问其数据成员;即使有其他代码会给这个对象添加方法或数据成员,但也不可能有别的办法访问到传入构造函数中的原始数据。其同样不能使用instanceof操作符确定对象类型。

for...in遍历对象时的顺序问题

首先,for...in循环的确是有顺序的。

那么当它遍历一个对象时,顺序和优先级如下:

  • 优先按照升序规则遍历key为整数属性的属性
  • 其次遍历key为 Symbol 类型的属性
  • 再其次按照属性创建时间升序遍历

整个过程可以理解为:首先创建一个名为 keys 的空数组,然后先遍历对象中的数组索引的属性,结果以升序排列,并逐个放入 keys 中;再遍历字符串属性(但不是数组索引),以属性创建时间升序排列,并逐个放入 keys 中去;然后再遍历 Symbol 类型的属性名,同样以属性创建时间升序排列,放入 keys 中,最后返回 keys 数组

所以当我们对遍历结果顺序有要求的情况下,最好不要使用for...in来遍历对象,遍历数组也不推荐(因为会枚举所有可枚举的属性,不一定只有数组的每一项)

按照MDN官方的说法:

for ... in是为遍历对象属性而构建的,不建议与数组一起使用,数组可以用Array.prototype.forEach()和for ... of,那么for ... in的到底有什么用呢?它最常用的地方应该是用于调试,可以更方便的去检查对象属性(通过输出到控制台或其他方式)。尽管对于处理存储数据,数组更实用些,但是你在处理有key-value数据(比如属性用作“键”),需要检查其中的任何键是否为某值的情况时,还是推荐用for ... in。

参考链接
JavaScript for...in 循环出来的对象属性顺序到底是什么规律?
如何按原顺序打印出对象的属性?

Git SSH key

https和SSH区别

  • 前者可以随意克隆github上的项目,而不管是谁的;而后者则是你必须是你要克隆的项目的拥有者或管理员,且需要先添加 SSH key ,否则无法克隆。
  • https url 在push的时候是需要验证用户名和密码的;而 SSH 在push的时候,是不需要输入用户名的,如果配置SSH key的时候设置了密码,则需要输入密码的,否则直接是不需要输入密码的。

关于SSH

使用SSH协议,我们可以连接并验证远程服务器和服务。使用SSH密钥,我们可以连接到GitHub,而无需在每次访问时提供用户名或密码。

检查现有的SSH key

在生成SSH密钥前,应先检查是否已有SSH密钥对

  • 1、打开Git Bash
  • 2、输入ls -al ~/.ssh以查看是否存在现有密钥
  • 3、检查目录列表是否已有类似id_rsaid_rsa.pub的两个文件,前者是密钥文件,后者是公钥文件

默认情况下,公钥的文件名是以下之一:

id_dsa.pub

id_ecdsa.pub

id_ed25519.pub

id_rsa.pub

如果未发现有类似的公钥和私钥对,则可以按照如下方法生成一个SSH key:

  • 1、打开Git Bash
  • 2、输入下面的命令,-C后面为你自定义的注释信息
ssh-keygen -t rsa -b 4096 -C " [email protected]"

各参数含义:

-t:密钥类型,一般为dsa,ecdsa,ed25519和rsa这几种,默认为rsa,可省略;

-b:密钥的位数;

-C:注释文字,比如邮箱。

  • 3、当出现以下提示指定保存位置时可选择直接Enter回车,即选择默认保存位置
Enter a file in which to save the key (/c/Users/you/.ssh/id_rsa):[Press enter]
  • 4、接着会提醒设置密码,可以直接回车回车,当然也可以选择设置密码
Enter passphrase (empty for no passphrase): [Type a passphrase]
Enter same passphrase again: [Type passphrase again]

到这里就生成了一个新的SSH key,可以使用ls命令查看生成后的文件目录:

  • id_rsa和id_rsa.pub分别为私钥和公钥

新建一个config文件

touch ~/.ssh/config

然后在config文件中添加以下内容:

Host *.github.com
    IdentityFile ~/.ssh/id_rsa
    User '你的用户名'

接着将id_rsa.pub内的内容复制并添加到你的GitHub账户中(Settings->SSH and GPG keys->New SSH key)

OK,进行一下链接测试

回车后出现:

Hi xxx! You've successfully authenticated, but GitHub does not # provide shell access.

就表示设置成功了,Bingo!

修改remote url

在你的项目目录下先使用git remote -v查看远程地址

$ git remote -v
origin https://github.com/someaccount/someproject.git (fetch)
origin https://github.com/someaccount/someproject.git (push)

可以看出当前项目是使用https协议进行访问的,然后我们可以使用命令改成SSH协议进行访问:

git remote set-url origin [email protected]:AccountName/Project-name.git

之后再查看远程地址:

$ git remote -v
origin [email protected]:AccountName/Project-name.git (fetch)
origin [email protected]:AccountName/Project-name.git (push)

发现remote url已经变了。

然后你就可以愉快的使用git fetch, git pull , git push,再也不用输入烦人的密码了(如果设置了密钥密码passphrase,则会需要输入该密码)

tips:

有时候push不上去,提示 the project you were looking for could not be found. 可能是远程仓库地址换了

如果想修改SSH key密码,如下操作:

$ cd ~/.ssh/ 

然后修改rsa类型密钥的密码:

$ ssh-keygen -f id_rsa -p  

然后按照提示输入你的old passphrasenew passphrase就OK了。

GitHub Help了解更多

HTTP与HTTPS的区别,以及为什么HTTPS比HTTP安全

之前每次看到类似“http与https的区别?”的问题时,都会自己思考一下答案,好像只是浅显地知道https比http安全,但究竟为什么更安全,却又似乎说不出个所以然,或者说很多细节地方自己都是不清楚的。为了搞清楚,也为了系统地了解一下http相关的知识,前段时间阅读了一波《图解HTTP》,不得不说这本书真的算是通俗易懂,了解到了很多之前不清楚的知识点(协议、报文、状态码、首部字段、身份认证、资源缓存以及web攻击等)。如果想了解更多http相关的知识的同学当然也可以选择阅读《HTTP权威指南》。

HTTP

HTTP,全称超文本传输协议,是一种详细规定客户端与web服务器之间互相通信的规则,通过因特网传送万维网文档的数据传送协议。它的特点是:

  • 无状态,每个请求结束后都会被关闭,每次的请求都是独立的,它的执行情况和结果与前面的请求和之后的请求是无直接关系的,它不会受前面的请求应答情况直接影响,也不会直接影响后面的请求应答情况;服务器中没有保存客户端的状态,客户端必须每次带上自己的状态去请求服务器,就像是“人生只如初见”,比如说用户需要请求某个数据,需要登录权限,用户登录之后进行请求,结果因为http的无状态,等用户下一次还想请求一份数据,还需要再次登录,这样不就很烦了吗,所以就需要session和cookie来进行状态管理了。

  • 明文传输(未经过加密的报文),为什么通信时不加密是一个缺点,这是因为,按TCP/IP 协议族的工作机制,通信内容在所有的通信线路上都有可能遭到窥视。无论世界哪个角落的服务器在和客户端通信时,在此通信线路上的某些网络设备、光缆、计算机等都不可能是个人的私有物,所以不排除某个环节中会遭到恶意窥视行为。即使已经过加密处理的通信,也会被窥视到通信内容,这点和未加密的通信是相同的。只是说如果通信经过加密,就有可能让人无法破解报文信息的含义,但加密处理后的报文信息本身还是会
    被看到的。

  • 不验证通信方的身份,因此有可能遭遇伪装。HTTP 协议中的请求和响应不会对通信方进行确认。也就是说存在“服务器是否就是发送请求中 URI 真正指定的主机,返回的响应是否真的返回到实际提出请求的客户端”等类似问题。在 HTTP 协议通信时,由于不存在确认通信方的处理步骤,任何人都可以发起请求。另外,服务器只要接收到请求,不管对方是谁都会返回一个响应(但也仅限于发送端的 IP 地址和端口号没
    有被 Web 服务器设定限制访问的前提下;不论是谁发送过来的请求都会返回响应,因此不确认通信方,会存在以下各种隐患:1、无法确定请求发送至目标的 Web 服务器是否是按真实意图返回响应的那台服务器。有可能是已伪装的 Web 服务器;2、无法确定响应返回到的客户端是否是按真实意图接收响应的那个客户端。有可能是已伪装的客户端;3、无法确定正在通信的对方是否具备访问权限。因为某些Web 服务器上保存着重要的信息,只想发给特定用户通信的权限;4、无法判定请求是来自何方、出自谁手;5、即使是无意义的请求也会照单全收。无法阻止海量请求下的 DoS 攻击(Denial of Service,拒绝服务攻击)。

  • 无法证明报文的完整性。因此,在请求或响应送出之后直到对方接收之前的这段时间内,即使请求或响应的内容遭到篡改,也没有办法获悉;换句话说,没有任何办法确认,发出的请求 / 响应和接收到的请
    求 / 响应是前后相同的。

HTTPS

HTTPS,全称Hyper Text Transfer Protocol Secure,相比http,多了一个secure,也就是TLS(SSL),一个安全套接层。https和http都属于应用层(application layer),基于TCP(以及UDP)协议,但是又完全不一样。TCP用的port是80, https用的是443。

HTTPS 并非是应用层的一种新协议。只是 HTTP 通信接口部分用SSL(Secure Socket Layer)和 TLS(Transport Layer Security)协议代替而已。通常,HTTP 直接和 TCP 通信。当使用 SSL时,则演变成先和 SSL通信,再由 SSL和 TCP 通信了。简言之,所谓 HTTPS,其实就是身披SSL协议这层外壳的 HTTP。

image

HTTPS解决的问题:

  • 信任主机的问题.。
    采用https 的server 必须从CA (数字证书认证机构处于客户端与服务器双方都可信赖的第三方机构的
    立场上)申请一个用于证明服务器用途类型的证书,该证书有了CA的签名,客户端才能知道访问的服务器是安全的。 目前基本所有的在线购物和网银等网站或系统,关键部分应用都是https 的,客户通过信任该证书,从而信任了该主机,这样才能保证安全。

  • 通讯过程中的数据的泄密和被窜改
    使用https协议,服务端和客户端之间的所有通讯都是加密的。客户端和服务端各有自己的一对非对称的密钥,一把叫做私有密钥(private key),另一把叫做公开密钥(public key),顾名思义,私有密钥不能让其他任何人知道,而公开密钥则可以随意发布,任何人都可以获得。使用公开密钥加密方式,发送密文的一方使用对方的公开密钥进行加密处理,对方收到被加密的信息后,再使用自己的私有密钥进行解密。利用这种方式,不需要发送用来解密的私有密钥,也不必担心密钥被攻击者窃听而盗走。要想根据密文和公开密钥,恢复到信息原文是异常困难的,因为解密过程就是在对离散对数进行求值,这并非轻而易举就能办到。退一步讲,如果能对一个非常大的整数做到快速地因式分解,那么密码破解还是存在希望的。但就目前的技术来看是不太现实的。
    image

简单点说就是:HTTP + 认证 + 加密 + 完整性保护 = HTTPS

HTTPS 的通信步骤

image

  • 步骤 1: 客户端通过发送 Client Hello 报文开始 SSL通信。报文中包含客户端支持的 SSL的指定版本、加密组件(Cipher Suite)列表(所使用的加密算法及密钥长度等)。
  • 步骤 2: 服务器可进行 SSL通信时,会以 Server Hello 报文作为应答。和客户端一样,在报文中包含 SSL版本以及加密组件。服务器的加密组件内容是从接收到的客户端加密组件内筛选出来的。
  • 步骤 3: 之后服务器发送 Certificate 报文。报文中包含公开密钥证书。
  • 步骤 4: 最后服务器发送 Server Hello Done 报文通知客户端,最初阶段的 SSL握手协商部分结束。
  • 步骤 5: SSL第一次握手结束之后,客户端以 Client Key Exchange 报文作为回应。报文中包含通信加密中使用的一种被称为 Pre-mastersecret 的随机密码串。该报文已用步骤 3 中的公开密钥进行加密。
  • 步骤 6: 接着客户端继续发送 Change Cipher Spec 报文。该报文会提示服务器,在此报文之后的通信会采用 Pre-master secret 密钥加密。
  • 步骤 7: 客户端发送 Finished 报文。该报文包含连接至今全部报文的整体校验值。这次握手协商是否能够成功,要以服务器是否能够正确解密该报文作为判定标准。
  • 步骤 8: 服务器同样发送 Change Cipher Spec 报文。
  • 步骤 9: 服务器同样发送 Finished 报文。
  • 步骤 10: 服务器和客户端的 Finished 报文交换完毕之后,SSL连接就算建立完成。当然,通信会受到 SSL的保护。从此处开始进行应用层协议的通信,即发送 HTTP 请求。
  • 步骤 11: 应用层协议通信,即发送 HTTP 响应。
  • 步骤 12: 最后由客户端断开连接。断开连接时,发送 close_notify 报文。上图做了一些省略,这步之后再发送 TCP FIN 报文来关闭与 TCP的通信。

下面是对整个流程的图解。图中说明了从仅使用服务器端的公开密钥证书(服务器证书)建立 HTTPS 通信的整个过程。

image

HTTPS的加密技术

  • 共享密钥加密的困境

    加密和解密同用一个密钥的方式称为共享密钥加密(Common key
    crypto system),也被叫做对称密钥加密。以共享密钥方式加密时必须将密钥也发给对方。可究竟怎样才能安全地转交?在互联网上转发密钥时,如果通信被监听那么密钥就可会落入攻击者之手,同时也就失去了加密的意义。另外还得设法安全地保管接收到的密钥。也就是说,发送密钥就存在被窃听的风险,不发送,对方就不能解密。再说如果密钥能够安全送达,那么数据也能够安全送达,那加密也就失去其意义了。

  • 使用两把密钥的公开密钥加密

    公开密钥加密方式很好地解决了共享密钥加密的困难。公开密钥加密使用一对非对称的密钥。一把叫做私有密钥(private key),另一把叫做公开密钥(public key)。顾名思义,私有密钥不能让其他任何人知道,而公开密钥则可以随意发布,任何人都可以获得。使用公开密钥加密方式,发送密文的一方使用对方的公开密钥进 行加密处理,对方收到被加密的信息后,再使用自己的私有密钥 进行解密。利用这种方式,不需要发送用来解密的私有密钥,也不必担心密钥被攻击者窃听而盗走。另外,要想根据密文和公开密钥,恢复到信息原文是异常困难的,因为解密过程就是在对离散对数进行求值,这并非轻而易举 就能办到。退一步讲,如果能对一个非常大的整数做到快速地因 式分解,那么密码破解还是存在希望的。但就目前的技术来看是 不太现实的。

  • HTTPS 采用混合加密机制

    因此,HTTPS采用的是共享密钥加密和公开密钥加密两者并用的混合加密机制。若密钥能够实现安全交换,那么有可能会考虑仅使用公开密钥加密来通信。但是公开密钥加密与共享密钥加密相比,其处理速度要慢。所以应充分利用两者各自的优势,将多种方法组合起来用于通信。在交换密钥环节使用公开密钥加密方式,之后的建立通信交换报文阶段则使用共享密钥加密方式。上图中生成的master secret即共享密钥,之后的交换的报文信息都将使用它来进行加密。

HTTPS加密

HTTPS的问题

  • HTTPS足够安全吗?世界上没有绝对的安全。比如2014年的Heartbleed漏洞席卷全球,很多网站受到heartbleed威胁,其中就有雅虎,stackoverflow这样的网站。但总的来说对于绝大部分人来说HTTPS还是相对安全的,至少比HTTP安全。
  • HTTPS 还有一个问题,那就是当使用 SSL时,它的处理速度会变

image

SSL的慢分两种。一种是指通信慢。另一种是指由于大量消耗CPU 及内存等资源,导致处理速度变慢。

  • 和使用 HTTP 相比,网络负载可能会变慢 2 到 100 倍。除去和TCP 连接、发送 HTTP 请求 • 响应以外,还必须进行 SSL通信,因此整体上处理通信量不可避免会增加。
  • 另一点是 SSL必须进行加密处理。在服务器和客户端都需要进行加密和解密的运算处理。因此从结果上讲,比起 HTTP 会更多地消耗服务器和客户端的硬件资源,导致负载增强。
    -针对速度变慢这一问题,并没有根本性的解决方案,我们会使用SSL加速器这种(专用服务器)硬件来改善该问题。该硬件为SSL通信专用硬件,相对软件来讲,能够提高数倍 SSL的计算速度。仅在 SSL处理时发挥 SSL加速器的功效,以分担负载。

为什么不一直使用 HTTPS?

  • 因为与纯文本通信相比,加密通信会消耗更多的CPU 及内存资源。如果每次通信都加密,会消耗相当多的资源,平摊到一台计算机上时,能够处理的请求数量必定也会随之减少。因此,如果是非敏感信息则使用 HTTP 通信,只有在包含个人信息等敏感数据时,才利用 HTTPS 加密通信。特别是每当那些访问量较多的 Web 网站在进行加密处理时,它们所承担着的负载不容小觑。在进行加密处理时,并非对所有内容都进行加密处理,而是仅在那些需要信息隐藏时才会加密,以节约资源。

  • 想要节约购买证书的开销也是原因之一。 要进行 HTTPS 通信,证书是必不可少的。而使用的证书必须向认证机构(CA)购买。证书价格可能会根据不同的认证机构略有不同。那些购买证书并不合算的服务以及一些个人网站,可能只会选择采用 HTTP 的通信方式。

参考书籍

《图解HTTP》

《HTTP权威指南》

mongoose笔记

MongoDB用户权限

  1. 数据库用户角色:read、readWrite;

  2. 数据库管理角色:dbAdmin、dbOwner、userAdmin;

  3. 集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManager;

  4. 备份恢复角色:backup、restore;

  5. 所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase

  6. 超级用户角色:root
    其中有几个角色间接或直接提供了系统超级用户的访问(dbOwner 、userAdmin、userAdminAnyDatabase)

  7. 内部角色:__system

  • Read:允许用户读取指定数据库

  • readWrite:允许用户读写指定数据库

  • dbAdmin:允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile

  • userAdmin:允许用户向system.users集合写入,可以在指定数据库里创建、删除和管理用户

  • clusterAdmin:只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限。

  • readAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读权限

  • readWriteAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读写权限

  • userAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限

  • dbAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。

  • root:只在admin数据库中可用。超级账号,超级权限

常用命令

  • 重命名表明
db.OLDNAME.renameCollection( "NEWNAME" )
  • 删除表
db.COL.drop()

Mongoose API
Mongoose参考手册

实现一个深克隆(考虑环)

const deepClone = (parent) => {
   const parents = [], children = []
   const _clone = (parent) => {
     if (parent === null) return null
     if (typeof parent !== 'object' && typeof parent !== 'function') return parent
     let child
     child = Object.prototype.toString.call(parent) === '[object Array]' ? [] : {}
     const index = parents.indexOf(parent)
     if (index !== -1) {
	return children[index]
     }
     parents.push(parent)
     children.push(child)
     Object.keys(parent).forEach((key) => {
	child[key] = typeof parent[key] === 'object' ? _clone(parent[key]) : parent[key]
     })
     return child
     }
  return _clone(parent)
}

test

const obj = {
    a: 1,
    b: {
        c: [
	      {
		   d: 2
	      }
	   ]
	}
}
obj.b.c[0].d = obj.b
const newObj = deepClone(obj)
newObj.b.f = 3
console.log(obj, newObj)

typeof的原理(typeof null为何等于object)

不同的对象在底层都表示为二进制, 在 JavaScript 中二进制前三位都为 0 的话会被判断为 object 类型, null 的二进制表示是全 0, 自然前三位也是 0, 所以执行 typeof null时会返回“object”。

javascript 中的 null:既是对象,又不是对象

typeof null === 'object';  // true
null instanceof Object  // false

null instanceof null   // Uncaught TypeError: Right-hand side of 'instanceof' is not an object

底层二进制使用低位表示变量的类型信息

  • 000:对象
  • 1:整数
  • 010:浮点数
  • 100:字符串
  • 110:布尔

有 2 个值比较特殊:

  • undefined:用 - (−2^30)表示。
  • null:对应机器码的 NULL 指针,一般是全零。

在第一版的 javascript 实现中,判断类型的代码是这么写的:

if (JSVAL_IS_VOID(v)) {  // (1)
    type = JSTYPE_VOID;
} else if (JSVAL_IS_OBJECT(v)) {  // (2)
    obj = JSVAL_TO_OBJECT(v);
    if (obj &&
        (ops = obj->map->ops,
            ops == &js_ObjectOps
            ? (clasp = OBJ_GET_CLASS(cx, obj),
            clasp->call || clasp == &js_FunctionClass) // (3,4)
            : ops->call != 0)) {  // (3)
        type = JSTYPE_FUNCTION;
    } else {
        type = JSTYPE_OBJECT;
    }
} else if (JSVAL_IS_NUMBER(v)) {
    type = JSTYPE_NUMBER;
} else if (JSVAL_IS_STRING(v)) {
    type = JSTYPE_STRING;
} else if (JSVAL_IS_BOOLEAN(v)) {
    type = JSTYPE_BOOLEAN;
}

(1):判断是否为 undefined
(2):如果不是 undefined,判断是否为对象
(3):如果不是对象,判断是否为数字
(4):。。。

这样一来,null 就出了一个 bug。根据 type tags 信息,低位是 000,因此 null 被判断成了一个对象。这就是为什么 typeof null 的返回值是 object。

这是 JavaScript 最初实现的一个 bug,目前的 JavaScript 引擎已经不这么去实现了,但是这个 bug 却一直流传了下来。至于对象的内部表示,不同的 JavaScript 引擎实现起来都是不一样的。

V8引擎是如何知道js数据类型的?

先执行方法A( ), 间隔0.5秒后, 再执行方法B( ), 再间隔0.5秒后,执行方法A( ),如此循环执行

// 参数1:需要循环执行的函数数组; 参数2:执行的时间间隔
const doLoop = (fns, interval) => {
   let step = 0
   const excu = (fn) => {
       if (typeof fn !== 'function') {
	  throw new Error(`'${fn}' is not a function`)
       }
       setTimeout(() => {
	  fn()
	  ++step
          if (step >= fns.length) {
            // 使用setTimeout避免栈溢出
	     setTimeout(() => {
		doLoop(fns, interval)
	     }, interval)
	  } else {
	     excu(fns[step])
          }
      }, step === 0 ? 0 : interval)
   }
   excu(fns[0])
}

//  test
const A = () => {
    console.log('A')
}
const B = () => {
    console.log('B')
}
const C = () => {
    console.log('C')
}
const D = () => {
    console.log('D')
}

doLoop([A, B, C, D], 500)

正则中的 "?=" 与 "?!" 的作用

前瞻:
exp1(?=exp2) 查找exp2前面的exp1
后顾:
(?<=exp2)exp1 查找exp2后面的exp1
负前瞻:
exp1(?!exp2) 查找后面不是exp2的exp1
负后顾:
(?<!=exp2)exp1 查找前面不是exp2的exp1

解决移动端弹出键盘将页面往上顶,导致页面整体往上,显示不全

问题描述

最近在做移动端项目(框架使用Vue)时,发现一个问题:在移动端真机上点击一个文本输入框,弹出键盘,为了使键盘不挡住输入框,页面被往上推了,输入框失去焦点后,键盘收起,咦?页面怎么卡在那了,上面的都看不见了,试着滑动也没办法让它回归原位。

解决方案

最终解决方案是:给input监听blur事件,在失去焦点时重置body的scrollTop就好了,因为对input做了一次组件封装,只需要在blur回调函数中加上如下一段代码就ok了,这也体现出组件复用的好处,我使用input的时候就用封装过的input组件就行了,不用担心在每个用到input的时候都要手动加上这些代码:

onBlur () {
    // 其它代码...
    document.body.scrollTop = 0
}

扩充部分

移动端meta标签之viewport

如果<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">中没有user-scalable=no可能还会导致键盘弹出时页面比例变大,使页面看起来向左上方偏移了,所以不要忘了user-scalable=no

::before 和 :after 中双冒号和单冒号有什么区别?

类似:link, :hover等,单冒号是用来表示伪类的,伪元素在CSS1中已存在,当时语法是用 : 表示,如 :before 和 :after,但是CSS3做了修订,伪元素用 :: 表示,如 ::before 和 ::after,以此区分伪元素和伪类;由于低版本IE对双冒号不兼容,开发者为了兼容性各浏览器,继续使使用 :after 这种老语法表示伪元素;综上所述:::before 是 CSS3 中写伪元素的新语法; :after 是 CSS1 中存在的、兼容IE的老语法。

Linux常用命令

重启命令
1、reboot
2、shutdown -r now 立刻重启(root用户使用)
3、shutdown -r 10 过10分钟自动重启(root用户使用)
4、shutdown -r 20:35 在时间为20:35时候重启(root用户使用)
如果是通过shutdown命令设置重启的话,可以用shutdown -c命令取消重启

关机命令
1、halt 立刻关机
2、poweroff 立刻关机
3、shutdown -h now 立刻关机(root用户使用)
4、shutdown -h 10 10分钟后自动关机
如果是通过shutdown命令设置关机的话,可以用shutdown -c命令取消重启

Vue vs React

构建工具

React和Vue都有自己的构建工具,你可以使用它快速搭建开发环境。React可以使用Create React App (CRA),而Vue对应的则是vue-cli。两个工具都能让你得到一个根据最佳实践设置的项目模板。

Chrome 开发工具

React和Vue都有很好的Chrome扩展工具去帮助你找出bug。它们会检查你的应用,让你看到Vue或者React中的变化。你也可以看到应用中的状态,并实时看到更新。

配套框架

Vue与React最后一个相似但略有不同之处是它们配套框架的处理方法。相同之处在于,两个框架都专注于UI层,其他的功能如路由、状态管理等都交由同伴框架进行处理。
而不同之处是在于它们如何关联它们各自的配套框架。Vue的核心团队维护着vue-router和vuex,它们都是作为官方推荐的存在。而React的react-router和react-redux则是由社区成员维护,它们都不是官方维护的。

Virtual DOM

Vue和React都是采用Virtual DOM,所谓的Virtual DOM其实是DOM树的虚拟体现,是一个JavaScript对象,它的诞生是基于这么一个概念:改变真实的DOM状态远比改变一个JavaScript对象的花销要大得多。

Virtual DOM是一个映射真实DOM的JavaScript对象,如果需要改变任何元素的状态,那么是先在Virtual DOM上进行改变,而不是直接改变真实的DOM。当有变化产生时,一个新的Virtual DOM对象会被创建并计算新旧Virtual DOM之间的差别。之后这些差别会应用在真实的DOM上。

例子如下,我们可以看看下面这个列表在HTML中的代码是如何写的:

<ul class="list">
  <li class="item">item 1</li>
  <li class="item">item 2</li>
</ul>

而在JavaScript中,我们可以用对象简单地创造一个针对上面例子的映射:

{
    type: 'ul', 
    props: {'class': 'list'}, 
    children: [
        { type: 'li', props: {'class': 'item'}, children: ['item 1'] },
        { type: 'li', props: {'class': 'item'}, children: ['item 2'] }
    ]
}

真实的Virtual DOM会比上面的例子更复杂,但它本质上是一个嵌套着数组的原生JavaScript对象。
当新一项被加进去这个JavaScript对象或者改变某个属性时,一个函数会计算新旧Virtual DOM之间的差异并反应在真实的DOM上。计算差异的算法是高性能框架的秘密所在,React和Vue在此实现上有点不同。

Vue宣称可以更快地计算出Virtual DOM的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。而对于React而言,每当应用的状态被改变时,全部子组件都会重新渲染。当然,这可以通过shouldComponentUpdate这个生命周期方法来进行控制,但Vue将此视为默认的优化。

小结:如果你的应用中,交互复杂,需要处理大量的UI变化,那么使用Virtual DOM将是个好主意。如果你更新元素并不频繁,那么Virtual DOM也并不一定适用,性能很可能还不如直接操控DOM。

组件化(模板 vs JSX)

Vue和react都是采用组件化开发的**,Vue默认采用近似常规HTML的模板语法(其实也可以选择render和JSX),而React使用JavaScript的扩展语法JSX语法(即HTML in JavaScript)

状态管理 vs 对象属性

React中的state(状态)不能直接被改变,需通过setState()去更新状态,而Vue中state对象不是必须的,数据由data属性在Vue对象中进行管理。

Vue.js的作者尤雨溪曾说过,(Vue的)解决方案适用于小型应用,但对于大型应用而言不太适合。
大多数情况下,框架内置的状态管理是不足以支撑大型应用的,Redux或Vuex等状态管理方案是必须使用的。

encodeURI()和encodeURIComponent()

encodeURI() 函数通过将特定字符的每个实例替换为一个、两个、三或四转义序列来对统一资源标识符 (URI) 进行编码 (该字符的 UTF-8 编码仅为四转义序列)由两个 "代理" 字符组成)。

encodeURI(URI)
  • 参数URI为一个完整的URI
  • 返回值为一个新字符串, 表示提供的字符串编码为统一资源标识符 (URI)。

encodeURI 会替换所有的字符,但不包括以下字符,即使它们具有适当的UTF-8转义序列:

  • 保留字符: ; , / ? : @ & = + $
  • 非转义的字符: 字母 数字 - _ . ! ~ * ' ( )
  • 数字符号:#
encodeURI('https://www.baidu.com/ a b c')
// "https://www.baidu.com/%20a%20b%20c"

encodeURI 自身无法产生能适用于HTTP GET 或 POST 请求的URI,例如对于 XMLHTTPRequests, 因为 "&", "+", 和 "=" 不会被编码,然而在 GET 和 POST 请求中它们是特殊字符。然而encodeURIComponent这个方法会对这些字符编码。

encodeURIComponent()是对统一资源标识符(URI)的组成部分进行编码的方法。它使用一到四个转义序列来表示字符串中的每个字符的UTF-8编码(只有由两个Unicode代理区字符组成的字符才用四个转义字符编码)。

encodeURIComponent 转义除了字母、数字、().!~*'-_之外的所有字符。

encodeURIComponent('https://www.baidu.com/ a b c')
// "https%3A%2F%2Fwww.baidu.com%2F%20a%20b%20c"

对称加密和非对称加密的理解

对称加密

加密和解密使用的是同一个密钥,也就是说加密密钥同时用作解密密钥,这种方法在密码学中叫做对称加密算法,对称加密算法使用起来简单快捷,密钥较短,且破译困难,除了数据加密标准(DES),另一个对称密钥加密系统是国际数据加密算法(IDEA),它比DES的加密性好,而且对计算机功能要求也没有那么高。
常见的对称加密算法有DES、3DES、Blowfish、IDEA、RC4、RC5、RC6和AES

非对称加密

非对称加密指的是:加密和解密使用不同的秘钥,一把作为公开的公钥,另一把作为私钥。公钥加密的信息,只有对应私钥才能解密。私钥加密的信息,只有对应公钥才能解密。

常见的非对称加密算法有:RSA、ECC(移动设备用)、Diffie-Hellman、El Gamal、DSA(数字签名用)

对称加密算法相比非对称加密算法来说,加解密的效率要高得多。但是缺陷在于对于秘钥的管理上,以及在非安全信道中通讯时,密钥交换的安全性不能保障。所以在实际的网络环境中,会将两者混合使用。

例如:

  1. 服务端计算出一对秘钥pub/pri。将私钥保密,将公钥公开。
  2. 客户端请求服务端时,拿到服务端的公钥pub。
  3. 客户端通过AES计算出一个对称加密的秘钥X。 然后使用pub将X进行加密。
  4. 客户端将加密后的密文发送给服务端。服务端通过pri解密获得X。
  5. 然后两边的通讯内容就通过对称密钥X以对称加密算法来加解密。

for、map及forEach的异同

处理效率对比

处理效率上经过我的测试,for > forEach > map

处理效率测试代码如下:

var arr = [],
  max = 1000 // 多组max取值(10000,100000,...)进行测试
for (var i = 1; arr.push(i++) < max;)

var mapArr = [],forEachArr = [],forArr = []

console.time('map')
arr.map(function (val) {
  mapArr.push(val)
})
console.timeEnd('map')

console.time('forEach')
arr.forEach(function (val) {
  forEachArr.push(val);
})
console.timeEnd('forEach')

console.time('for')
for (var i = 0; i < arr.length; i++) {
  forArr.push(arr[i])
}
console.timeEnd('for')

map在功能定义上来说不适合与for以及forEach来进行比较,它会生成一个经过处理的新数组,因此内部实现肯定会相对于forEach来说更复杂一些.

forEach相对于for来说,实现同样的功能,除了方便书写能够节省代码量之外,还有没有其他的优势或者特殊用途?

首先通过两者的效率来看,在处理较长的数组时,可能不会使用forEach来遍历数组,但是forEach也有它另外一个优势,那就是在对稀疏数组的处理时,会跳过数组中的空位

for循环处理稀疏数组

var arr = new Array(100)

arr[0] = 1
arr[99] = 3

for (var i = 0, l = arr.length; i < l; i++) {
    console.log(`arr[${i}]`, arr[i])
}
console.log('i :' , i)

// 打印结果:
// arr[0] 1
// arr[1] undefined
// arr[2] undefined
// ...
// arr[99] 3
// i : 100

forEach处理稀疏数组

arr[0] = 1
arr[99] = 3

var count = 0
arr.forEach(function(value, index) {
    count++
    console.log(`arr[${index}]`, value)
})
console.log('count', count)

// 打印结果:
// arr[0] 1
// arr[99] 3
// count 2

正则表达式中的 "?:" 的作用

(?:x) 匹配 'x' 但是不记住匹配项,因此也不能反向引用该匹配值,这种叫作非捕获括号

const reg1 = /(\w+),(\d+),(\w+)/
const reg2 = /(?:\w+),(?:\d+),(\w+)/
const str = 'abc,123,de'

str.match(reg1)
// ["abc,123,de", "abc", "123", "de"]
str.replace(reg1, '$1哈哈$2哈哈$3') // $n (n是一个数字,表示第n个捕获组的内容)
// "abc哈哈123哈哈de"

str.match(reg2)
// ["abc,123,de",  "de"]
str.replace(reg1, '$1哈哈$2哈哈$3')
// "de哈哈$2哈哈$3"

github或者gitlab上fork仓库后同步源仓库的更新

首先给fork后的仓库配置上游仓库

  • 查看远程状态
git remote -v
  • 配置上游仓库
git remote add upstream https://github.com/ORIGIN_OWNER/ORIGIN_RESPOSITORY.git
  • 再次查看远程状态,确认是否配置成功

同步源仓库更新

  • 从上游仓库 fetch 分支内容
git fetch upstream ORIGIN_BRANCH
  • 切换至你对应的本地分支
  • 把上游仓库的对应分支内容合并到本地对应分支
git merge upstream/master
  • 将更新push至你的远程仓库

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.