-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontent.json
More file actions
1 lines (1 loc) · 55.8 KB
/
content.json
File metadata and controls
1 lines (1 loc) · 55.8 KB
1
{"meta":{"title":"Cloud","subtitle":"From end to beginning","description":"","author":"Zoe","url":"http://iszoe.github.io"},"pages":[],"posts":[{"title":"变量提升与函数作用域链的理解","slug":"var","date":"2018-02-12T04:04:30.000Z","updated":"2018-04-26T11:47:17.952Z","comments":true,"path":"2018/02/12/var/","link":"","permalink":"http://iszoe.github.io/2018/02/12/var/","excerpt":"","text":"写篇日记说说自己对变量提升与函数作用域链的理解,看看自己对基础知识的认知有什么疏漏的地方。 想要更清晰的理解变量提升与函数作用域链,我们可以从JavaScript解释器如何执行函数的规则开始。在这之前,我们先来了解一个常说的概念执行上下文。 执行上下文 Execution Context当需要向别人描述一件事情的经过时,往往需要把事情的前因后果以及与其相关的场景预先说明一下。相同的,在代码运行之前,JavaScript解释器会做一些变量内存分配,代码上下文关联的准备工作,这就是Execution Context。 所以执行上下文 Execution Context 可以认为是一个执行环境,这个执行环境可以有三种类型产生。 全局代码,全局代码会产生一个全局的执行上下文当浏览器加载我们的script的时候,首先会进入默认的global execution context全局执行上下文,在全局代码中,你声明了一个函数,当程序执行到这个函数时,就会创建一个新的执行上下文EC,并将这个上下文推到执行堆栈的顶部。函数运行结束,或者return的时候,都会退出当前执行上下文的,相应地EC堆栈就会弹出,栈指针会自动移动位置。相关代码执行完以后,EC堆栈只会包含全局上下文(global context),一直到整个应用程序结束。 函数代码,每次执行一个函数的时候都会产生一个新的函数执行上下文当前ECMAScript规范指出独立作用域只能通过“函数(function)”代码类型的执行上下文创建。所以函数类型的执行上下文,会产生一个独立的作用域。 Eval代码,要在内部eval函数内执行的文本会产生一个新的执行上下文。 在执行代码的时候活动的执行上下文组在逻辑上组成一个 堆栈。堆栈底部永远都是全局上下文(global context),而顶部就是当前(活动的)执行上下文。堆栈在EC(执行上下文)类型进入和退出上下文的时候被修改(推入或弹出)。 举个例子,来自 What is the Execution Context & Stack in JavaScript? 12345678(function foo(i) { if (i === 3) { return; } else { foo(++i); }}(0)); 上面的函数递归调用了自己三次,每次调用foo函数的时候都会创建一个新的执行上下文。直到函数执行完毕,退出当前执行上下文的,直到EC堆栈只包含全局上下文。如下面的动图 执行上下文堆栈,有下面5个注意点1.单线程2.同步执行3.只有一个全局上下文(Global context)4.可以创建无数的执行上下文5.每个函数调用的时候都会创建一个新执行上下文,就算是递归函数调用自己也会创建新的执行上下文 那么,当执行一个函数的时候发生了什么?每当一个函数执行的时候,就会产生一个新的执行上下文也叫执行环境(Execution Context),然后执行代码。 对 JavaScript 解释器来说,每次对执行上下文的调用都会有两个阶段: 创建阶段 Creation Stage(注意是函数调用的时候,但还没有执行任何函数內的代码 创建作用域链 Scope Chain 创建变量,函数和参数 variable object 确定 this 的值 代码执行阶段 Activation / Code Execution Stage 分配值、引用函数和解释/执行代码 于是在创建阶段,我们可以将可执行的上下文定义为有三个属性的对象 12345executionContextObj = { 'scopeChain': { /* 作用域链 */ }, 'variableObject': { /* 函数参数/参数、内部变量和函数声明。 */ }, 'this': {}} 先来聊一聊 variableObject 变量对象(简称VO)对编程语言来说,给变量赋值,获取变量的值,是需要解决的基本问题,变量自己应该知道它的数据存储在哪里,并且知道如何访问。这种机制称为变量对象(variable object)。如上所述,每个函数执行都会新建执行上下文,执行上下文创建时 JavaScript 解释器会对函数內函数,声明还有参数初始化,这些数据就会储存在变量对象VO中。 1234全局上下文变量对象GlobalContextVO (VO === this === global)函数上下文变量对象FunctionContextVO (VO === AO, 并且添加了<arguments>和<formal parameters>) 在代码执行前,我们就会有一个全局环境 globalContext 。 全局上下文的变量对象就是全局对象自身,在其他上下文中VO是由活动对象(activation object,缩写为AO)充当,是不能直接访问的,因为它只是内部机制的一个实现。变量对象就是一个包含普通参数与特殊参数的对象(可以认为是具有索引属性的参数映射表)。 全局环境的变量对象VO始终存在,而局部环境的变量对象VO,则只在函数执行的过程中存在。 JavaScript 解释器在VO储存数据的规则(按照顺序执行) 创建参数对象 arguments object,检查参数的上下文,初始化名称和值,并创建一个引用副本。 扫描上下文函数声明。2.1 对于找到的每个函数,在变量对象中创建一个属性,该属性是确切的函数名,它有一个指向内存中函数的引用指针。2.2 如果函数名对应的属性名已经存在则重写这个属性的值 扫描上下文变量声明。3.1 创建参数对象对于找到的每个变量声明,在变量对象中创建一个属性,该属性名就是是变量名,并初始化该值为 undefined。3.2 如果这个属性名已经存在则什么都不做,继续扫描。 举个栗子洛12345var a = 1;function b(n){ var c = 2;}b(20) 最外层的全局执行环境会创建一个全局上下文的变量对象,运行到b()执行时又会生成一个函数上下文对象。跟据上面的规则我们来捋一下这两个上下文对象的VO。 创建阶段:1234567891011VO(globalContext){ a: undefined, b: pointer to function b()}VO(b functionContext) = { arguments:{ n:20 } c: undefined}; 在全局上下文 globalContext ,没有参数对象,JavaScript 解释器会接着扫描函数声明,找到函数 b激活在VO对象上,然后会扫描变量声明,找到变量 a 就会初始化属性名 a 在VO中。创建过程结束后,会执行全局上下文,执行到 b 函数前,就会创建一个 b 函数运行函数的上下文,首先创建参数对象 arguments,接着扫描到 b 函数內的函数声明,然后变量声明,扫描到变量 c 初始化变量 c 储存在函数上下文VO中。 执行阶段:1234567891011VO(globalContext){ a: 1, b: pointer to function b()}VO(b functionContext) = { arguments:{ n:20 } c: 2}; 执行环境创建后,才开始执行代码进入执行阶段,此时变量对象才开始被赋值。在执行环境上下文內声明的变量或者函数,会保存在当前最近的执行上下文的VO中。每次新执行一个函数都会开辟一个新执行环境的上下文。 以上对函数中函数和声明的初始化,就会发生我们所说的的变量提升现象。 仔细说说被提升的变量举个三个栗子1234567alert(a) //报错 Uncaught ReferenceError: a is not defined//=============alert(b) //undefinedvar b = 1;//=============alert(c) //报错 Uncaught ReferenceError: c is not definedc = 1; 第二个栗子对比第一个执行的时候会弹出undefined而不是报错,因为在代码执行前在 executionContextObj 中已经创建的变量对象VO中初始化了变量的值。从而函数执行时,属性值 b已经被初始化为 undefined。 12345VO(globalContext){ b: undefined}alert(b) //undefinedb = 1 第三个栗子不使用 var 声明变量使js报错了。1c = 1; 这种赋值语句相当于给全局对象创建了一个新的属性。使用关键字声明的属性和直接赋值给对象的属性并不是同一概念。使用关键字声明的变量有一个特性(attribute):{DontDelete},这个特性意味着不能用 delete 操作符直接删除变量。 任何使用 var声明的属性不能从全局作用域或函数的作用域中删除。 任何用 let 或 const 声明的属性不能够从它被声明的作用域中删除 举个小例子(第四栗子)1234567a = 10;alert(delete a); // truealert(window.a); // undefinedvar b = 20;alert(delete b); // falsealert(window.b); // still 20 总结一下:函数执行前会扫描上下文中变量声明,遇到 var 等关键字声明的变量时会将变量名作为属性名储存在当前执行上下文的VO中。如果初始化变量时没用关键字声明,就相当于给全局环境对象创建了一个新属性,在执行代码时才会进行赋值,不会在创建VO的阶段初始化这个属性,也就没有变量提升的现象。 按照解释器运行规则,函数声明也会被解释器提升声明,而且函数会在变量创建之前就激活在VO上。 举个栗子123456789console.log(typeof a); //function pointerconsole.log(typeof b); //undefinedvar a = 'hello';var b = function(){ return 'word'}function a(){} 为什么我们打印a的类型的时候是 function ,而 b 的类型是undefined 呢?首先解释第一个问题 a 的类型为什么是 function ?上面我们有了解了关函数执行前创建阶段,会先扫描函数声明,并且激活在VO(globalContext)上。123VO(globalContext){ a: pointer to function a()} 然后接下来解释器会扫描变量声明。当扫描到var a = 'hello';时解释器会将 a 作为属性储存在VO(globalContext)中,但是VO(globalContext)中 a这个属性名已经存在,所以解释器什么都不会做,继续扫描下面的代码直至结束,于是储蓄在VO(globalContext)中的a属性指向函数a。 由此我们可以调换一下声明的位置举个栗子12345console.log(typeof a); //function pointerfunction a(){}var a = 'hello'; so ~ 结果肯定还是一毛一样。 再看看第二个问题 b 的类型为什么是undefined 呢?当解释器扫描到var b = XXX的时候,变量b会创建在变量对象VO上,但是解释器不会解释等号右边的 Assignment Expression(赋值表达式)。123VO(globalContext){ b: undefined} 在函数执行的创建阶段,解释器只会扫描函数声明,在变量对象VO中创建一个和函数名一样的属性名区分函数声明与函数表达式,我们就可以清楚的知道哪些会函数会提升,哪些不会。 12345678910111213alert(a); //function a(){}alert(b); //undefinedalert(c); //报错 Uncaught ReferenceError: c is not definedalert(d); //报错 Uncaught ReferenceError: c is not definedfunction a(){ return 'hello'}var b = function(){ return 'word'}(function c (){}())(function d (){})() 总结一下: 匿名函数必然是函数表达式 如果有名字的函数作为赋值表达式的一部分那么他也是一个表达式 如果有名字的函数被括号“()”括住,那么他也是一个表达式 关于变量提升还有两个小点需要注意 eval 中的代码没有变量提升,解释器运行到eval时会开辟一个新的上下文环境。 return 后的声明的函数不会提升 再来了解一下 scopeChain 作用域链:先铺上定义,引用一下书中所写 每个执行上下文都有自己的作用域链scopeChain,用于解析标识符。当执行上下文被创建的时候,它的作用域链初始化为当前运行函数的scope属性中的对象。这些值按照他们出现在函数中的顺序,被复制到执行上下文的作用域链中。这个过程一旦完成,一个被称为“变量对象(AO)”的新对象就为执行上下文创建好了。变量对象作为函数运行时的变量对象,包含了所有的局部变量,命名参数,参数集合以及this。然后此变量对象被推入作用域链的最前端。 函数中存在这一个内部属性[[Scope]],在函数创建的时候便能确定,这个内部属性就会包含函数被创建的作用域中对象的集合。 举个栗子洛12345678910111213var x = 10;(function foo() { var y = 20; (function bar() { var z = 30; // \"x\" and \"y\" are \"free variables\" // and are found in the next (after // bar's activation object) object // of the bar's scope chain console.log(x + y + z); })();})(); 函数中的[[Scope]]属性会包含她们 外层的变量VO的集合。我们看一下12345// foo的scope属性是global的VOfoo.["[[Scope]]"] = global.["Variable Object"]// bar的scope属性是foo的AO和global的VO的集合bar.["[[Scope]]"] = {foo.["Activation Object"], global.["Variable Object"]} 在一个新函数创建的时候,就会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部[[scope]]属性中。这个[[scope]]属性是在函数创建的时候就已经被确定的,而在调用这个函数的时候,创建了执行上下文 executionContextObj 然后通过复制函数的[[scope]]属性,然后再创建AO放在复制的作用域链的最顶部”0号位”,初始化为作用域链scopeChain。 所以scopeChain是一个集合:当前上下文的[VO] + (保存在[[scope]]內的)所有父类的词法[VO]的集合。 再来个栗子1var total = add(5, 10); so ~ 我们可以认为 scopeChain 就是 当前上下文的[VO] + 函数的[[scope]]属性 既然函数的[[scope]]是一个静态的属性,在函数创建的时候就被指定了,所以函数的作用域链其实在函数创建的时候也确认了,无关乎函数在哪里调用。 我们来看一个栗子12345678910var a = 2;function foo(){ console.log(a)}function nick(){ var a = 4; foo()}nick() 函数foo被调用时控制台会打印2还是4呢?12345678910111213//foo函数定义在全局对象上,所以foo 函数的[[scope]]属性foo.["[[Scope]]"] = global.["Variable Object"]//再来看一下全局的VO对象,运行到 foo 函数时,全局的上下文已经在执行阶段,属性a已经赋值。global.VO = { a : 2, foo : pointer to function foo() nick : pointer to function nick()}//foo 函数执行时,会讲foo 函数上下文的VO对象推入scope chain 的第0位;fooExecutionContext:{ 'scopeChain': { foo.["Variable Object"],global.["Variable Object"] }, 'variableObject': {}, } 标识符解析是沿着作用域链一级一级地搜索标识符的过程。所以在搜索属性a的时候,会先从foo对象VO中搜索a属性,未检索到会接着往下一级搜索,此时foo函数的下一级时全局global.VO,全局VO中a属性的值为2,所以控制台打印的值是2。 栗子一个接一个123456789101112131415var myAlerts = [];for (var i = 0; i < 5; i++) { myAlerts.push( function inner() { alert(i); } );}myAlerts[0](); // 5myAlerts[1](); // 5myAlerts[2](); // 5myAlerts[3](); // 5myAlerts[4](); // 5 我们希望myAlerts数据內的方法可以保存每次循环i的值,但是弹出每个i值却都是5,那这是为什么呢? 在 Javascript 中for循环是不能创建独立的作用域的。ECMAScript规范指出独立作用域只能通过“函数(function)”代码类型的执行上下文创建。所以 myAlerts[0],myAlerts[1],myAlerts[2]…的函数都是创建在全局环境下 12345678//myAlerts[0]myAlerts[0].[\"[[Scope]]\"] = global.[\"Variable Object\"]global.VO{ i : undefined}//每次循环的时候 global.VO.i都会被重新赋值//当myAlerts[0]()调用时,全局循环已经结束global.VO.i = 5 所以不管调用myAlerts数组內的哪一个函数打印出的i值都是5。 这个问题要如何解决?我们熟悉的闭包来啦~ 闭包(closures):函数对象可以通过作用域链相关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性称为 ‘闭包’ 想要解决上面的问题,我们就不能在myAlerts[0]的函数內直接引用global.VO中的i值。我们需要创建一个新的作用域。12345678910111213var myAlerts = [];for (var i = 0; i < 5; i++) { (function (n){ myAlerts.push( function inner() { alert(n); } ); })(i)}myAlerts[0](); // 0 我们在for循环里面创建了一个立即执行函数IEF,每次循环就会执行一次IEF,每次执行IEF都会新建一个上下文对象。 12345678910//第一次循环i=0IEF.VO:{ arguments:{ n:0 } myAlerts[0]:pointer to function inner()}//当函数myAlerts[0]执行时ECobj.scopeChain = { myAlerts[0].VO , IEF.VO , global.VO}//函数myAlerts[0]会先搜索函数內定义的属性n,接着会搜索下一级IEF.VO中的属性n,找到IEF.VO.arguments.n,值为0 当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途,是 保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所 在环境的变量对象。如果这个环境是函数,则将其变量对象(activation object)作为变量对象。变量对象在最开始时只包含一个变量,即 arguments 对象(这个对象在全局环境中是不存在的)。作用域链中 的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一直延 续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始, 然后逐级地向后回溯,直至找到标识符为止(如果找不到标识符,通常会导致错误发生)。 总结每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。 而在函数执行完成后,栈将其环境弹出,该环境被销毁(保存在其中的所有变量和函数定义也随之销毁),控制权会返回给之前的执行环境。 复习一下三个重要的点。 执行上下文调用过程 创建阶段:创建执行上下文对象。 执行阶段:分配值、引用函数和解释/执行代码 执行上下文对象结构:12345executionContextObj = { 'scopeChain': { /* 作用域链 当前VO + 函数[[scope]]*/ }, 'variableObject': { /* 函数参数/参数、内部变量和函数声明。 */ }, 'this': {}} `variableObject储存数据的规则(按照顺序执行) 创建参数对象 arguments object,检查参数的上下文,初始化名称和值,并创建一个引用副本。 扫描上下文函数声明。2.1 对于找到的每个函数,在变量对象中创建一个属性,该属性是确切的函数名,它有一个指向内存中函数的引用指针。2.2 如果函数名对应的属性名已经存在则重写这个属性的值 扫描上下文变量声明。3.1 创建参数对象对于找到的每个变量声明,在变量对象中创建一个属性,该属性名就是是变量名,并初始化该值为 undefined。3.2 如果这个属性名已经存在则什么都不做,继续扫描。 再举最后一个栗子巩固一下: 123456789function check (count){ return function add(){ console.log(++ count) }}var n = check(2)n()//3n()//4 123456789101112131415161718192021222324//初始化global.VO:{ check: pointer to function check() n: undefined}//执行到var n = check(2)global.VO:{ check: pointer to function check() n: pointer to function add()}check.VO:{ //初始化check arguments:{ count: 2 }}check.scopeChain:{check.VO,global.VO}add.VO:{ //初始化add}add.scopeChain:{add.VO, check.VO,global.VO}//执行到n() 根据作用域链在check.VO找到count为2;执行 ++count;打印3//check.VO.count 重新赋值 3//重复执行n() 根据作用域链在check.VO找到count为3;执行 ++count;打印3//check.VO.count 重新赋值 4 将将将~ 就到这里啦 参考Javascript的声明What is the Execution Context & Stack in JavaScript?我用了两个月的时间才理解 letECMA-262-3 in detail","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://iszoe.github.io/categories/JavaScript/"},{"name":"基础","slug":"JavaScript/基础","permalink":"http://iszoe.github.io/categories/JavaScript/基础/"}],"tags":[]},{"title":"呕心沥血对 JavaScript 中 this 的理解","slug":"this","date":"2018-01-31T06:01:36.000Z","updated":"2018-03-04T15:16:56.009Z","comments":true,"path":"2018/01/31/this/","link":"","permalink":"http://iszoe.github.io/2018/01/31/this/","excerpt":"","text":"this到底指向哪里在JavaScript 中,函数中this到指向,经常会引起自己的困惑,所以写一篇文章来整理下自己对这个疑点的理解。 函数的执行所有的 JavaScript 函数都有一个内部属性[[Call]],用来运行该函数。1F.[[Call]](thisArg, argumentsList) 上面代码中,F是一个函数对象,[[Call]]是它的内部方法,F.[call]表示运行该函数,thisArg表示[[Call]]运行时this的值,argumentsList则是调用时传入函数的参数。 thisArg和this是什么关系?ECMA规范里的描述是这样的: If the function code is strict code, set the ThisBinding to thisArg.在严格模式下,thisArg和this是一一对应的。 Else if thisArg is null or undefined, set the ThisBinding to the global object.如果thisArg为null或者undefined则this指向全局对象。 Else if Type(thisArg) is not Object, set the ThisBinding to ToObject(thisArg).如果thisArg为非对象类型,则会强制转型成对象类型。 Else set the ThisBinding to thisArg.剩下的情况thisArg和this为一一对应的关系。 概括一下就是3条:1.thisArg和this是一一对应。2.非严格模式下thisArg为null或者undefined则this指向全局对象。3.如果thisArg为非对象类型,则会强制转型成对象类型。 记住这三条,JavaScript 和其他语言不同的是,函数中this的指向不是在函数声明时指定的,而是调用时指定。那this到底是指向哪里呢? In JavaScript, as in most object-oriented programming languages, this is a special keyword that is used within methods to refer to the object on which a method is being invoked.一般而言JavaScript中,this 指向函数执行的当前对象 在JavaScript 中函数调用大概有这4中情况: 1.调用对象方法 2.普通函数调用 3.间接调用 4.构造函数调用 我们分别来看一下这四种情况 1.调用对象方法调用对象的方法类似 obj.fn() 的调用形式。 举个栗子 12345678910111213var a = { name: 'bob', showName: function(){ console.log(this.name) }}var b = { name: 'lily', showName: a.showName}b.showName() //lily b.showName()函数引用了函数a.showName,this是在函数执行时指定,showName()执行时,this指向了此时showName函数运行时的所在宿主对象b。 回顾一下 JavaScript 函数内部属性[[Call]]1F.[[Call]](thisArg, argumentsList) 一般而言JavaScript中,this 指向函数执行的当前对象,函数执行时 thisArg 传入的是此时函数的宿主对象。 再来看看下面的图:函数b.showName()和a.showName() 指向了同一个函数,当使用a.showName()方法调用时,this指向宿主对象a,使用b.showName()调用时,则this指向宿主对象b。 2.普通函数调用直接调用声明的函数 fn()。 一个栗子1234567891011var name = 'jack'var a = { name: 'bob', showName: function(){ console.log(this.name) }}var b = a.showName;b() //jack b函数是引用的函数a.showName,在b()执行的时候没有明确的宿主对象。这个时候拿出我们刚才看的ECMA规则,1F.[[Call]](thisArg, argumentsList) 没有明确宿主对象的时候 thisArg 传入的相当于 null 或者 undefined,非严格模式下 thisArg 为 null 或者 undefined 则 this 指向全局对象。在浏览器中 this 指向全局对象 window 。 再举个栗子 –立即执行函数12345678910111213var name = 'jack'var a = { name: 'bob', showName: function(){ (function(){ console.log(this.name) }()) }}var b = a.showName;b() //jack b()函数中声明了一个立即执行的匿名函数,继续回顾 JavaScript 函数内部属性[[Call]]1F.[[Call]](thisArg, argumentsList) b()函数执行时没有明确的宿主对象,thisArg 传入的是 null 或者 undefined,此时 this 在浏览器中指向 window 。 3.间接调用间接调用是指使用 apply 和 call ,bind 等方法改变函数执行当前对象。方法的第一个参数改变函数中 this 的指向。 举个栗子1234567891011var age = 18;var a = { name:'lili', age:16}var b = function(){ console.log(this.age)}b() // 18b.call(a) //16 F.call(thisObj,[arg1……]) 方法的 thisObj 和 arglist 会作为 F 内部属性 [[Call]] 的参数传入进行函数的执行操作。上面这个栗子 b 函数使用 call(a) 方法调用函数,会将第一个参数对象 a 作为 b 函数内部属性[[Call]]的第一个参数 thisArg 来执行函数。此时b 函数内部 this 指向 a,this.age 为 a.age 打印出16。 apply 和 call 都会立即调用函数,两者的区别就是 arglist 传参格式不同。call 需要把参数按顺序传递进去,而 apply 则是把参数放在数组里。123456call([thisObj[,arg1[, arg2[, [,.argN]]]]])apply([thisObj[,argArray]])-------------------------------------------fn.call(thisObj,args1,args2);//或者fn.apply(thisObj,[args1,args2]); 而 bind 是返回对应函数,不会立即调用。bind()方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。 1fn.bind(thisObj,args1,args2); //bind不会立即调用对应函数,会返回一个绑定函数 有一个地方值得注意 1234567891011121314function a(){ console.log(this)}function b(){}var c = {};a.call(); //windowa.call(null); //windowa.call(undefined); //windowa.call(1); //Numbera.call(''); //Stringa.call(true); //Booleana.call(b); //function b(){}a.call(c); //Object 回忆一下ECMA规范,非严格模式下 thisArg 为 null 或者 undefined 则 this 指向全局对象,如果thisArg为非对象类型,则会强制转型成对象类型。如上个栗子,我们非严格的浏览器环境下,传入空,null,undefined 都会指向 window,传入 String,Boolean,Number 等基础类型,会返回一个基础类型包装过的对象。 4.构造函数调用构造函数调用指使用 new 关键词,调用构造器,创建一个新对象。 举个栗子1234567function Worker(){ this.name = 'lily'; this.age = '16'; console.log(this);}Worker(); // windownew Worker(); // Worker {name: \"lily\", age: \"16\"} 函数调用 new 操作符时,会创建一个新对象,并用 this 指向它。 其他的一些疑点一般而言JavaScript中,this 指向函数执行的当前对象,函数的调用一般可以概括为上面4中情况。但是还有一些特殊的情况,影响 this 的指向。 setTimeout , setInterval 函数执行时 this 的对象时全局对象举个栗子 1234567891011var a = { name: 'lucy', showName: function(){ console.log(this); }, myName: function(){ //此时的 this 指向对象a setTimeout(this.showName,1000) //setTimeout 延迟执行 a.showName 函数中的this 指向window }} a.myName()// window setTimeout 中的延迟执行的代码中的 this 永远都指向 window。 我们可以使用 bind 方法,改变函数内 this 的指向1234567891011var a = { name: 'lucy', showName: function(){ console.log(this); }, myName: function(){ //此时的 this 指向对象a setTimeout(this.showName.bind(this),1000) //setTimeout 延迟执行 a.showName 函数中的this 指向window }} a.myName()// {name: \"lucy\", showName: ƒ, myName: ƒ} 超时调用的代码都是在全局作用域中执行的,因此函数中this的值在非严格模式下指向window对象,在严格模式下是undefined—-《JavaScript高级程序设计》 再看一个栗子,使用setTimeout(字符串代码, 延迟) 123456789101112131415161718var name = 'lili';function b (){ var name = 'jack'; setTimeout(function(){ console.log(name); //函数内声明的变量 name console.log(this.name); //this 指向window window.name lili },1000)}function c (){ var name = 'lucy'; setTimeout('console.log(name)',1000) setTimeout('console.log(this.name)',1000)}b() // jack lilic() // lili lili 对比一下 123456789101112var name = 'lili';var a = { name: 'lucy', showName: function(){ console.log(name); console.log(this.name); }, myName: function(){ setTimeout(\"this.showName()\",1000) //在window全局作用域下创建 函数this.showName(),window下无showName方法。 }}a.myName() // Uncaught TypeError: this.showName is not a function 有个共同的现象如果setTimeout(字符串代码, 延迟)使用这种方式执行的函数,默认会在 window 全局作用域下创建一个新的函数。此时this 会指向 window。 小结一下 setTimeout中的延迟执行的代码中的this永远都指向window。 setTimeout(this.showName,1000),setTimeout 参数中的this,是根据上下文来判断的。 setTimeout(“this.showName()”,1000) 执行代码如果是字符串形式的代码,默认在window全局作用域下创建一个新的函数。 eval 方法解析出的this12345678var name = 'jack'var a = { name: 'bob', showName: function(){ eval('console.log(this.name)'); //和直接执行console.log(this.name) 相同 }}a.showName() // bob eval 相当于是在当前位置填入代码。 lamda表达式(箭头函数)中 this 的指向lamda 表达式俗称肩头函数。箭头函数 this 的定义:箭头函数中的 this 是在定义函数的时候绑定,而不是在执行函数的时候绑定。这个和普通函数的 this 绑定刚好相反。 举个栗子 123456789101112var a = { name: 'lucy', showName: function(){ console.log(this.name); }, myName: function(){ setTimeout(() => { this.showName(); },1000) }}a.myName()// lucy setTimeout 执行的匿名函数为箭头函数,定义时就绑定了上下文中的 this ,即使在 setTimeout 延迟在全局作用域中执行,因为已经绑定了 this ,所以仍旧指向 a.name 。 use strict 模式对this的影响严格模式下的函数调用,回忆一下文章刚开始介绍的ECMA规则第一条:thisArg和this是一一对应。在非严格模式下 thisArg 为 null 或者 undefined 则 this 指向全局对象,而在严格模式下 thisArg 和 this 是一一对应,就意味着函数中 thisArg 为 null 或者 undefined 时,this 不会转化指向全局对象,会遵守一一对应原则,传入的什么就是什么。 对比一下下面两个栗子 1234567891011function a(){ console.log(this)}(function(){ 'use strict'; console.log(this); //undefined a.apply(null) //window a.apply(this) //window a.apply(undefined) //window})() 1234567891011'use strict';function a(){ console.log(this)}(function(){ console.log(this); //undefined a.apply(null) //null a.apply(this) //undefined a.apply(undefined) //undefined})() use strict 模式在函数内部定义,则只会影响函数内部作用域内的代码执行,不会影响定义在此函数作用域外的函数的代码执行。 总结函数执行是依靠内部属性 [[Call]],用来运行该函数。属性 [[Call]] 有一个 thisArg 的参数来决定函数中的this指向。三条规则:1.thisArg和this是一一对应。2.非严格模式下thisArg为null或者undefined则this指向全局对象。3.如果thisArg为非对象类型,则会强制转型成对象类型。函数四种调用方式: 1.调用对象方法,指向宿主函数 2.普通函数调用,指向全局变量 3.间接调用,改变函数内部指向 4.构造函数调用,指向构造后的对象 参考文章Javascript中this关键字详解深入理解函数内部原理(六从Ecma规范深入理解this","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://iszoe.github.io/categories/JavaScript/"},{"name":"基础","slug":"JavaScript/基础","permalink":"http://iszoe.github.io/categories/JavaScript/基础/"}],"tags":[]},{"title":"前端自动化测试工具","slug":"mocha","date":"2018-01-30T08:02:14.000Z","updated":"2018-02-06T07:52:11.670Z","comments":true,"path":"2018/01/30/mocha/","link":"","permalink":"http://iszoe.github.io/2018/01/30/mocha/","excerpt":"","text":"在自己编写代码的过程中,因为大部分的业务代码,很少会去写测试用例,当然也很少去关注自动化测试这一块。但是在用到公共组件,模块的时候,编写测试用例,接入自动化测试,让自己写代码的时候有了一剂强心针。 了解一下 TDD(测试驱动开发)或BDD(行为驱动开发)TDD的原理是在开发功能代码之前,先编写单元测试用例代码,测试代码确定需要编写什么产品代码。TDD中侧重点偏向开发,有利于更加专注软件设计。BDD对TDD的理念进行了扩展,主要用于测试代码是否符合客户的需求,这里的BDD更加侧重于代码的功能逻辑。BDD描述的行为就像一个个的故事(Story) 主要的测试工具 测试框架: mocha Jasmine 等等框架提供了清晰简明的语法来描述测试用例,可以对测试用例精细分组,可以清楚的发现测试抛出的error。这里后续以Mocha为例。 断言库:Should.js、chai、expect.js、Node.js原生assert库 等等断言库提供了很多语义化的方法来对值做各种各样的判断。后续以Should.js为例。 测试管理工具: KarmaKarma 不是一个测试框架,也不是一个断言库,仅仅启动一个 http server,通过你熟知的测试框架生成运行测试的HTML。这个测试工具的一个强大特性就是,它可以监控(Watch)文件的变化,然后自行执行,通过console.log显示测试结果。 Mocha简单的讲讲mocha的语法,举个栗子:1234567describe('Array', function() { describe('#indexOf()', function() { it('should return -1 when the value is not present', function() { assert.equal(-1, [1, 2, 3].indexOf(4)) }) })}) 上面就是一个简单的测试脚本,测试脚本里面包括一个或多个describe块,每个describe块应该包括一个或多个it块describe块称为“测试套件”(test suite),表示一组相关的测试。它是一个函数,第一个参数是测试套件的名称(“加法函数的测试”),第二个参数是一个实际执行的函数。 it块称为“测试用例”(test case),表示一个单独的测试,是测试的最小单位。它也是一个函数,第一个参数是测试用例的名称(“should return -1 when the value is not present”),第二个参数是一个实际执行的函数。 Mocha允许在test目录下面,放置配置文件mocha.opts 测试异步函数12345678describe(\"async\", () => { it('read book async', function (done) { book.read((err, result) => { expect(result).to.be.a('string'); done(); }) })}) 异步函数 有一个done函数。it块执行的时候,传入一个done参数,当测试结束的时候,必须显式调用这个函数,告诉Mocha测试结束了。否则,Mocha就无法知道,测试是否结束,会一直等到超时报错。 Should.jsShould.js 是一个 BDD 风格的断言库。可以使用.an, .of, .a, .and, .be, .have, .with, .is, .which (什么都不会做)等使得链式语句非常容易阅读。 KarmaKarma 会启动一个web服务器,将js源代码和测试脚本放到指定的浏览器上执行,可用于测试所有主流Web浏览器。Karma 还可以监控文件的变换,立即开始测试。 我们可以全局安装 karma-cli 来简化karma的调用,方便使用karma。1$ npm install -g karma-cli 在项目中使用1$ karma init 会自动生成Karma的配置文件 karma.conf.js ,下面介绍一些常用的配置12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576module.exports = function(config) { config.set({ // base path that will be used to resolve all patterns (eg. files, exclude) // basePath 相对目录,files和exclude里的文件路径都会相对于它 // basePath 就是相对于 karma.config.js 的所在目录(karma目录) basePath: '', // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter // 用到的测试框架,我在这里用的mocha frameworks: ['mocha'], // list of files / patterns to load in the browser //加载至浏览器的文件, //pattern:需要匹配的模式,必须有值 //watched: 默认值 true,如果autoWatch 设为true ,所有watched 属性为true的文件都会被监测变化 //served: 默认值 true,该文件是否由karma webserver 提供 //included:默认值 true,浏览器是否需要通过<script>标签引入该文件。如果你想手动引入,例如通过Require.js ,就将include设为false。 //下面第一个等同于 {pattern: 'js/**.js', watched: true, served: true, included: true} files: [ 'js/**.js', 'test/**.js' ], // list of files to exclude // 浏览器会从 files 里忽略下面的的文件,不加载它们 exclude: [ ], // preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor // 预处理,文件加载至浏览器前可以先进行处理 这里加了测试覆盖率 preprocessors: { 'js/**.js': ['coverage'], //'**/*.coffee': ['coffee'] }, //自定义Launchers customLaunchers: { Chrome_travis_ci: { base: 'Chrome', flags: ['--no-sandbox'] } }, // test results reporter to use // possible values: 'dots', 'progress' // available reporters: https://npmjs.org/browse/keyword/karma-reporter reporters: ['progress','coverage'], //覆盖率报告配置 coverageReporter: { type : 'html', dir : 'coverage/' }, // web server port port: 9876, // enable / disable colors in the output (reporters and logs) colors: true, // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG // 控制台打印 log 的等级 logLevel: config.LOG_INFO, // enable / disable watching file and executing tests whenever any file changes // 监听文件 配置在file文件夹中 autoWatch: true, // start these browsers // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher // 测试浏览器,有IE,Chrome,ChromeCanary,FireFox,Opera,Safari,PhantomJS... // # Install the launcher first with NPM: // 选择浏览器的时候要在 npm 里Install 相应的 launcher browsers: ['Chrome'], // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits // 如果是true,跑完一次所有测试就会退出。 singleRun: false, //默认情况下 karma 会加载所有 karma- 开头的 npm 组件; plugins: ['karma-*'], // Concurrency level // how many browser should be started simultaneous concurrency: Infinity })} 默认情况下所有资源由http://localhost:[PORT]/base/提供,举个栗子:123files: [ {pattern: 'test/js/name.js', watched: false, included: false, served: true}], 上面的js可以用 http://localhost:[PORT]/base/test/js/name.js 访问(注意URL里的base)你可以设置proxies 来替换路径,举个栗子123proxies: { \"/js/\": \"/base/test/js/\"}, 则可以用 http://localhost:[PORT]/js/name.js 访问。 files 里面也可以直接加载线上文件123files: [ 'https://cdn.bootcss.com/jquery/2.2.4/jquery.js'], 更多用法查看文档 在测试手机浏览器Karma 允许直接打开链接的方式捕获浏览器 http://[hostname]:[port]/ 将hostname 换成你的 IP 地址,在手机中打开(注意在同一网络中),就可以让测试运行在你的手机浏览器中,可以在控制台看见打印的测试结果。 持续集成CITravis-CI是一个持续集成构建项目,对于Github上的开源项目是免费的。gitlab 也有类似项目 Gitlab CI。 git 开源项目接入 Travis-CI首先需要在 Travis-CI 登录你的 Github 账户 在你的项目里选择需要 CI 构建的项目 在项目的根目录创建 .travis.yml 文件 git push 提交代码,就能自动构建啦 等等… .travis.yml 需要根据你的测试条件来配置123456789101112language: node_jsnode_js: - 'stable' - '4.0.0' - '5.0.0'cache: directories: - node_modulesbefore_install: - npm installscript:after_script: language定义了运行环境的语言,我们创建的是一个JavaScript项目, language 选择 node_js , 而对应的node_js可以定义需要在哪几个Node.js版本做测试,比如这里的定义,代表着会分别在最新稳定版、4.0.0、5.0.0版本的Node.js环境下做测试。 而script则是测试利用的命令,Travis-CI 默认是会执行 npm test 的,我们把自己项目开发所需要的命令都写在package.json的scripts里面,保证 npm test 可用。 而after_script则是在测试完成之后运行的命令 karma 集成到 CI想要在 Readme.md 上显示build:pass的图标点击在弹出框里选择markdown,复制下面文本框里的内容到 Readme.md 上,提交一下就能看到build:pass的图标啦 有一些值得注意的地方: yml格式需要书写正确(确保每个space都必须要正确,travis-ci提供了一个检测工具); 使用脚本的时候不要使用watch模式 对于npm安装,不需要打印安装信息(因为log太多也会构建失败) 更多问题参考文档 代码覆盖率?看看你的测试用例写的怎么样!想每次 Travis-CI 集成自动化测试之后自动生成代码覆盖率报告,在git项目中显示 Coverage Status的图标。和Travis-CI 一样需要登录 coveralls 的网站登录你的 Github 账户,选择需要(ADD REPO)生成测试报告的项目。 使用的 karma 来自动生成代码覆盖率报告需要安装下面两个包 karma-coverage karma-coveralls在karma 的配置文件中 karma.conf.js123456789101112131415preprocessors: { 'quz/*.js': ['coverage'] //add 配置需要测试覆盖率的文件},// test results reporter to use// possible values: 'dots', 'progress'// available reporters: https://npmjs.org/browse/keyword/karma-reporterreporters: ['progress', 'coverage' ,'coveralls'], //modify 需要生成哪些代码报告coverageReporter: { //覆盖率报告要如何生成,包括覆盖率页面、lcov.info、coverage.json、以及命令行里的提示 type: 'lcov', dir: './coverage/'},// web server port 这样提交到github上 Travis-CI 跑测试的时候就会自动生成测试报告提交到 coveralls 上 同样的在 coveralls 上,找到 Coverage Status的图标点击右上角EMBED 选择markdown 文本框内容复制到 Readme.md 上,就展示每次执行完成后代码覆盖率的图标啦!参考文档:聊一聊前端自动化测试","categories":[{"name":"测试","slug":"测试","permalink":"http://iszoe.github.io/categories/测试/"}],"tags":[]},{"title":"Linux01","slug":"linux01","date":"2018-01-25T04:00:33.000Z","updated":"2018-02-12T04:45:53.399Z","comments":true,"path":"2018/01/25/linux01/","link":"","permalink":"http://iszoe.github.io/2018/01/25/linux01/","excerpt":"","text":"##111111","categories":[{"name":"Linux","slug":"Linux","permalink":"http://iszoe.github.io/categories/Linux/"}],"tags":[]},{"title":"Hexo 分分钟快速建站","slug":"hexo","date":"2018-01-16T09:57:51.000Z","updated":"2018-02-12T05:25:42.241Z","comments":true,"path":"2018/01/16/hexo/","link":"","permalink":"http://iszoe.github.io/2018/01/16/hexo/","excerpt":"","text":"最近想认真的写一个博客来梳理自己的知识网络,在各路网友的推荐下,找到了 hexo + GitPage,来搭建静态博客,简单快捷,易上手,分分钟帮你建站。 你要做的主要就是三步:1.安装 hexo2.下载一个你喜欢的主题3.执行hexo new XX开始写文章 安装 hexo首先保证你电脑里的 Node.js,Git 已经安装。 使用 npm 完成 Hexo 的安装。1234$ npm install -g hexo-cli$ hexo init <folder>$ cd <folder>$ npm install 具体请参考: 这里 生成的目录结构12345678910├── .deploy #需要部署的文件├── node_modules├── public #生成的静态网页文件├── scaffolds #模板├── source #博客正文和其他源文件, 404 favicon CNAME 等都应该放在这里| ├── _drafts #草稿| └── _posts #文章├── themes #主题├── _config.yml #全局配置文件└── package.json 找到全局配置文件的 _config.yml 修改网站基本信息12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273# Site 这里需要根据你自己的网站修改哦title: Cloud # 网站首页的标题subtitle: From end to beginning # 网站副标题 (页面上展示的副标题需要在主题配置文件里设置)description: # 描述author: Zoe # 作者language: zh-CN # 语言 会影响到你主题样式里面的语言哦timezone: Asia/Shanghai # 时区# URLurl: http://iszoe.github.io # 配置你发布路径的urlroot: / # 根目录如果页面不是放在网站跟目录,需要配置rootpermalink: :year/:month/:day/:title/ # 单个文件访问的的路径permalink_defaults:# Directory 目录source_dir: source # 源文件路径文件public_dir: public # 生成的网页文件的路径文件tag_dir: tags # 标签archive_dir: archives # 归档category_dir: categories # 分类code_dir: downloads/codei18n_dir: :lang # 国际化skip_render:# Writing 设置生成博文的默认格式,不用修改new_post_name: :title.md # File name of new postsdefault_layout: post # 默认模板(post page photo draft)titlecase: false # 标题转换成大写external_link: true # 新标签页里打开连接filename_case: 0render_drafts: falsepost_asset_folder: falserelative_link: falsefuture: truehighlight: enable: true line_number: true auto_detect: false tab_replace:# Home page setting# path: Root path for your blogs index page. (default = '')# per_page: Posts displayed per page. (0 = disable pagination)# order_by: Posts order. (Order by date descending by default)index_generator: path: '' per_page: 10 order_by: -date# Category & Tag 分类和标签default_category: uncategorized #默认分类category_map:tag_map:# Date / Time format 日期时间格式## Hexo uses Moment.js to parse and display date## You can customize the date format as defined in## http://momentjs.com/docs/#/displaying/format/date_format: YYYY-MM-DDtime_format: HH:mm:ss# Pagination## Set per_page to 0 to disable paginationper_page: 10 # 每页文章数, 设置成 0 禁用分页pagination_dir: page# Extensions## Plugins: https://hexo.io/plugins/## Themes: https://hexo.io/themes/theme: hueman # 主题设置 如果更换主题需要在这里修改主题文件夹的名称# Deployment## Docs: https://hexo.io/docs/deployment.htmldeploy: type:Github # 部署类型, 本文使用Github 基本操作1234567$ hexo new \"文章名\" # 新建一个md$ hexo new 布局 \"文章名\" # 新建一个md$ hexo publish 布局 \"文章名\" #发布文章的操作,文章开始没有被分类到post$ hexo server # 运行本地服务$ hexo generate # 自动根据当前目录下文件,生成静态网页$ hexo deploy # 远程部署,同步到 GitHub$ hexo clean # 清除静态页面缓存(清除 public 文件夹) 页面信息1234567title: \"XXX\" # 博文题目date: 2014-11-21 11:25:38 # 生成时间tags: tag # 标签, 多个标签也可以使用格式 [tag1, tag2, tag3,...]categories: cat # 多级使用 [cat1,cat2,cat3]comments: false # 关闭评论---正文, 使用 Markdown 语法书写 如果不想博文在首页全部显示, 并能出现阅读全文按钮效果, 需要在你想在首页显示的部分下添加123此处及以上的内容会在首页显示<!--more-->一下是在首页隐藏的部分 更换主题hexo 会有一个默认的主题,如果你不喜欢,快去重新挑一个你爱的吧,传送门。 我最后挑选了 hueman ,所以下面以 hueman 为例,介绍一下如何更换主题。首先当然是下载主题文件,下载之后需要更改文件夹名为 hueman (可以改成任何你喜欢的名字),放在 themes 文件夹内,这时需要修改全局配置文件 _config.yml 里:1theme: hueman # 改为你的主题文件名 为了支持 hueman 内置的 search 功能需要下载 hexo-generator-json-content 1$ npm install -S hexo-generator-json-content 详细见文档 每个主题的目录下都有一个主题配置文件 _config.yml ,在这里修改所有主题相关的配置1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283# Menusmenu: Home: / # Home的路径,默认是 / # Delete this row if you don't want categories in your header nav bar Categories: # 加上这个参数,会在 menu 菜单里显示文章分类的列表 Tags: # 加上这个参数,会在 menu 菜单里显示文章标签的列表 About: /2018/01/16/hexo # 同理 如果加上一个 About ,menu 菜单会显示About按钮,如果后面配置的是一个路径,菜单会直接跳转到这个路径下#如果需要副标题 需要添加subtitle: # 副标题# Customizecustomize: logo: # 配置你自己的logo width: 158 # 你要设置的图片宽高 px height: 38 url: images/logo-name3.png # 图片放在 hueman/source/css/images 内 theme_color: '#ff7849' # 可自定义的主题色 highlight: androidstudio sidebar: left # sidebar position, options: left, right thumbnail: true # 是否开启文章缩略图 favicon: # path to favicon social_links: # for more icons, please see http://fontawesome.io/icons/#brand #twitter: / #facebook: / #google-plus: / github: https://github.com/isZoe #weibo: / #rss: /# Widgetswidgets: - recent_posts - category - archive - tag - tagcloud - links# Searchsearch: insight: true # you need to install `hexo-generator-json-content` before using Insight Search swiftype: # enter swiftype install key here baidu: false # you need to disable other search engines to use Baidu search, options: true, false# Commentcomment: disqus: #hexo-theme-hueman # enter disqus shortname here duoshuo: # enter duoshuo shortname here youyan: # enter youyan uid here facebook: # enter true to enable isso: # enter the domain name of your own comment isso server eg. comments.example.com changyan: appId: # enter the changyan appId here appKey: # enter the changyan appKey here on: # enter true to enable valine: # Valine Comment System https://github.com/xCss/Valine on: # enter true to enable valine appId: # enter the leancloud application appId here appKey: # enter the leancloud application appKey here notify: # enter true to enable <Mail notifier> verify: # enter true to enable <Validation code> placeholder: Just Do It # enter the comment box placeholder# Shareshare: default # options: jiathis, bdshare, addtoany, default# Pluginsplugins: lightgallery: true # options: true, false justifiedgallery: true # options: true, false google_analytics: # enter the tracking ID for your Google Analytics baidu_analytics: # enter Baidu Analytics hash key bing_site_verification: # enter Bing verification key here mathjax: false # options: true, false# Miscellaneousmiscellaneous: open_graph: # see http://ogp.me fb_app_id: fb_admins: twitter_id: google_plus: links: #Hexo: http://hexo.io 此时你配置好了你的主题文件,大功告成,就可以爽爽的写博客了。 hueman 主题的一些小修改,和基础用法配置了主题文件之后,还是对它有一些小不满,于是做了一些细微的改动,还有一些文章配合主题的基本用法,也贴了出来。 文章多级分类想在侧边栏显示多级的文章分类,记得在 _config.yml 中开启Categories:。在文章.md头部加上1categories: [cat1 ,cat2] # 多级可使用数组的形式,侧边栏的分类 hueman 默认只支持到两级 如果想支持更多级分类,去 hueman/layout/widget/category.ejs1234567...<%- list_categories(site.categories,{ depth: 2, // 把depth 参数修改成你想要的层级数 style: 'list', show_count: true,}) %>... 文章缩略图human 是支持文章缩略的一个主题,如果要使用文章缩略图,首选需要在主题配置 _config.yml 中开启thumbnail: true。默认会选取文章的第一张图作为缩略图,需要自定义的话,去文章.md在头部加上12thumbnail: 'css/images/hexo.jpg' # 图片放在 hueman/source/css/images 内 # 或者直接引用外链 如果把图片放在 hueman/source/css/images 内,直接写上面这个路径引用,http://localhost:4000/预览时会发现,在 page 的页面里面 sidebar 的预览图就会因为路径错误显示不正确。human 的文档里面使用了一个外链的图片,所以如果想图省事可以直接使用一个外链,但是如果想试用站内链接,我们需要对主题文件做一点小小的处理。我参考了一下 页面 logo 的处理方式,需要打开 hueman/layout/common/thumbnail.ejs1234567891011....<% var thumbnailUrl = thumbnail(post) %><% if (thumbnailUrl) { %> // -- <span style=\"background-image:url(<%- url_for(post.thumbnail) %>)\" alt=\"<%= post.title %>\" class=\"thumbnail-image\"></span> // 将 background-image:url(<%- thumbnailUrl %>) url的路径修改成以下参数 // add <span style=\"background-image:url(<%- url_for(post.thumbnail) %>)\" alt=\"<%= post.title %>\" class=\"thumbnail-image\"></span><% } else { %> <span class=\"thumbnail-image thumbnail-none\"></span><% } %>.... 这样就可以把图片保存在本地啦~ 在文章中插入图片提供两种插入图片的方法 我们可以在全局配置文件 _config.yml1post_asset_folder: true 这样我们每次new,一个文章的时候,就会同时生成一个同名的文件夹,可以在里面放入图片,随后在文章中引用1{% img XXX.jpg tit图片标题 替换文字 %} 因为自己在文章中插入的图片不多,所以直接在 source 里面新建了一个文件夹 img ,把需要引用的图片放在了img/内,随后在文章中以绝对路径引用1{% img /img/XXX.jpg tit图片标题 替换文字 %} 添加 favicon在主题配置文件 _config.yml 中添加你的ico1favicon: /xrz.ico 注意将 .ico 文件放在主题文件夹的 source/ 目录下 source/xrz.ico 。 更换代码块样式这个只需要在主题配置文件 _config.yml 中修改 highlight 的值 1highlight: github # 更换你想要的代码主题样式 可以选择哪些样式呢,我们可以去 hueman/source/css/_highlight 文件夹,里面有很多 .styl 文件,文件名就是可选值。 简单的整理了一下自己的笔记,持续更新中.. 参考链接Hexo安装和配置","categories":[{"name":"Tool","slug":"Tool","permalink":"http://iszoe.github.io/categories/Tool/"}],"tags":[]},{"title":"Markdown 语法🐤","slug":"markdown","date":"2018-01-16T09:57:51.000Z","updated":"2018-02-23T17:20:19.584Z","comments":true,"path":"2018/01/16/markdown/","link":"","permalink":"http://iszoe.github.io/2018/01/16/markdown/","excerpt":"","text":"上标aaa1上标<sup>aaa</sup> 标记区块引用 类似 email 中用的引用方式 1> 类似 email 中用的引用方式 行间代码code1行间代码`code` 文字加粗文字加粗文字加粗对比文字加粗文字加粗 12345**文字加粗**__文字加粗__对比*文字加粗*_文字加粗_ 空一行123aaaa&nbsp;aaaa","categories":[{"name":"Tool","slug":"Tool","permalink":"http://iszoe.github.io/categories/Tool/"}],"tags":[]}]}