JavaScript作用域及作用域链的详解

1. JavaScript作用域

  作用域就是变量与函数的可访问范围,JavaScript的变量作用域分为两种:全局作用域局部作用域。另外JavaScript块级作用域

2.全局作用域

  在代码中任何地方都可以访问到的对象拥有全局作用域。全局作用域里的变量能够在其他作用域中被访问和修改。

  • 最外层函数在最外层函数外定义的变量拥有全局作用域
  • 所有未定义直接赋值的变量自动声明为拥有全局作用域
  • 所有window对象的属性拥有全局作用域
1
2
3
4
5
6
7
8
9
10
11
var str1 = "jessy1"; //定义一个全局变量
function func(){ //最外层函数,拥有全局作用域
var str2 = "jessy2" //局部作用域
str3 = "jessy3" //未定义直接赋值的变量
window.str4 = "jessy4" //window对象的属性
}
func(); //全局作用域
console.log(str1) // jessy1
console.log(str2) // str2 is not defined 局部作用域
console.log(str3) //jessy3
console.log(str4) // jessy4

3.局部作用域

  定义在函数中的变量就在局部作用域中。并且函数在每次调用时都有一个不同的作用域。这意味着同名变量可以用在不同的函数中。因为这些变量绑定在不同的函数中,拥有不同作用域,彼此之间不能访问。

1
2
3
4
5
6
7
8
9
function func1(){
var str = "jessy1" //局部作用域
console.log(str) //jessy1
}
function func2(){
var str = "jessy2" //局部作用域
console.log(str) //jessy2
}
console.log(str) // str is not defined 局部作用域

  以上例子可以看出,func1和func2函数都绑定了str变量。他们拥有不同的作用域,只可以访问自身的str。彼此不能访问。

4. JavaScripte没有块级作用域

  块级声明包括ifswitch,以及forwhile循环,和函数不同,它们不会创建新的作用域。在块级声明中定义的变量从属于该块所在的作用域。

1
2
3
4
5
6
7
8
9
10
11
if(true){
var str1 = "Jessy1";
}
function func(){
if(true){
var str2 = "Jessy2";
}
console.log(str2) //if块的作用域是局部,因此局部内可以访问str2
}
console.log(str1) //str1所属的块的作用域是全局作用域
console.log(str2) //str2 is not defined,str2所属块是局部作用域

  ECMAScript 6 引入了let关键字,用来声明变量。let 声明的变量只在它所在的代码块有效。

1
2
3
4
5
6
if(true){
let str1 = "Jessy1";
var str2 = "Jessy2"
}
console.log(str1) //str1 is not defined
console.log(str2) //Jessy2

  上面代码在代码块之中,分别用letvar声明了两个变量。然后在代码块之外调用这两个变量,结果let声明的变量报错,var 声明的变量返回了正确的值。这表明,let 声明的变量只在它所在的代码块有效。

5.执行环境(execution context)

  每个执行环境都有一个与之关联的变量对象,环境中定义的所有的变量和函数都保存在这个变量对象中。

5.1 执行环境

在JavaScript中有三种代码运行环境:

  • Gloal Code
      JavaScript代码开始运行的默认环境
  • Function Code
      代码进入一个JavaScript函数
  • Eval Code
      使用eval()执行代码

  为了表示不同的运行环境,JavaScript中有一个执行上下文(Execution context,EC)的概念也就是说,当JavaScript代码执行的时候,会进入不同的执行上下文,这些执行上下文就构成了一个执行上下文栈(Execution context stack,ECS)
例如以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var a = "global var";
debugger;
function foo(){
console.log(a);
}
function outerFunc(){
var b = "var in outerFunc";
console.log(b);
eval("var e='eee'");
function innerFunc(){
var c = "var in innerFunc";
console.log(c);
foo();
}
innerFunc();
}
eval("var d='ddd'");
outerFunc();

  代码首先进入Global Execution Context,然后依次进入outerFuncinnerFuncfoo的执行上下文,执行上下文栈可使用Chrome浏览器按F12查看,如下图:
image

  当JavaScript代码执行的时候,第一个进入的总是默认的Global Execution Context,所以说它总是在ECS的最底部
  对于每个Execution Context都有三个重要的属性,变量对象(Variable object,VO),作用域链(Scope chain)和this。

5.2 变量对象(Variable object)

  变量对象是与执行上下文相关的数据作用域。它是一个与上下文相关的特殊对象,其中存储了在上下文中定义的变量和函数声明。也就是说,一般VO中会包含以下信息:

  • 变量 (var, Variable Declaration);
  • 函数声明 (Function Declaration, FD);
  • 函数的形参
      当JavaScript代码运行中,如果试图寻找一个变量的时候,就会首先查找VO。对于前面例子中的代码,Global Execution Context中的VO就可以表示如下:
    image

5.3 活动对象(Activation object)

  只有全局上下文的变量对象允许通过VO的属性名称间接访问;在函数执行上下文中,VO是不能直接访问的,此时由激活对象(Activation Object,缩写为AO)扮演VO的角色。激活对象 是在进入函数上下文时刻被创建的,它通过函数的arguments属性初始化。
  Arguments Objects 是函数上下文里的激活对象AO中的内部对象,它包括下列属性:

  • callee: 指向当前函数的引用
  • length: 真正传递的参数的个数
  • properties-indexes: 就是函数的参数值(按参数列表从左到右排列)

  对于VO和AO的关系可以理解为,VO在不同的Execution Context中会有不同的表现:当在Global Execution Context中,可以直接使用VO;但是,在函数Execution Context中,AO就会被创建。
当上面的例子开始执行outerFunc的时候,就会有一个outerFunc的AO被创建:
image

6. 作用域链

  了解变量对象和活动对象之后,我们来了解下什么是作用域链。
  在执行环境创建阶段,作用域链在变量对象之后创建。作用域链包含变量对象。作用域链用于解析变量。当解析一个变量时,JavaScript 开始从最内层沿着父级寻找所需的变量或其他资源。作用域链包含自己执行环境以及所有父级环境中包含的变量对象。
  内部环境可以通过作用域链访问所在的外部环境,但是外部环境不能访问内部环境的任何变量和函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var level1 = "爷爷";
function outerFunc() {
var level2 = "爸爸";
function innerFunc() {
var level3 = "自己";
console.log(level1)
console.log(level2)
console.log(level3)
//这里可以访问level1,level2,level3
//在swapColor执行环境中找不到level1,level2变量对象,从父级环境中找到
}
innerFunc();
console.log(level1)
console.log(level2)
//这里可以访问level1,level2,但是不能访问level3
//(即在changeColor这个大函数的作用域内)
}
//这里只能访问level1 (即全局作用域)
outerFunc();

  从以上例子可以看出,在innerFunc执行环境中,可以访问自身的变量level1.。接着访问level2,找不到自身环境的level2,于是从父级outerFunc执行环境中开始查找到level2。访问level3时,在自身执行环境和父级执行环境都找不到,继续往父级的父级(爷爷级),最后找到爷爷级的level3。也就是说作用域链是从自己执行环境然后从父级环境一层层往上查找的。
  在outerFunc执行环境中,可以访问自身的变量level2以及父级变量level3。不能访问内部环境的变量level1。


作者: Jessy Hong

JavaScript的深拷贝和浅拷贝

1. JavaScript的变量类型

  • 基本类型:
     5种基本数据类型Undefined、Null、Boolean、NumberString,变量是直接按值存放的,存放在栈内存中的简单数据段,可以直接访问。

  • 引用类型:
    存放在堆内存中的对象,变量保存的是一个指针,这个指针指向另一个位置。当需要访问引用类型(如对象,数组等)的值时,首先从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据。

 JavaScript存储对象都是存地址的,所以浅拷贝会导致 obj1 和obj2 指向同一块内存地址。改变了其中一方的内容,都是在原来的内存上做修改会导致拷贝对象和源对象都发生改变,而深拷贝是开辟一块新的内存地址,将原对象的各个属性逐个复制进去。对拷贝对象和源对象各自的操作互不影响。

2. 深复制和浅复制

 深复制和浅复制只针对像 Object, Array 这样的复杂对象的。简单来说,浅复制是指只复制一层对象的属性,不会复制对象中的对象的属性,深复制则会递归复制了所有层级。

2.1 浅复制

方法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
shallowCopy(obj) {
// typeof判断类型,数组和对象返回object
if (typeof obj !== "object") return;
// 根据obj的类型判断是新建一个数组还是对象
var newObj = obj instanceof Array ? [] : {};
// 遍历obj,并且判断是obj的属性才拷贝
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key];
}
}
return newObj;
}
1
2
3
4
5
6
var newUser = this.shallowCopy(user)
newUser.from = "shenzhen"
newUser.name.lastName = "HaHa"
console.log(newUser.from === user.from) //false
console.log(newUser.name.lastName === user.name.lastName) //true
//浅复制,只复制一层对象的属性,第二层对象的属性lastName/firstName不复制

方法二:

1
2
3
4
5
6
7
8
shallowCopy(obj) {
if (typeof obj !== "object") return;
//Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。
if (obj instanceof Array) {
return Object.assign([], obj);
}
return Object.assign({}, obj);
}
1
2
3
4
5
6
7
8
9
10
11
var user = {
from: "zhuhai",
name: {
lastName: "Jessy"
}
}
var newUser = this.shallowCopy(user)
newUser.from = "shenzhen"
newUser.name.lastName = "HaHa"
console.log(newUser.from === user.from) //false
console.log(newUser.name.lastName === user.name.lastName) //true

2.2 深复制

方式一: 递归方式

1
2
3
4
5
6
7
8
9
10
11
deepCopy(obj) {
// 方式一 递归方式
if (typeof obj !== "object") return;
var newObj = obj instanceof Array ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = typeof obj[key] === "object" ? this.deepCopy(obj[key]) : obj[key];
}
}
return newObj;
}
1
2
3
4
5
6
7
8
9
10
11
var user = {
from: "zhuhai",
name: {
lastName: "Jessy"
}
}
var newUser = this.deepCopy(user)
newUser.from = "shenzhen"
newUser.name.lastName = "HaHa"
console.log(newUser.from === user.from) //false
console.log(newUser.name.lastName === user.name.lastName) //false

方式二: JSON

1
2
3
4
5
6
7
8
deepCopy(obj) {
//方式二 JSON
if (typeof obj !== "object") {
return;
}
var str = JSON.stringify(obj);
return JSON.parse(str);
}
1
2
3
4
5
6
7
8
9
10
11
var user = {
from: "zhuhai",
name: {
lastName: "Jessy"
}
}
var newUser = this.deepCopy(user)
newUser.from = "shenzhen"
newUser.name.lastName = "HaHa"
console.log(newUser.from === user.from) //false
console.log(newUser.name.lastName === user.name.lastName) //false

作者: Jessy Hong

用Hexo + Github 搭建属于自己的博客

1 环境准备 (此处不做介绍,So Easy)

  • github账号
  • 安装nodeJS, npm
  • 安装了Git

2 搭建github博客

2.1 创建仓库

新建一个名为 你的用户名.github.io的仓库,并且需要勾选Initialize this repository with a README.(因为我的已经存在这个仓库名字了,才会提示错误,请忽略)
image

3. 配置SSH Key(也可使用账号登录,忽略此步骤)

  1. 输入cd ~/.ssh 回车(看你是否有了ssh key 密钥,有了就备份)
  2. ssh-keygen -t rsa -C "邮件地址"
    然后连续3次回车,最终会生成一个文件在用户目录下,打开用户目录(C:\Users\Your UserName\.ssh),找到.ssh\id_rsa.pub文件,记事本打开并复制里面的内容,
    打开你的github主页,进入Setting -> SSH and GPG keys -> New SSH key:将复制的内容黏贴到输入框中
  3. 测试一下链接是否正常了,接着输入: ssh -T git@github.com,这时会问是否继续连接,我们输入 yes,这样,我们的git配置就完成了。
    image

4.使用Hexo写博客

  1. Hexo是一个快速, 简洁且高效的博客框架,支持Markdown格式,有很多优秀插件和酷炫主题。

    官网: http://hexo.io
    github: https://github.com/hexojs/hexo

  2. 安装Hexo

1
$ npm install -g hexo
  1. 初始化博客
    在电脑的某个地方新建一个名为my-blog的文件夹(名字可以随便取),比如我的是D:\gitHome\my-blog,这个文件夹就是你存放项目代码的地方。
1
2
3
4
5
$ D:\gitHome\my-blog #进入文件目录下
$ hexo init #初始化博客
$ npm install #下载博客的依赖包
$ hexo g #public文件夹下生成静态资源
# hexo s #启动hexo服务,访问localhost:4000,既然可以看到内容

默认的主题是landscape,界面如下:
image

  1. 修改主题
    如果不喜欢默认的主题,可以到官方主题上找到自己喜欢的:https://hexo.io/themes/
    本人最后选择了icarus(功能还算齐全,后续会继续介绍其配置)
    首先下载主题:
1
2
$ D:\gitHome\my-blog #进入文件目录下
$ git clone https://github.com/ppoffice/hexo-theme-icarus.git themes/icarus

下载主题后在D:\gitHome\my-blog\themes目录下会看到该主题
image

进入到D:\gitHome\my-blog\themes\icarus, 有_config.yml.example的文件,拷贝一份并且改名为_config.yml
另外进入D:\gitHome\my-blog,修改该目录下的_config.yml文件,修改theme主题为icarus
image

如果出现一些奇怪的问题,可以先执行==hexo clean==来清理public的内容,然后再执行==hexo g, hexo s.==

  1. 部署到github上
  • 方式一: 首先,ssh key已经配置好的方法。
    修改D:\gitHome\my-blog_config.yml文件中的 deploy部分
1
2
3
4
deploy:
type: git
repository: git@github.com:用户名/用户名.github.io.git
branch: master
  • 方式二:没有配置ssh key,可直接用账号/密码部署
1
2
3
4
deploy:
type: git
repository: https://用户名:密码@github.com/用户名/用户名.github.io.git
branch: master

最后,执行 ==hexo d== 命令进行部署
这个时候就可以网上访问到你的博客啦^~^ 比如我的:jessyhong.github.io

5. 绑定域名

首先你要注册一个域名,我是使用阿里云的,.top/.club等后缀的一年才4块钱,便宜到想哭

  • 域名配置最常见有2种方式,CNAME和A记录,CNAME填写域名,A记录填写IP。
    如果要使用A记录方式,先ping一下 用户名.github.ip的IP.
    image
  • 将A记录指向你ping出来的IP,将CNAME指向你的用户名.github.io,这样可以保证无论是否添加www都可以访问,如下:
    image

  • 然后到你的github项目根目录新建一个名为CNAME的文件(无后缀),里面填写你的域名jessyhong.top
    在你绑定了新域名之后,原来的你的用户名.github.io并没有失效,而是会自动跳转到你的新域名。

  • 欲知如何配置hexo主题中配置/信息,请见 《Hexo主题icarus的配置修改 & Icarus站内搜索swiftype》


    作者: Jessy Hong

Hexo主题icarus的配置修改 & Icarus站内搜索swiftype

1. Icarus提供了多语言切换,默认是英文的,改成中文的方式是:

进入项目目录(我的:D:\gitHome\my-blog),修改_config.yml 中的 language: zh-CN

2. 新建博客文章:

1
$ hexo new page post test-post(文章名)

会在_posts目录下生成test-post.md的文件,内容如下

1
2
3
4
5
6
tags/categories自己手动加,下一个需要用
title: test-post
date: 2018-04-25 18:04:11
tags: 标签1
categories: 分类1
thumbnail: /img/set-up-blog-title.png #文章的图片

3. 默认是没有categories/tags/about页面的,因此点击menu bar的时候会跳到错误页面

创建 categories/tags/about 页面

1
2
3
$ hexo new page categories
$ hexo new page tags
$ hexo new page about

创建完成之后一定要看看tags/categories生成的index.md文件的内容是否如下:

1
2
3
4
5
6
title: categories
layout: categories
# layout: tags 这是tags页面的
date: 2018-04-24 17:42:36
comments: false
type: categories

layout: categories 这个配置至关重要,如果不配置,
那么categories/tags 页面是不会有内容的,不会自动去扫文章中的categories的。

4.站内搜索 swiftype

  • 第一步:注册账号   https://swiftype.com/
  • 第二步:
    Create a New Engine –> 选择Swiftype Web Crawler –> Continue –> 输入Website URL(jessyHong.github.io) –> Create Engine Anyway –>最后输入Engine Name
    以上步骤创建Engine完成。

image

  • 第三步:
    等待一段时间,Swiftype回去爬取你的网站页面。可以再Content看到已经爬到了可搜索的页面

image

  • 第四步:
    安装代码,点击Install Search,复制install后面的key的内容。
    粘贴到主题目录D:\gitHome\my-blog\themes\icarus中的_confirm.yml文件中search下的swiftype(另外要把insight的改成false)

image

1
2
3
4
search:
insight: false
swiftype: 你的key
baidu: false

第五步: 修改配置

如上图: Install Search -> Change Configuration
之后可看到更详细的配置,4个部分,样式默认就好,也可以自己选;这里就说下面两个部分。

  • 配置results container - 搜索结果页,我使用的是默认的,在页面底部有自定义搜索页的案例。
    image

  • 配置Search field,这个就是搜索框-input的相关配置了,icarus主题默认就有,和swiftype提供的search field都是一样的input标签:
    image

    第六步:swiftype预览结果

    image


作者: Jessy Hong