在现有的基础上快速复制一个网站配置

在现有的基础上快速复制一个网站配置

私人文件,避免网上查看

根据郭高完成的已有配置进行设置。

第一步,复制配置文件,现有的配置文件是 /home/nginx/vhosts/ych.yihdot.com.conf
假设新创建的域名为 morning.yihdot.com,则运行复制命令 cp /home/nginx/vhosts/ych.yihdot.com.conf /home/nginx/vhosts/morning.yihdot.com.conf

第二步,修改配置文件项并保存

1
2
3
4
5
6
7
8
9
10
11
server{
listen 80;# 映射端口
server_name morning.yihdot.com; # 域名

access_log /home/nginx/logs/morning.yihdot.com_access.log; #blog日志文件
error_log /home/nginx/logs/morning.yihdot.com_error.log; #error日志文件
location / {
root /home/host/web; #域名对应的根节点
index index.html index.htm; #默认文件
}
}

第三步,站点搭建与启用

  1. 根据配置文件中设置的网站根节点添加目录
    1
    2
    3
    # cd /home/
    # mkdir host && cd host
    # mkdir web && cd web
  2. 在站点下新建HTML文件,作为站点构建是否成功的预览
    1
    # vim index.html
  3. 检测站点配置并启用站点
    1
    2
    3
    # cd /home/nginx/sbin
    # ./nginx -t
    # ./nginx reload
  4. 新增域名解析
    1
    morning.yihdot.com -> IP地址

第四步、删除

  1. 删除配置信息
    1
    2
    rm /home/nginx/vhosts/morning.yihdot.com.conf
    rm /home/host/web
  2. 重新加载nginx
    1
    2
    # cd /home/nginx/sbin
    # ./nginx reload
  3. 删除对应的域名解析

Git 分支应用 - Git 操作技巧

Git 分支应用 - Git 操作技巧

Git 处理分支的方式可谓是难以置信的轻量,创建新分支这一操作几乎能在瞬间完成,并且在不同分支之间的切换操作也是一样便捷。 与许多其它版本控制系统不同,Git 鼓励在工作流程中频繁地使用分支与合并,哪怕一天之内进行许多次。

创建分支

  创建一个分支就相当于创建可一个可移动的新指针,创建分支可以使用 git branch命令。

1
$ git branch <branchName>

  branchName为需要合并的分支名。可以使用命令创建一个名为 test的分支,当有多个分支时,多个分支之间是可以进行切换的。

切换分支

  切换分支就要用到 git checkout 命令。

1
$ git checkout <branchName>

  将分支从当前分支切换到了test分支当中。git 会根据特殊指针HEAD来判断当前所处的分支。之前使用git checkout命令撤销文件的修改,在这里用作为分支的切换。

  上面的两条命令是创建并切换到新创建的分支当中,两个命令可以进行合并成一个命令完成。

1
$ git checkout -b test

  创建并切换到新创建的分支test当中。

查看分支

  创建了多个分支之后,有时需要查看当前git仓库一共有多少个分支或者有哪些分支时,需要用到分支的查看命令。

1
2
3
$ git branch // 查看本地分支
$ git branch -r // 查看远程分支
$ git branch -a // 查看本地和远程的所有分支

合并分支

  开发分支的内容改变之后,切换到主分支当中,主分支是没有任何变化的,需要将开发分支合并到主分支当中,就应用到分支的合并。在主分支当中运行合并分支的命令。

1
$ git merge <branchName>

  branchName就是需要合并到当前分支的分支名称。

保存进度

  正在开发分支上进行项目开发,功能还没有开发完成,突然接到一个紧急BUG需要修复,这个时候需要借助于bug分支进行修复,但是没有提交的代码在切换分支时会报错。为完成开发的代码也不好进行提交操作,这个时候就需要保存当前的开发进度,切换到bug分支上先进行BUG的修复。

1
2
$ git stash
$ git stash save '进度备注信息'

  运行命令后在使用git status命令查看,发现该分支下没有需要暂存和提交的代码,这个时候就可以放心的切换分支了。
  查看有哪些保存的进度同样使用git stash命令

1
2
3
$ git stash show
$ git stash list
$ git stash shwo <stash>

show参数查看最后一次保存进度中的文件有哪些修改
list 查看保存的进度列表,多个保存可以看到是以stash@{0}:为下标进行保存的。顺序由近及远。
<stash> 表示的是保存的进度序列,可以查看该序列下的具体差异。

恢复进度

  修改完成BUG之后,切换到开发分支当中,这时候需要保存的进度进行恢复。有两种方法可以恢复进度。

1
2
$ git stash pop
$ git stash apply

  两种方法是有区别的,pop恢复最后一次保存的进度,并将其从进度列表中删除;apply恢复最后一次保存的进度,进度列表中的仍然保存。

删除进度

  删除进度同样有两种方法。

1
2
$ git stash drop <stash>
$ git stash clear

  两种方法的区别是第一种,删除指定的进度,第二种是清空所有的进度列表。

创建进度分支

  可以从保存的进度当中分离出来一个新的分支作为开发。

1
$ git stash branch <branchName> [<stash>]
如果不输入`<stash>`则默认是最后一次保存的进度。

删除分支

  像bug分支使用过后并过后并不需要一直存在,就需要及时的删除分支,删除分支仍需要使用git branch命令。

1
2
$ git branch -d <branchName>
$ git branch -D <branchName>

Git 撤销操作 - Git 操作技巧

Git 撤销操作 - Git 操作技巧

在进行git的任何一个阶段都可以撤销某些操作,但需要注意的是,有些撤销的操作却是不可逆的。这是在使用 Git 的过程中,会因为操作失误而导致之前的工作丢失的少有的几个地方之一。

取消暂存文件

将工作区内的文件进行修改,然后运行git add命令将修改过后的文件提交到暂存区,但此时发现,提交的多个文件中只有部分是需要提交的,这个时候就可以运行撤销操作;

1
2
$ git reset HEAD <file>
$ git reset HEAD .

在这个命令中的HEAD在使用git log命令的时候可以看到,最后一条的提交记录中就显示HEAD,参数是<file>则表示撤销某个文件的暂存操作,参数是.表示撤销当前暂存的所有文件。

取消文件修改

不想保留对文件的修改,该如何撤销到上次提交后的状态,在这里就需要借助于一个新的操作命令;

1
2
$ git checkout -- <file>
$ git checkout .

在这里需要注意的是,该撤销文件针对的是已经跟踪提交过的文件或者是从远程仓库中克隆下来的文件,对于新创建的文件并没有实际效果。
运行 git checkout -- <file> 是对某一个文件的修改。git checkout .是撤销当前工作区内的所有文件修改。

补充提交

  有一种特殊的应用场景,当我们完成提交操作后,发现漏提交了一个或者一部分文件,那么如何添加上遗漏的文件重新提交呢。

1
2
$ git add <file>
$ git commit --amend

  使用git add命令是将遗漏的文件添加到暂存区,git commit --amend将会替换就近的最后一次提交结果,最终只呈现一次提交。

命令提示

  当执行完一步操作之后,git 是会给出相应的可能需要执行的命令操作,当运行git status命令之后,会在控制面板的结果输出命令信息。(提交完成后是没有相应的命令提示的)
  当文件处于修改状态时运行git status,将会输出如下信息:

1
2
3
4
5
6
7
$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)

  可以明显的看出,在输出内容信息中有两个命令提醒git add <file>git checkout -- <file>就是告诉使用者可以将文件添加到暂存区或者撤销文件修改;

版本回退

  对文件进行多次修改提交之后,发现修改的文件出现了问题,需要回退到上一个版本或者是之前的某一个版本,这个时候就要使用到版本回退。

1
2
3
$ git reset --hard HEAD^
$ git reset --hard ~(n)
$ git reset --hard (commitID)

  HEAD是指当前的版本,git reset --hard HEAD^ 是指返回上一个版本,一个^表示一个版本,向上返回n个版本,可以使用n个^表示。
  多个^会比较的麻烦,可以直接使用数字来替换 git reset --hard ~100 就表示向上返回100个版本。
  当提交的版本过多时,很难细查到底需要返回多少个版本,如果要返回到指定的版本就需要借助于版本的ID,查询版本ID可以通过两个命令git loggit reflog

Git 的状态管理及基础命令 - Git应用技巧

Git 的状态管理及基础命令 - Git应用技巧

Git 是世界上较为先进的分布式版本管理系统。在各种程序代码开发中应用广泛,其方便高效快捷带来不一样的开发体验。
使用Git进行版本管理首先需要有一个版本管理仓库。其实说白了就是在电脑上创建一个文件夹,使用Git将该文件夹下的所有内容管理起来。被Git管理的每个文件的增加、修改、删除都能够被准确追踪到,以便在任何时刻刻根据文件追踪历史将文件还原。

创建本地仓库

创建本地仓库有两种方法:

1
2
$ git init
$ git clone <url>

第一种方法是在本地新建一个文件夹(避免使用中文),进入到文件夹中,运行git init命令。

1
2
3
$ mkdir textgit
$ cd textgit
$ git init

使用 git init 命令会在当前文件夹下创建一个掩藏的目录 .git,该目录既是进行版本控制的重要文件,删除该目录也就解除了git的版本控制。不要随意修改该目录下的文件内容,否则将会破坏该仓库下的版本控制管理。

第二种方法是克隆远程仓库。在合适的文件夹下运行git命令,会将远程仓库完整的复制到本地,本地仓库名(文件夹的名字)就是远程仓库的名字。

1
$ git clone <url>

在复制的远程仓库里,也可以找到隐藏文件夹.git

完成本地仓库的创建之后先来了解一下远程仓库的几种常见状态。

GIt状态

Git文件状态表:

代码 名称 说明
Untracked 未跟踪 新建文件
Unmodified 未修改 已提交文件
Modified 已修改 提交后的文件修改
Staged 已暂存 新建文件跟踪或修改文件暂存

Git 基本的工作流程可以分为以下几方面:

  1. 在工作目录中修改文件;
  2. 暂存文件,将文件的快照放入暂存区域;
  3. 提交更新,找到暂存区域的文件,将快照永久性存储到 Git 仓库目录。

查看状态

  要查看哪些文件处于什么状态,可以用 git status 命令。查看简短命令使用git status -s;

1
2
$ git status
$ git status -s

跟踪新文件

  使用命令git add来进行文件的跟踪。
  git add命令后面可以跟具体的文件或者是文件目录,如果是一个文件则仅跟踪当前文件,如果是文件目录则跟踪该文件目录下的所有文件。
示例

1
2
3
$ git add test.txt
$ git add .
$ git add -A

  . 表示当前目录文件,包括修改和新增文件,但不包括被删除的文件。
  -A 跟踪仓库里的所有文件,包括修改、新增和删除。

文件提交

  提交到暂存区的文件只是临时保存,需要完成提交操作才算正式的提交到git版本仓库中,提交文件就要用到git commit 命令。

1
2
$ git commit -m '提交信息描述'
$ git commit -a -m '提交描述信息'

  参数-a将已经跟踪的文件跳过暂存操作,直接完成提交。

文件差异

  Git支持文件修改之后的差异对比,对比两个文件的差异就要用到git diff命令,该命令也可以附带参数。

1
2
$ git diff
$ git diff --cached

  两个命令的对比都是针对已经跟踪的文件,未跟踪文件不参与对比,但在使用上是有差异的:

  • git diff:如果暂存区有该文件,则与暂存区文件进行比较,没有则与提交后的文件比较;
  • git diff --cached 暂存区的文件与提交的文件进行比较。

文件删除

  删除虽然并非是git的主要功能,但是也做了操作支持,删除git库里的文件,就要用到git rm 命令

1
2
$ git rm <file>
$ git rm -f <file>

  在命令后跟具体文件路径+文件名,就可以将文件删除,但有时因文件修改未暂存提交,就会造成删除失败,则可以通过加-f参数,将文件强制删除。

历史记录

  经过多次修改提交之后,就可能记不清楚都进行了哪些操作,这个是就需要查看git仓库的操作日志,也就是git仓库的历史操作记录,查看日志就要用到 git log 命令,该命令可搭配不同的参数实现多种效果。

1
2
3
$ git log 
$ git log -p -(n)
$ git log --oneline -(n)
  • -p 完整显示每个更新之间的差异
  • --oneline 显示每次提交的简要信息(commitID、注释信息)
  • -(n) 查看就近的 N 条提交记录。

获取帮助

  在使用git 命令的过程中,如果有遇到哪个命令不确定或者该命令的正确使用规范不清楚,可以查询命令帮助。

1
$ git help <verb>

示例

1
$ git help add

  运行命令后悔打开默认浏览器,显示该命令的详细应用说明。

文件忽略

  不需要使用git跟踪的文件可以设置忽略跟踪,文件的忽略就需要配置.gitignore文件。文件 .gitignore 的格式规范如下:

  • 所有空行或者以 # 开头的行都会被 Git 忽略。
  • 可以使用标准的 glob 模式匹配。
  • 匹配模式可以以(/)开头防止递归。
  • 匹配模式可以以(/)结尾指定目录。
  • 要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(!)取反。

Git 远程操作 - Git 操作技巧

Git 远程操作 - Git 操作技巧

远程仓库是指托管在因特网或其他网络中的项目版本库。 与他人协作涉及管理远程仓库以及根据需要推送或拉取数据。管理远程仓库包括了解如何添加远程仓库、移除无效的远程仓库、管理不同的远程分支并定义它们是否被跟踪等等。

查看远程仓库

当使用克隆命令把远程仓库克隆到本地时,默认情况下,远程仓库和本地仓库已经形成了关联。当不知道本地仓库是否已经关联远程仓库时,可通过命令进行查看。

1
2
$ git remote
$ git remote -v

两个命令分别输出远程仓库名和远程仓库名+仓库链接的信息。如果没有关联远程仓库则什么也不输出。

添加远程仓库

如果本地仓库为关联远程仓库或者需要关联多个远程仓库是,就需要给本地仓库添加远程仓库的关联。

1
$ git remote add <自定义仓库名称> <远程仓库地址>

远程仓库地址默认情况是origin,在手动添加时可以自定义远程仓库关联名称。需要注意命名格式,推荐仅使用小写英文字母。
一个本地仓库可以关联多个远程仓库。

从远程仓库拉取

当关联远程仓库后,从远程仓库拉取内容时就需要用到内容拉取命令。

1
2
$ git fetch [remote-name]
$ git pull [remote-name]

这两个命令都可以从远程仓库上拉取内容,需要注意与git clone的区别,git clone命令在使用时需要知道远程仓库地址,不需要关联远程仓库,内容拉取完成后会自动关联,而git fetchgit pull 则需要关联远程仓库进行操作。
git fetchgit pull命令在使用是也是有区别的,git fetch会获取远程仓库中的所有分支内容到本地仓库,而git pull将会拉取并合并远程仓库中的指定分支,不指定默认为当前所处分支。

推送到远程仓库

仓库关联后即可以从远程仓库中获取代码,也可以推送本地代码到远程仓库。

1
$ git push <连接仓库名> [远程分支]:[本地分支]

推送代码到远程仓库,必须先关联远程仓库,默认会推送当前所处分支的代码到远程仓库中,如果远程仓库中不包含该分支,则会自动创建。

解除远程仓库关联

本地仓库关联多个远程仓库,可以使用移除关联的方法,降不需要的远程仓库关联接解除掉。

1
2
$ git remote remove <remte-name>
$ git remote rm <remte-name>

两个命令的用法和效果是一个的,rm仅仅是remove的简写而已。

JavaScript 中的多态

JavaScript 中的多态

1. 什么是多态

  “多态”一词,从字面上看可以理解为多种形态。在语言中可以这样理解,同样一段代码或者一个函数运行得到不同的结果反馈。

  龟兔赛跑的故事都比较的了解,当一声令下“开始”的时候,乌龟和兔子是以不同的方式前进,乌龟是爬行而兔子是跳跃。如果使用代码表示就是如下的形势。

1
2
3
4
5
6
7
8
9
10
var run = function( animal ){
if( animal instanceof Tortoise){
console.log("爬行");
}else if( animal instanceof Rabbit){
console.log("跳跃");
}
}

run( new Tortoise()); // "爬行"
run( new Rabbit()); // "跳跃"

  这段代码从一个方面表现出多态,当发出“开始”的命令时,它们做出了不同的反应。但是这段代码是有弊端的,此时是龟兔赛跑,如果现在需要猪和兔子赛跑,或者是乌龟、兔子和猪三者一起赛跑,我们就需要修改当前的run函数了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    var run = function( animal ){
if( animal instanceof Tortoise){
console.log("爬行");
}else if( animal instanceof Rabbit){
console.log("跳跃");
- }
+ } else if( animal instanceof Pig){
+ console.log("奔跑");
+ }
}

run( new Tortoise()); // "爬行"
run( new Rabbit()); // "跳跃"
+ run( new Pig()); // "奔跑"

  不难发现,此时的多态是非常脆弱的,只因一点需求的变动我们就需修改run函数来满足。如果参赛动物过多时,就是造成run函数代码臃肿,变成巨大的函数,增加维护成本。

多态背后的思想是将“做什么”和“谁去做以及怎样去做”分离开来,也就是将“不变的事物”与 “可能改变的事物”分离开来。

  在比赛中,动物都可以行进这是不变的,只是不同的动物其行进方式是不一样的,那么把可变的部分(动物的行进方式)提取出来。

把不变的部分隔离出来,把可变的部分封装起来,这给予了我们扩展程序的能力,程序看起来是可生长的,也是符合开放—封闭原则的,相对于修改代码来说,仅仅增加代码就能完成同样的功能,这显然优雅和安全得多。

2. 多态的灵活应用

  首先是把不变的部分隔离出来,所有的动物都可行进, 那么隔离后的代码就如下:

1
2
3
var run = function( animal ){
animal.go();
}

  将可变的部分以对象的形式单独进行封装。(对象形式不限,下面使用的是构造函数方法)

1
2
3
4
5
6
7
8
9
10
11
12
var Tortoise = function(){}
Tortoise.prototype.go = function(){
console.log("爬行");
}

var Rabbit = function(){}
Rabbit.prototype.go = function(){
console.log("跳跃");
}

run(new Tortoise()); // "爬行"
run(new Rabbit()); // "跳跃"

  此时,当裁判一声令下开始的时候,动物会以各自的方式行进,当增加参赛动物时,仅需要增加该动物的代码即可,不需要修改其他的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    var Tortoise = function(){}
Tortoise.prototype.go = function(){
console.log("爬行");
}

var Rabbit = function(){}
Rabbit.prototype.go = function(){
console.log("跳跃");
}

+ var Rabbit = function(){}
+ Rabbit.prototype.go = function(){
+ console.log("奔跑");
+ }

run(new Tortoise()); // "爬行"
run(new Rabbit()); // "跳跃"
+ run(new Rabbit()); // "奔跑"

3. 多态在实际开发中的应用

  在实际的开发中我们会引入一些第三方的接口,且根据实际的项目应用条件以及需求变更,可能会有多个第三方备选方案,在更换第三方接口时未采用多态方案,则会造成大量的代码备份与修改。同时维护成本也是直线上升。

  通过对封装继承多态组合等技术的反复使用,提炼出一些可重复使用的面向对象设计技巧。而多态在其中又是重中之重,绝大部分设计模式的实现都离不开多态性的思想。

你必须懂的前端性能优化

centos7安装Nginx、使用nginx记录

一、安装各种依赖

1
2
3
4
5
6
7
8
9
10
11
#gcc安装,nginx源码编译需要
yum install gcc-c++

#PCRE pcre-devel 安装,nginx 的 http 模块使用 pcre 来解析正则表达式
yum install -y pcre pcre-devel

#zlib安装,nginx 使用zlib对http包的内容进行gzip
yum install -y zlib zlib-devel

#OpenSSL 安装,强大的安全套接字层密码库,nginx 不仅支持 http 协议,还支持 https(即在ssl协议上传输http)
yum install -y openssl openssl-devel

二、下载 nginx

官网下载地址:下载链接

三、安装

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
#根目录使用ls命令可以看到下载的nginx压缩包,然后解压
tar -zxvf nginx-1.16.1.tar.gz

#解压后进入目录
cd nginx-1.16.1

#使用默认配置
./configure

#编译安装
make
make install

#查找安装路径,默认都是这个路径
[root@VM_0_12_centos ~]# whereis nginx
nginx: /usr/local/nginx

#启动、停止nginx
cd /usr/local/nginx/sbin/
./nginx #启动
./nginx -s stop #停止,直接查找nginx进程id再使用kill命令强制杀掉进程
./nginx -s quit #退出停止,等待nginx进程处理完任务再进行停止
./nginx -s reload #重新加载配置文件,修改nginx.conf后使用该命令,新配置即可生效
./nginx -t 验证nginx 配置项是否通过

#重启nginx,建议先停止,再启动
./nginx -s stop
./nginx

#查看nginx进程,如下返回,即为成功
[root@VM_0_12_centos ~]# ps aux|grep nginx
root 5984 0.0 0.0 112708 976 pts/1 R+ 14:41 0:00 grep --color=auto nginx
root 18198 0.0 0.0 20552 612 ? Ss 11:28 0:00 nginx: master process ./nginx
nobody 18199 0.0 0.0 23088 1632 ? S 11:28 0:00 nginx: worker process

四、开机启动

1
2
3
4
5
6
#在rc.local增加启动代码即可
vi /etc/rc.local
#增加一行 /usr/local/nginx/sbin/nginx,增加后保存
#设置执行权限
cd /etc
chmod 755 rc.local

启动完成后在浏览器中输入 http://你的IP地址/ 即可看到 nginx 的欢迎界面。

五、配置域名映射

1
2
3
4
5
#进入nginx配置文件目录,找到nginx的配置文件nginx.conf
cd /usr/local/nginx/conf/

#直接修改
vi nginx.conf

修改的内容大致位置如图:

nginx.conf

1
2
3
4
5
6
7
8
#listen为监听的端口
listen 80;
#server_name为域名
server_name www.test.com;
#location是访问地址的设置,locahost也可以用服务器ip代替
location / {
proxy_pass http://localhost:8080;
}
1
2
3
#修改完成后,重新加载配置文件
cd /usr/local/nginx/sbin/
./nginx -s reload

六、域名生效

进入域名控制台,添加或者修改解析地址,如果原来配置了解析,新解析需要一定时间才能生效

转载原文:《centos7安装Nginx、使用nginx记录》

npm 常用整理

npm 常用整理

加载源设置

重置npm包的下载更新源,指向国内镜像

1
npm config set registry https://registry.npm.taobao.org

npm更新

将npm更新到最新的版本

1
npm install -g npm

查看npm包

查看npm模块是否安装成功或者查看的版本

1
npm list 模块名称

JavaScript探秘之旅 - 揭开 new 运算符的神秘面纱

JavaScript探秘之旅 - 揭开 new 运算符的神秘面纱

JavaScript 中的类

在学习ES6之前,需要谨记的一条法则就是JavaScript中是没有类的概念的,但是却能够使用new运算符进行类的实例化。这就让一些小伙伴们疑惑了。Way?

way

认识 new

在ES6之前,JavaScript确实是没有类这个概念的,这个是语言设计者在设计的时候就已经规划好的,不加入类。而能够正常使用new实例化出来一个对象,在这里应用的是一个构造函数。先通过一个示例看一下效果。

1
2
3
4
5
6
7
8
9
10
11
function Person(name){
this.name = name;
}
Person.prototype.getName = function(){
return this.name;
}
// 使用 new 运算符
var o = new Parson('Bryan');
console.log(o.name); // => Bryan
console.log(o.getName()); // => Bryan
console.log(Object.getPrototypeOf(0) === Person.prototype ); // => true

这是一个相对比较简单的构造函数应用示例。使用new运算符将普通函数转换成构造函数。

经历了什么

在使用new前后普通的函数都会受到哪些影响呢?举例说明一下:

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//  栗子一
function Person(name){
// 打印当前作用域内的this
console.log('this=>',this);
return {name:name};
}
Person.prototype.getName = function(){
return this.name;
}
// 1. 普通函数调用
var p1 = Person('Nana');
console.log('======')
console.log(p1);
console.log('p1.name=>',p1.name);
console.log('p1.getName=>',p1.getName());
/**
* 输出结果
* this=> Window
* ======
* Object { name: "Nana" }
* p1.name=> Nana
* TypeError p1.getName is not a function
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Person(name){
// 打印当前作用域内的this
console.log('this=>',this);
this.name = name;
return {name:name};
}
Person.prototype.getName = function(){
return this.name;
}
// 构造函数
var p2 = new Person('Bryan');
console.log('======')
console.log(p2);
console.log('p2.name=>',p2.name);
console.log('p2.getName=>',p2.getName());
/**
* 输出结果
* this=> Object { name: "Bryan" }
* ======
* Object { name: "Bryan" }
* p2.name=> Bryan
* TypeError p2.getName is not a function
*/

观察示例中的输出结果,未使用new时函数依然作为普通函数执行,内部的this指向window,当使用new运算符时,将普通函数转化成构造函数应用,此时的函数就是一个构造器,内部this指向发生改变,指向一个新的对象(在这里是返回的对象)。
总结下来就是一下几个方面:

  • 创建了一个新的对象(返回结果具备对象特征);
  • 新对象原型链关联函数对象原型链(原型链继承);
  • 将this指针修改为新创建的对象(构造函数内部赋值);
  • 如果函数没有返回对象,则返回this(函数返回对象,结果对象发生改变)。

(对于上述的几点总结,需要多次进行组合测试,如函数返回对象的情况,函数返回字符串的情况等等。)

原理实现

通过分析总结出在使用new运算符之后改变的多方面内容,这些内容也可以作为实现new运算符的一个思路。
在这里使用objectCreate作为操作符new的代替。按照总结的步骤思路,进行实现。

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
function Person (name){
this.name = name;
}
Person.prototype.getName = function(){
return this.name;
}
// 使用函数 objectCreate 代替操作运算符 new
function objectCreate (){
// 创建一个新的对象
var obj = {};
// 链接构造函数对象,此例子是 Person
// obj.__proto__ = Person.prototype; // => 不能写死,使用灵活的方法
// 埋点,如何将伪数组转换成数组;伪数组如何应用数组方法。
var Constructor = [].shift.call(arguments); // 提取第一个参数,当前案例即为 Person
obj.__proto__ = Constructor.prototype; // 链接对象
// 为什么要使用 obj.__proto__? 因为 __proto__ 是对象特有的,Prototype是函数特有的。

// 修改this指针为新对象,借助apply方法,接收返回的结果

var ret = Constructor.apply(obj,arguments);

// 返回最终的创建结果(分函数返回对象和其他返回结果)
// 必须保证返回的结果是对象
return typeof ret == "object" ? ret : obj;
}

// 进行实例测试
var p = objectCreate(Person, 'Bryan');
console.log(p); // => Object { name: "Bryan" }
console.log(p.name); // => Bryan
console.log(p.getName()); // => Bryan

根据总结出来的几点情况,成功复现了new操作符的功能,和使用new生成的结果是一样的。

举例

篇尾总结

当在使用new Function(...)时,发生了一些生动的事情,这些事情也并非异常什么,通过模拟能够轻轻松松解决其神秘面纱。在来看看会发生的事情有哪些:

  1. 一个继承自 Function.prototype 的新对象被创建。
  2. 使用指定的参数调用构造函数 Function,并将 this 绑定到新创建的对象。
  3. 由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤新创建的对象。