疯狂的技术宅

疯狂的技术宅 查看完整档案

北京编辑  |  填写毕业院校  |  填写所在公司/组织 blog.yidengxuetang.com/ 编辑
编辑

资深技术宅,爱好广泛,兴趣多变。博览群书,喜欢扯淡。十八种语言样样稀松。想要了解更多,请关注微信公众号:充实的脑洞

个人动态

疯狂的技术宅 发布了文章 · 4月7日

在JavaScript中实现队列

作为一个优秀的程序猿需要具有知识的广度。首先是要了解你选择的编程语言。如果你正在阅读这篇文章,最有可能使用 JavaScript。

然而在熟悉了编程语言之后,你还必须了解如何根据任务轻松且有效地操纵数据。这就是数据结构的用武之地。

在本文中,我将描述队列数据这个结构:它都有哪些操作以及在 JavaScript 中怎样实现。

1. 队列数据结构

如果你喜欢四处旅行,肯定在火车站经历过检票这道手续。如果有很多人要坐火车,那么很自然地会形成一个队列。刚进入车站的人加入队列。另一边刚刚通过检票的人从队列中走出。这就是队列的一个例子,与队列数据结构的操作方式相同。

队列是一种遵循先入先出(FIFO)规则的数据结构。第一个进入队列中的项目(输入)是第一个出队(输出)的。

队列有2个指针:队首和队尾。最先进入队列进行排队的项目位于队首,而最后进入队列的项目位于队尾

回顾车站的例子,第一个检票的是在队列的队首。刚进入队列的人在队尾。

image.png

从更高的层面来看,队列是一种允许你按照先后顺序处理项目的数据结构。

2. 队列的操作

队列支持 2 个主要操作:入队(enqueue)出队(dequeue),另外还有 peek 和 length 操作。

2.1 入队操作

入队操作在队列的尾部插入项目,使其成为队列的队尾。

image.png

上图中的入队操作在队尾插入了 8,之后 8 成为队列的队尾。

queue.enqueue(8);

2.2 出队操作

出队操作取出队列中第一个项目,此时队列中的下一个项目成为队首。

image.png

在上图中,出队操作返回项目7并从队列中删除。 出队之后之后,项目 2 成为新的队首。

queue.dequeue(); // => 7

2.3 Peek 操作

Peek 操作读取队首的项目,但是不改变队列。

image.png

上图中 7 是队首。 peek 操作只需返回队首 7 但是不修改队列。

queue.peek(); // => 7

2.4 length

length 操作返回队列中包含项目的数量。

image.png

上图中的队列有 4 项:462 和。7。结果队列长度为 4

queue.length; // => 4

2.5 队列操作的时间复杂度

关于队列所有操作的重点:enqueue,dequeue,peek 和 length 必须以常数时间复杂度 O(1) 执行。

常数时间复杂度 O(1) 意味着无论队列大小如何(不管是有 10 个还是 100 万个项目),这些操作都必须在相对一致的时间内执行。

3. 用 JavaScript 实现队列

来看一下怎样在保证所有操作必须以常数时间复杂度O(1) 要求实现队列这种数据结构。

class Queue {
  constructor() {
    this.items = {};
    this.headIndex = 0;
    this.tailIndex = 0;
  }

  enqueue(item) {
    this.items[this.tailIndex] = item;
    this.tailIndex++;
  }

  dequeue() {
    const item = this.items[this.headIndex];
    delete this.items[this.headIndex];
    this.headIndex++;
    return item;
  }

  peek() {
    return this.items[this.headIndex];
  }

  get length() {
    return this.tailIndex - this.headIndex;
  }
}

const queue = new Queue();

queue.enqueue(7);
queue.enqueue(2);
queue.enqueue(6);
queue.enqueue(4);

queue.dequeue(); // => 7

queue.peek();    // => 2

queue.length;    // => 3

const queue = new Queue() 是创建队列的实例。

queue.enqueue(7) 方法将 7 存入队列中。

queue.dequeue() 从队列中取出一个头部项目,而 queue.peek() 只读队首项。

最后的 Queue.Length 显示队列中还有多少个项目。

关于实现:在 Queue 类中,普通对象 this.Items 将队列的项目通过数值索引保持。 队首项的索引由 Where.HeadInex 跟踪,队尾项由 this.tailIndex 跟踪。

队列方法的复杂度

Queuequeue()dequeue()peek()length() 方法中存在:

  • 属性访问器(如:this.items[this.headIndex]),
  • 执行算数操作(如:this.headidex++

这些方法的时间复杂度是恒定的时间 O(1)

4. 总结

队列是一种遵循先入先出(FIFO)规则的的数据结构。

队列有 2 个主要操作:入队和出队。 另外,队列可以有辅助操作,例如 peek 和 length。

所有队列操作都必须以常数时间 O(1) 执行。

挑战一下:改进 dequeue()peek() 方法,当在空队列上执行时会抛出错误。

173382ede7319973.gif


本文首发微信公众号:前端先锋

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章


欢迎继续阅读本专栏其它高赞文章:


查看原文

赞 7 收藏 5 评论 0

疯狂的技术宅 收藏了文章 · 4月6日

一个能够确定 this 值的算法

每个 JavaScript 程序猿,包括我自己,都一直在努力了解 this 关键字在代码中的真正身份。

我设计了一个通用算法,可以帮你在任何情况下确定 this 关键字的值。虽然我尽可能的使算法容易看懂,但还是建议你多看几遍并理解相关术语。

另外还用了几个例子展示怎样用这个算法一步一步的对 this 进行评估,最后你自己亲自试一试。

1. this 算法

把算法定义为 ThisValueOfFunction(func, invocationType) ,返回值为在以 invocationtype 方式调用函数 func 时的 this 值:

ThisValueOfFunction(func, invocationType):

  1. 如果 func 是一个箭头函数,那么

    1. 如果 func 是在最外面的作用域 中定义的,那么返回 globalObject
    2. 否则

      1. SuffeFuncFunc外部函数
      2. 返回 ThisValueOfFunction(outerFunc, outerInvocationType)
  2. 如果 funcoriginFunc 函数的绑定函数,那么

    1. thisArgFunc = OriginFunc.bind(thisarg) 的参数
    2. 返回 thisArg
  3. 如果 funcsomeclass 中的 constructor() 方法,那么

    1. instanceinstance = new SomeClass() 的实例
    2. 返回 instance
  4. 如果 func 是一个常规函数,那么

    1. 如果 invocationtype作为构造函数,那么

      1. newObject 是新构造的对象 newObject = new func()
      2. 返回 newObject
    2. 如果 invocationtype是间接调用的,那么

      1. thisArgfunc.call(thisArg)func.apply(thisArg) 的参数
      2. 返回 thisArg
    3. 如果 invocationtype方法,那么

      1. object 是在 object.func() 上调用 func 的对象
      2. 返回 object
    4. 如果 invocationtype常规的,那么

      1. 如果启用了严格的模式,那么返回 undefined
      2. 否则返回 globalObject

1.1 算法中使用的术语

这个算法使用了大量的 JavaScript 术语。如果你不熟悉某些东西,先看下面的解释。

  • 箭头函数

    箭头函数是使用粗箭头语法 => 定义的函数。 箭头函数示例:

    const sum = (number1, number2) => {
      return number1 + number2;
    }
  • 绑定函数

    绑定函数是通过在函数上调用方法 myFunc.bind(thisArg, arg1, ..., argN)创建的函数。 绑定函数的示例:

    function originalFunction() {
      // ...
    }
    
    const boundFunction = originalFunction.bind({ prop: 'Value' });
  • 常规函数

    常规函数是用 function 关键字或在对象上定义的简单 JavaScript 函数。 常规函数的示例:

    function regularFunction(who) {
      return `Hello, ${who}!`;
    }
    
    const object = {
      anotherRegularFunction(who) {
        return `Good bye, ${who}!`
      }
    };
  • constructor()

    constructor()class 内部的一种特殊方法,用于初始化类实例。

    class SomeClass() {
      constructor(prop) {
        this.prop = prop;
      }
    }
  • 最外部的作用域

    最外部的作用域是没有外部作用域的最顶级作用域。

    // 最外部的作用域
    let a = 1;
    
    function someFunction() {
      // someFunction() 的作用域
      // 这里不是最外部的作用域
      let b = 1;
    }
  • 外部函数
    外部函数在其作用域内包含另一个函数。

    // outerFunction() 是 myFunction() 的外部函数
    function outerFunction() {
      function myFunction() {
        //...
      }
    }
  • 全局对象

    全局对象是在全局作用域内始终存在的对象。 window 是浏览器环境中的全局对象,在 Node 环境中是 global

  • 调用
    函数的调用只是使用一些参数来调用该函数。

    function sum(number1, number2) {
      return number1 + number2;
    }
    sum(1, 3);           // 调用
    sum.call({}, 3, 4);  // 调用
    sum.apply({}, 5, 9); // 调用
    
    const obj = {
      method() {
        return 'Some method';
      }
    };
    obj.method(); // 调用
    
    class SomeClass {
      constructor(prop) {
        this.prop = prop;
      } 
    }
    const instance = new SomeClass('Value'); // 调用
  • 构造函数调用
    使用 new 关键字调用函数或类时,将发生构造函数调用。

    function MyCat(name) {
      this.name = name;
    }
    const fluffy = new MyCat('Fluffy'); // 构造函数调用
    
    class MyDog {
      constructor(name) {
        this.name = name;
      }
    }
    const rex = new MyDog('Rex'); // 构造函数调用
  • 间接调用
    使用 func.call(thisArg, ...)func.apply(thisArg, ...) 方法调用函数时,会发生间接调用。

    function sum(number1, number2) {
      return number1 + number2;
    }
    
    sum.call({}, 1, 2);  // 间接调用
    sum.apply({}, 3, 5); // 间接调用
  • 方法调用
    当在属性访问器表达式 object.method() 中调用函数时,将发生方法调用。

    const object = {
      greeting(who) {
        return `Hello, ${who}!`
      }
    };
    
    object.greeting('World');    // 方法调用
    object['greeting']('World'); // 方法调用
  • 常规调用
    只用函数参数变量调用 func(...) 时,会发生常规调用。

    function sum(number1, number2) {
      return number1 + number2;
    }
    
    sum(1, 4); // 常规调用
  • 严格模式
    严格模式是对运行 JavaScript 代码有特殊限制的一种特殊模式。 通过在脚本的开头或函数作用域的顶部添加 use strict 指令来启用严格模式。

2.例子

例 1

const myFunc = () => {
  console.log(this); // logs `window`};

myFunc();

ThisValueOfFunction(myFunc, “常规的”)

myfunc 是箭头函数:从而在算法中匹配情况 1。同时 myFunc 在最外面的作用域内定义,匹配情况 1.1

算法 1.1 中返回 globalObject 意思是 myFunc 中的 this 值为全局对象 window(在浏览器环境中)。

例 2

const object = {
  method() {
    console.log(this); // logs { method() {...} }  } 
};

object.method();

ThisValueOfFunction(object.method, “作为方法调用”)

method() 同时是 object 的属性,是常规函数。与算法的情况 4 匹配。

object.method() 是一种方法调用,因为是属性访问的,送一因此与 4.3 匹配。

然后,根据 4.3method() 方法中的 this 等于方法的拥有者 (object.method()) — object

例 3

function MyCat(name) {
  this.name = name;

  const getName = () => {
    console.log(this); // logs { name: 'Fluffy', getName() {...} }    return this.name;
  }

  this.getName = getName;
}

const fluffy = new MyCat('Fluffy');
fluffy.getName();

ThisValueOfFunction(getName, “作为方法调用”)

getName() 是一个箭头函数,所以符合算法的情况 1;因为 mycatgetName()的外部函数,然后与 1.2 匹配。

分支 1.2.2thisgetName() 箭头函数内部的值等于外部函数的值 MyCat

所以让我们在 MyCat 函数上运行算法 ThisValueOfFunction(MyCat, "做为构造函数")

ThisValueOfFunction(MyCat, “作为构造函数”)

MyCat 是常规函数,所以跳转到算法的分支 4

因为 MyCat 做为构造函数调用 new MyCat('Fluffy'),符合分支 4.1。最后根据 4.1.14.1.2thisMyCat 中等于构造的对象:fluffy

然后,返回箭头函数后符合 1.2.2,在 getname() 中的 this 等于 mycatthis,最终结果为 fluffy

3. 练习

要理解这个算法,最好自己亲自试试。下面是 3 个练习。

练习 1

const myRegularFunc = function() {
  console.log(this); // logs ???};

myRegularFunc();

如何确定 myRegularFunc() 中的 this 值?写出你的判断步骤。

练习 2

class MyCat {
  constructor(name) {
    this.name = name;
    console.log(this); // logs ???  }
}

const myCat = new MyCat('Lucy');

如何确定 new MyCat('Lucy') 中的 this 值?写出你的判断步骤。

练习3

const object = {
  name: 'Batman',

  getName() {
    const arrow = () => {
      console.log(this); // logs ???      return this.name;
    };

    return arrow();
  };
}

object.getName();

如何确定 arrow() 中的 this 值?写出你的判断步骤。

173382ede7319973.gif


本文首发微信公众号:前端先锋

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章


欢迎继续阅读本专栏其它高赞文章:


查看原文

疯狂的技术宅 收藏了文章 · 4月6日

ES2018 中 4 个有用的功能

ES2018 规范引入了四个新功能。这些功能包括异步迭代,rest/spread 属性,Promise.prototype.finally() 和正则表达式改进。本问将帮你了解这些 ES2018 功能的工作原理及使用方法。

异步迭代

异步迭代是讨论的比较少 ES2018 功能之一。虽然还有很多关于 ES2018 其他功能的讨论,但几乎没有关于异步迭代这方面的内容。通过异步迭代,我们可以得到异步的可迭代对象和迭代器。

这意味着你可以把 await 关键字与 for…of 循环放在一起使用。你可以用这些循环对可迭代对象进行迭代。可迭代对象的包括数组、map、set,NodeList,函数的 arguments 参数,TypedArray 等。

在 ES2018 之前,for...of 循环是同步的。如果你试着迭代涉及异步操作的可迭代对象并 await,则无法正常工作。循环本身会保持同步,基本上忽略 await ,并在其内部的异步操作可以完成之前完成迭代。

// 下面的代码在 ES2018 之前不起作用,因为循环保持同步。
// 创建一个异步函数:
async function processResponses(someIterable) {
  // 对可迭代对象进行迭代
  for (let item of someIterable) {
    // 通过异步操作处理项目,例如promise:
    await processItem(item)
  }
}

同时 for...of 循环也可以与异步代码一起使用。也就是说可以在遍历可迭代对象时执行一些异步操作。for...of 循环将会是异步的,让你能够等待异步操作完成。

需要记住的是在哪里使用 await 关键字。不需要把它放进循环体中,应该将其放在for...of关键字中 for 的后面。现在当你用 next() 方法获取异步迭代器的下个值时,将会得到一个 Promise。如果你想了解更多信息,可以在 GitHub 上去看看(https://github.com/tc39/propo...)。

// 创建一个异步函数:
async function processResponses(someIterable) {
  //遍历可迭代对象并等待异步操作的结果
  for await (let item of someIterable) {
    processItem(item)
  }
}

Rest/Spread 属性

restspread 并不是真正的新功能。两者都是在 ES6 中作为新的运算符引入的,它们很快就开始流行起来。可以说 JavaScript 程序员喜欢它们。唯一的问题是它们只能用在数组和参数上,不过 ES2018 把这两个功能引入了对象中。

restspread 运算符的语法都非常简单,由三个点(...)组成。这些点后面是要在其上使用 restspread 运算符的对象。接下来简单的讨论一下两者的工作原理。

对象的 rest 运算符

rest 运算符使你可以将对象的所有剩余对象属性属性提取到新对象上。要注意这些属性必须是可枚举的。如果你已经对某些属性使用了分解,那么 rest 运算符会只提取剩余的属性。

// Rest example:

const daysObj = {
  one: 'Monday',
  two: 'Tuesday',
  three: 'Wednesday',
  four: 'Thursday',
  five: 'Friday'
}

//使用解构将变量的前两个属性分配给变量。
//然后,使用rest将其余属性分配给第三个变量。
const { one, two, ...restOfDays } = daysObj
// rest 仅提取 "three", "four" 和 "five" 
// 因为我们已经提取了 "one" 和 "two" 

console.log(one)
// Output:
// 'Monday'

console.log(two)
// Output:
// 'Tuesday'

console.log(restOfDays)
// Output:
// { three: 'Wednesday', four: 'Thursday', five: 'Friday' }

如果要对对象使用 rest 运算符,需要记住两点:首先,只能用一次,除非把它用在嵌套对象上。其次,必须在最后使用。这就是为什么在上面的例子中,在解构前两个属性之后而不是之前看到它的原因。

// 这行代码不起作用,因为把 rest 运算符用在了最前面:
const { ...all, one, two } = { one: 1, two: 2, three: 3 }

//这行能起作用:
const { one, two, ...all } = { one: 1, two: 2, three: 3 }

// 这行不起作用,因为同一级别上有多个 rest 运算符:
const { one, ...some, ...end } = { /* some properties */ }

// 这行能起作用,在多个级别上的多个 rest 运算符:
const { one, {...secondLevel }, ...firstLevel } = { /* some properties */ }

对象的 spread 运算符

spread 运算符的作用是可以通过插入另一个对象的所有属性来创建新对象。 Spread 运算符还允许你从多个对象插入属性。也可以把这个运算符与添加新属性结合使用。

// Spread example:
const myOriginalObj = { name: 'Joe Doe', age: 33 }
// 用 spread 运算符创建新对象:
const myNewObj = { ...myOriginalObj }

console.log(myNewObj)
// Output:
// { name: 'Joe Doe', age: 33 }


// 添加属性的例子:
const myOriginalObj = { name: 'Caesar' }
// 用 spread 运算符创建新对象
// 并添加新的属性“genre”:
const myNewObj = { ...myOriginalObj, genre: 'Strategy' }

console.log(myNewObj)
// Output:
// {
//   name: 'Caesar',
//   genre: 'Strategy'
// }


// Spread 运算符并合并两个对象:
const myObjOne = { title: 'Eloquent JavaScript' }
const myObjTwo = { author: 'Marijn Haverbeke' }

const myNewObj = { ...myObjOne, ...myObjTwo }

console.log(myNewObj)
// Output:
// {
//   title: 'Eloquent JavaScript',
//   author: 'Marijn Haverbeke'
// }

当从多个对象插入属性并添加新属性时,顺序很重要

我来解释一下,假设你要用 spread 运算符基于两个现有对象创建一个新对象。第一个已有对象中包含具有某些值的属性 title。第二个对象也包含属性 title,但是值不一样。最终到底取哪个 title

答案是最后一个。如果对第一个对象使用 spread 运算符,然后再对第二个对象使用,则第二个 title 会生效。如果你将 spread 运算符永在第二个对象上,则第一个 title 会生效。

// Spread 运算符并合并两个对象:
const myObjOne = {
  title: 'Eloquent JavaScript',
  author: 'Marijn Haverbeke',
}

const myObjTwo = {
  title: 'You Don\'t Know JS Yet',
  language: 'English'
}

// 用 spread 运算符通过组合 “myObjOne” 和 “myObjTwo” 创建新对象
// 注意:“myObjTwo” 中的 “title” 会将覆盖 “myObjTwo” 的 “title”
// 因为“ myObjTwo”排在最后。
const myNewObj = { ...myObjOne, ...myObjTwo }

console.log(myNewObj)
// Output:
// {
//   title: "You Don't Know JS Yet",
//   author: 'Marijn Haverbeke',
//   language: 'English'
// }


// 注意:“myObjOne” 中的 “title” 将覆盖 “myObjTwo” 的 “title”
const myNewObj = { ...myObjTwo, ...myObjOne }

console.log(myNewObj)
// Output:
// {
//   title: 'Eloquent JavaScript',
//   language: 'English',
//   author: 'Marijn Haverbeke'
// }

Promise.prototype.finally()

一开始有两个用于 Promise 的回调函数。其中一个是 then(),在实现诺 Promise 执行。第二个是catch(),在 promise 被拒绝或 then() 抛出异常时执行。 ES2018 增加了用于 Promise 的第三个回调函数 finally()

每次完成 promise 时,都会执行 finally() 回调,不管 promise 是否完成。这个回调的一般用于执行应始终发生的操作。例如关闭模态对话框、关闭数据库连接或进行某些清理。

// finally() example:
fetch()
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.log(error))
  //最后做点什么:
  .finally(() => console.log('Operation done.'))

对正则表达式的改进

ES2018 还对正则表达式功能进行了的一些改进。这些改进包括 s(dotAll) 标志,后行断言,命名捕获组和 unicode 属性转义。

s(dotAll)

首先是 s(dotAll) 。与点(.)不同,s(dotAll) 允许对换行符及表情符号进行匹配。

// s(dotAll) example:
/hello.world/.test('hello\nworld')
// Output:
// false

/hello.world/s.test('hello\nworld')
// Output:
// true

后行断言

在ES2018之前,JavaScript仅支持先行断言。先行断言用于基于其后的文本来匹配模式。在 ES2018 中增加了对后行断言的支持。通过它可以基于模式之前的文本模式来进行匹配。后行断言的语法为 ?<=

// 后行断言例子:
/(?<=green) apple/.test('One red apple is on the table.')
// Output:
// false

/(?<=green) apple/.test('One green apple is on the table.')
// Output:
// true

断言后面也有一个反向的回溯。仅当子字符串之前没有断言时,此断言才与模式匹配。对后行断言取反操作的语法是 ?<!

/(?<!green) apple/.test('One red apple is on the table.')
// Output:
// true

/(?<!green) apple/.test('One green apple is on the table.')
// Output:
// false

命名捕获组

另一个被 ES2018 引入到正则表达式的好功能是命名捕获组。命名捕获组的语法为 ?<some_name>

const date_pattern = /(?<day>\d{2})\/(?<month>\d{2})\/(?<year>\d{4})/
const result = date_pattern.exec('11/12/2021')

console.log(result)
// Output:
// [
//   '11/12/2021',
//   '11',
//   '12',
//   '2021',
//   index: 0,
//   input: '11/12/2021',
//   groups: [Object: null prototype] { day: '11', month: '12', year: '2021' }
// ]

console.log(result.groups.day)
// Output:
// '11'

console.log(result.groups.month)
// Output:
// '12'

console.log(result.groups.year)
// Output:
// '2021'

Unicode 属性转义

每个 unicode 字符都有许多属性。例如:空白字符,大小写,字母,ASCII,表情符号等。现在你可以在正则表达式中访问这些属性了。

要使用这个功能需要做两件事。首先必须使用 /u 标志。这个标志告诉 JavaScript 你的字符串是一系列 Unicode 代码点。第二是使用 \p{}。你要检查的属性位于大括号之间,反之则用 \P{}

// 用俄语创建一个字符串(西里尔字母):
const myStrCyr = 'Доброе утро'

//创建英文字符串(拉丁字母):
const myStrLat = 'Good morning'

//测试“ myStrCyr”是否包含西里尔字符:
/\p{Script=Cyrillic}/u.test(myStrCyr) // true

//测试“ myStrLat”是否包含西里尔字符:
/\p{Script=Cyrillic}/u.test(myStrLat) // false

// 测试“myStrLat” 是否包含西里尔字符:
/\p{Script=Latin}/u.test(myStrCyr) // false

// 测试“myStrLat” 是否包含拉丁语字符:
/\p{Script=Latin}/u.test(myStrLat) // true

173382ede7319973.gif


本文首发微信公众号:前端先锋

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章


欢迎继续阅读本专栏其它高赞文章:


查看原文

疯狂的技术宅 收藏了文章 · 4月6日

10分钟用Hugo打造一个静态网站

什么是静态网站生成器?

简简单的说,静态网站生成器会获取你的内容,并将其应用于模板,然后生成基于 HTML 的静态网站。非常适合个人博客。

好处:

  • 快速部署
  • 安全(无动态内容)
  • 快速迅速
  • 使用简单
  • 能够进行版本控制

那么,都有哪些流行的静态网站生成器呢?

  • Gatsby (React/JS)
  • Hugo (Go)
  • Next.js (React/JS)
  • Jekyll (Ruby)
  • Gridsome (Vue/JS)

这些项目在 GitHub 上的知名度非常高。

Hugo 是什么?

其官方网站号称 Hugo 是世界上最快的静态网站引擎。

image.png

Hugo 是用 Go 语言编写的,它还有非常丰富的主题系统。

安装 Hugo

Mac:

brew install hugo

Linux:

sudo apt-get install hugo
或者
sudo pacman -Syu hugo

然后执行下面的命令检查是否安装成功:

hugo version

使用Hugo

创建一个新项目:

hugo new site my-project

下载一个主题。可以在 https://themes.gohugo.io/ 找到更多你喜欢的主题。

cd my-project
git init
git submodule add https://github.com/budparr/gohugo-theme-ananke.git themes/ananke

将主题添加到配置文件。

echo 'theme = "ananke"' >> config.toml

添加一篇文章。

hugo new posts/my-first-post.md

它看上去应该像这样:

---
title: "My First Post"
date: 2021-03-10T18:37:11+08:00
draft: true
---

Hello World!

可以在这里给文章添加添加更多属性配置(标签,描述,类别,作者)。

可以在 https://gohugo.io/content-man... 了解更多的配置项。

看看效果:

hugo server -D

在浏览器中打开 http://localhost:1313 就能看到你的网站了。

Hugo 的目录结构

.
├── archetypes
├── assets (not created by default)
├── config.toml
├── content
├── data
├── layouts
├── static
└── themes
  • archetypes:Archetypes 是内容模板文件,其中包含预配置的首选项(日期、标题、草稿等)。可以用自定义的预配置前端字段创建新的原型。
  • assets:Assets 文件夹存储所有文件,这些文件由 Hugo Pipes 处理(例如 CSS/Sass 文件)。默认不创建这个目录。
  • config.toml:Hugo 使用 config.tomlconfig.yamlconfig.json(可以在网站根目录中找到)作为默认网站配置文件。除了单独的配置文件之外,你还可以使用 config directory 来分隔不同的环境。
  • content:所有内容文件放在这里。顶级文件夹计为内容部分。如果你有 devopsnodejs 部分,那么你需要有 content/devops/first-post.mdcontent/nodejs/second-post.md 目录。
  • data:这里用来存储配置文件,Hugo 会在生成你网站时用到。
  • layouts:以 .html 文件的形式存储模板。有关更多信息,请参见 Styling 部分。
  • static:存储所有静态内容:图片、CSS、JavaScript 等。当 Hugo 创建你的网站时,static 目录中的所有资源均按原样复制。
  • themes:你所选择的 Hugo 主题。

修改静态网站的样式

我们在之前应用了一个主题。现在,如果我们检查 themes 文件夹,可以看到样式文件。

但是要当心!

千万不要直接编辑这些文件!

应该将主题目录结构复制到 layouts 文件夹。

假设我要将自定义 CSS 应用于主题。

主题有一个 themes/theme-name/layouts/partials 文件夹,可以在其中找到一些HTML模板(header.htmlfooter.html)。现在我们将编辑 header.html 模板,将内容从这个文件复制到 layouts/partials/header.html 中,并注意在主题 layouts 根目录中创建与主题相同的目录结构。

layouts/partials/header.htmlss
themes/theme-name/layouts/partials/header.html

创建一个自定义CSS文件: static/css/custom-style.css,然后把自定义 CSS 文件添加到 config.toml 中:

[params]
 custom_css = ["css/custom-style.css"]

打开 layouts/partials/header.html

将这段代码添加到 <head> 标签内:

{{ range .Site.Params.custom_css -}}
   <link rel="stylesheet" href="{{ . | absURL }}">
{{- end }}

现在,就可以覆盖主题中所应用的 CSS 类。

构建静态网站

在项目的根目录下执行 hugo 命令:

>>> hugo
                   | EN  
-------------------+-----
  Pages            | 14  
  Paginator pages  |  0  
  Non-page files   |  0  
  Static files     |  1  
  Processed images |  0  
  Aliases          |  6  
  Sitemaps         |  1  
  Cleaned          |  0  

Total in 74 ms

执行成功后,会生成一个public 目录,这个目录中的内容就是我们静态网站的所有内容。

然后就可以托管到 GitHub 或 OSS 中了。

Hugo 还提供了更多的内容,可以到官方文档查看:https://gohugo.io/documentation/

173382ede7319973.gif


本文首发微信公众号:前端先锋

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章


欢迎继续阅读本专栏其它高赞文章:


查看原文

疯狂的技术宅 发布了文章 · 4月5日

ES2018 中 4 个有用的功能

ES2018 规范引入了四个新功能。这些功能包括异步迭代,rest/spread 属性,Promise.prototype.finally() 和正则表达式改进。本问将帮你了解这些 ES2018 功能的工作原理及使用方法。

异步迭代

异步迭代是讨论的比较少 ES2018 功能之一。虽然还有很多关于 ES2018 其他功能的讨论,但几乎没有关于异步迭代这方面的内容。通过异步迭代,我们可以得到异步的可迭代对象和迭代器。

这意味着你可以把 await 关键字与 for…of 循环放在一起使用。你可以用这些循环对可迭代对象进行迭代。可迭代对象的包括数组、map、set,NodeList,函数的 arguments 参数,TypedArray 等。

在 ES2018 之前,for...of 循环是同步的。如果你试着迭代涉及异步操作的可迭代对象并 await,则无法正常工作。循环本身会保持同步,基本上忽略 await ,并在其内部的异步操作可以完成之前完成迭代。

// 下面的代码在 ES2018 之前不起作用,因为循环保持同步。
// 创建一个异步函数:
async function processResponses(someIterable) {
  // 对可迭代对象进行迭代
  for (let item of someIterable) {
    // 通过异步操作处理项目,例如promise:
    await processItem(item)
  }
}

同时 for...of 循环也可以与异步代码一起使用。也就是说可以在遍历可迭代对象时执行一些异步操作。for...of 循环将会是异步的,让你能够等待异步操作完成。

需要记住的是在哪里使用 await 关键字。不需要把它放进循环体中,应该将其放在for...of关键字中 for 的后面。现在当你用 next() 方法获取异步迭代器的下个值时,将会得到一个 Promise。如果你想了解更多信息,可以在 GitHub 上去看看(https://github.com/tc39/propo...)。

// 创建一个异步函数:
async function processResponses(someIterable) {
  //遍历可迭代对象并等待异步操作的结果
  for await (let item of someIterable) {
    processItem(item)
  }
}

Rest/Spread 属性

restspread 并不是真正的新功能。两者都是在 ES6 中作为新的运算符引入的,它们很快就开始流行起来。可以说 JavaScript 程序员喜欢它们。唯一的问题是它们只能用在数组和参数上,不过 ES2018 把这两个功能引入了对象中。

restspread 运算符的语法都非常简单,由三个点(...)组成。这些点后面是要在其上使用 restspread 运算符的对象。接下来简单的讨论一下两者的工作原理。

对象的 rest 运算符

rest 运算符使你可以将对象的所有剩余对象属性属性提取到新对象上。要注意这些属性必须是可枚举的。如果你已经对某些属性使用了分解,那么 rest 运算符会只提取剩余的属性。

// Rest example:

const daysObj = {
  one: 'Monday',
  two: 'Tuesday',
  three: 'Wednesday',
  four: 'Thursday',
  five: 'Friday'
}

//使用解构将变量的前两个属性分配给变量。
//然后,使用rest将其余属性分配给第三个变量。
const { one, two, ...restOfDays } = daysObj
// rest 仅提取 "three", "four" 和 "five" 
// 因为我们已经提取了 "one" 和 "two" 

console.log(one)
// Output:
// 'Monday'

console.log(two)
// Output:
// 'Tuesday'

console.log(restOfDays)
// Output:
// { three: 'Wednesday', four: 'Thursday', five: 'Friday' }

如果要对对象使用 rest 运算符,需要记住两点:首先,只能用一次,除非把它用在嵌套对象上。其次,必须在最后使用。这就是为什么在上面的例子中,在解构前两个属性之后而不是之前看到它的原因。

// 这行代码不起作用,因为把 rest 运算符用在了最前面:
const { ...all, one, two } = { one: 1, two: 2, three: 3 }

//这行能起作用:
const { one, two, ...all } = { one: 1, two: 2, three: 3 }

// 这行不起作用,因为同一级别上有多个 rest 运算符:
const { one, ...some, ...end } = { /* some properties */ }

// 这行能起作用,在多个级别上的多个 rest 运算符:
const { one, {...secondLevel }, ...firstLevel } = { /* some properties */ }

对象的 spread 运算符

spread 运算符的作用是可以通过插入另一个对象的所有属性来创建新对象。 Spread 运算符还允许你从多个对象插入属性。也可以把这个运算符与添加新属性结合使用。

// Spread example:
const myOriginalObj = { name: 'Joe Doe', age: 33 }
// 用 spread 运算符创建新对象:
const myNewObj = { ...myOriginalObj }

console.log(myNewObj)
// Output:
// { name: 'Joe Doe', age: 33 }


// 添加属性的例子:
const myOriginalObj = { name: 'Caesar' }
// 用 spread 运算符创建新对象
// 并添加新的属性“genre”:
const myNewObj = { ...myOriginalObj, genre: 'Strategy' }

console.log(myNewObj)
// Output:
// {
//   name: 'Caesar',
//   genre: 'Strategy'
// }


// Spread 运算符并合并两个对象:
const myObjOne = { title: 'Eloquent JavaScript' }
const myObjTwo = { author: 'Marijn Haverbeke' }

const myNewObj = { ...myObjOne, ...myObjTwo }

console.log(myNewObj)
// Output:
// {
//   title: 'Eloquent JavaScript',
//   author: 'Marijn Haverbeke'
// }

当从多个对象插入属性并添加新属性时,顺序很重要

我来解释一下,假设你要用 spread 运算符基于两个现有对象创建一个新对象。第一个已有对象中包含具有某些值的属性 title。第二个对象也包含属性 title,但是值不一样。最终到底取哪个 title

答案是最后一个。如果对第一个对象使用 spread 运算符,然后再对第二个对象使用,则第二个 title 会生效。如果你将 spread 运算符永在第二个对象上,则第一个 title 会生效。

// Spread 运算符并合并两个对象:
const myObjOne = {
  title: 'Eloquent JavaScript',
  author: 'Marijn Haverbeke',
}

const myObjTwo = {
  title: 'You Don\'t Know JS Yet',
  language: 'English'
}

// 用 spread 运算符通过组合 “myObjOne” 和 “myObjTwo” 创建新对象
// 注意:“myObjTwo” 中的 “title” 会将覆盖 “myObjTwo” 的 “title”
// 因为“ myObjTwo”排在最后。
const myNewObj = { ...myObjOne, ...myObjTwo }

console.log(myNewObj)
// Output:
// {
//   title: "You Don't Know JS Yet",
//   author: 'Marijn Haverbeke',
//   language: 'English'
// }


// 注意:“myObjOne” 中的 “title” 将覆盖 “myObjTwo” 的 “title”
const myNewObj = { ...myObjTwo, ...myObjOne }

console.log(myNewObj)
// Output:
// {
//   title: 'Eloquent JavaScript',
//   language: 'English',
//   author: 'Marijn Haverbeke'
// }

Promise.prototype.finally()

一开始有两个用于 Promise 的回调函数。其中一个是 then(),在实现诺 Promise 执行。第二个是catch(),在 promise 被拒绝或 then() 抛出异常时执行。 ES2018 增加了用于 Promise 的第三个回调函数 finally()

每次完成 promise 时,都会执行 finally() 回调,不管 promise 是否完成。这个回调的一般用于执行应始终发生的操作。例如关闭模态对话框、关闭数据库连接或进行某些清理。

// finally() example:
fetch()
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.log(error))
  //最后做点什么:
  .finally(() => console.log('Operation done.'))

对正则表达式的改进

ES2018 还对正则表达式功能进行了的一些改进。这些改进包括 s(dotAll) 标志,后行断言,命名捕获组和 unicode 属性转义。

s(dotAll)

首先是 s(dotAll) 。与点(.)不同,s(dotAll) 允许对换行符及表情符号进行匹配。

// s(dotAll) example:
/hello.world/.test('hello\nworld')
// Output:
// false

/hello.world/s.test('hello\nworld')
// Output:
// true

后行断言

在ES2018之前,JavaScript仅支持先行断言。先行断言用于基于其后的文本来匹配模式。在 ES2018 中增加了对后行断言的支持。通过它可以基于模式之前的文本模式来进行匹配。后行断言的语法为 ?<=

// 后行断言例子:
/(?<=green) apple/.test('One red apple is on the table.')
// Output:
// false

/(?<=green) apple/.test('One green apple is on the table.')
// Output:
// true

断言后面也有一个反向的回溯。仅当子字符串之前没有断言时,此断言才与模式匹配。对后行断言取反操作的语法是 ?<!

/(?<!green) apple/.test('One red apple is on the table.')
// Output:
// true

/(?<!green) apple/.test('One green apple is on the table.')
// Output:
// false

命名捕获组

另一个被 ES2018 引入到正则表达式的好功能是命名捕获组。命名捕获组的语法为 ?<some_name>

const date_pattern = /(?<day>\d{2})\/(?<month>\d{2})\/(?<year>\d{4})/
const result = date_pattern.exec('11/12/2021')

console.log(result)
// Output:
// [
//   '11/12/2021',
//   '11',
//   '12',
//   '2021',
//   index: 0,
//   input: '11/12/2021',
//   groups: [Object: null prototype] { day: '11', month: '12', year: '2021' }
// ]

console.log(result.groups.day)
// Output:
// '11'

console.log(result.groups.month)
// Output:
// '12'

console.log(result.groups.year)
// Output:
// '2021'

Unicode 属性转义

每个 unicode 字符都有许多属性。例如:空白字符,大小写,字母,ASCII,表情符号等。现在你可以在正则表达式中访问这些属性了。

要使用这个功能需要做两件事。首先必须使用 /u 标志。这个标志告诉 JavaScript 你的字符串是一系列 Unicode 代码点。第二是使用 \p{}。你要检查的属性位于大括号之间,反之则用 \P{}

// 用俄语创建一个字符串(西里尔字母):
const myStrCyr = 'Доброе утро'

//创建英文字符串(拉丁字母):
const myStrLat = 'Good morning'

//测试“ myStrCyr”是否包含西里尔字符:
/\p{Script=Cyrillic}/u.test(myStrCyr) // true

//测试“ myStrLat”是否包含西里尔字符:
/\p{Script=Cyrillic}/u.test(myStrLat) // false

// 测试“myStrLat” 是否包含西里尔字符:
/\p{Script=Latin}/u.test(myStrCyr) // false

// 测试“myStrLat” 是否包含拉丁语字符:
/\p{Script=Latin}/u.test(myStrLat) // true

173382ede7319973.gif


本文首发微信公众号:前端先锋

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章


欢迎继续阅读本专栏其它高赞文章:


查看原文

赞 4 收藏 4 评论 0

疯狂的技术宅 发布了文章 · 4月2日

10分钟用Hugo打造一个静态网站

什么是静态网站生成器?

简简单的说,静态网站生成器会获取你的内容,并将其应用于模板,然后生成基于 HTML 的静态网站。非常适合个人博客。

好处:

  • 快速部署
  • 安全(无动态内容)
  • 快速迅速
  • 使用简单
  • 能够进行版本控制

那么,都有哪些流行的静态网站生成器呢?

  • Gatsby (React/JS)
  • Hugo (Go)
  • Next.js (React/JS)
  • Jekyll (Ruby)
  • Gridsome (Vue/JS)

这些项目在 GitHub 上的知名度非常高。

Hugo 是什么?

其官方网站号称 Hugo 是世界上最快的静态网站引擎。

image.png

Hugo 是用 Go 语言编写的,它还有非常丰富的主题系统。

安装 Hugo

Mac:

brew install hugo

Linux:

sudo apt-get install hugo
或者
sudo pacman -Syu hugo

然后执行下面的命令检查是否安装成功:

hugo version

使用Hugo

创建一个新项目:

hugo new site my-project

下载一个主题。可以在 https://themes.gohugo.io/ 找到更多你喜欢的主题。

cd my-project
git init
git submodule add https://github.com/budparr/gohugo-theme-ananke.git themes/ananke

将主题添加到配置文件。

echo 'theme = "ananke"' >> config.toml

添加一篇文章。

hugo new posts/my-first-post.md

它看上去应该像这样:

---
title: "My First Post"
date: 2021-03-10T18:37:11+08:00
draft: true
---

Hello World!

可以在这里给文章添加添加更多属性配置(标签,描述,类别,作者)。

可以在 https://gohugo.io/content-man... 了解更多的配置项。

看看效果:

hugo server -D

在浏览器中打开 http://localhost:1313 就能看到你的网站了。

Hugo 的目录结构

.
├── archetypes
├── assets (not created by default)
├── config.toml
├── content
├── data
├── layouts
├── static
└── themes
  • archetypes:Archetypes 是内容模板文件,其中包含预配置的首选项(日期、标题、草稿等)。可以用自定义的预配置前端字段创建新的原型。
  • assets:Assets 文件夹存储所有文件,这些文件由 Hugo Pipes 处理(例如 CSS/Sass 文件)。默认不创建这个目录。
  • config.toml:Hugo 使用 config.tomlconfig.yamlconfig.json(可以在网站根目录中找到)作为默认网站配置文件。除了单独的配置文件之外,你还可以使用 config directory 来分隔不同的环境。
  • content:所有内容文件放在这里。顶级文件夹计为内容部分。如果你有 devopsnodejs 部分,那么你需要有 content/devops/first-post.mdcontent/nodejs/second-post.md 目录。
  • data:这里用来存储配置文件,Hugo 会在生成你网站时用到。
  • layouts:以 .html 文件的形式存储模板。有关更多信息,请参见 Styling 部分。
  • static:存储所有静态内容:图片、CSS、JavaScript 等。当 Hugo 创建你的网站时,static 目录中的所有资源均按原样复制。
  • themes:你所选择的 Hugo 主题。

修改静态网站的样式

我们在之前应用了一个主题。现在,如果我们检查 themes 文件夹,可以看到样式文件。

但是要当心!

千万不要直接编辑这些文件!

应该将主题目录结构复制到 layouts 文件夹。

假设我要将自定义 CSS 应用于主题。

主题有一个 themes/theme-name/layouts/partials 文件夹,可以在其中找到一些HTML模板(header.htmlfooter.html)。现在我们将编辑 header.html 模板,将内容从这个文件复制到 layouts/partials/header.html 中,并注意在主题 layouts 根目录中创建与主题相同的目录结构。

layouts/partials/header.htmlss
themes/theme-name/layouts/partials/header.html

创建一个自定义CSS文件: static/css/custom-style.css,然后把自定义 CSS 文件添加到 config.toml 中:

[params]
 custom_css = ["css/custom-style.css"]

打开 layouts/partials/header.html

将这段代码添加到 <head> 标签内:

{{ range .Site.Params.custom_css -}}
   <link rel="stylesheet" href="{{ . | absURL }}">
{{- end }}

现在,就可以覆盖主题中所应用的 CSS 类。

构建静态网站

在项目的根目录下执行 hugo 命令:

>>> hugo
                   | EN  
-------------------+-----
  Pages            | 14  
  Paginator pages  |  0  
  Non-page files   |  0  
  Static files     |  1  
  Processed images |  0  
  Aliases          |  6  
  Sitemaps         |  1  
  Cleaned          |  0  

Total in 74 ms

执行成功后,会生成一个public 目录,这个目录中的内容就是我们静态网站的所有内容。

然后就可以托管到 GitHub 或 OSS 中了。

Hugo 还提供了更多的内容,可以到官方文档查看:https://gohugo.io/documentation/

173382ede7319973.gif


本文首发微信公众号:前端先锋

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章


欢迎继续阅读本专栏其它高赞文章:


查看原文

赞 11 收藏 10 评论 0

疯狂的技术宅 收藏了文章 · 4月1日

对网站进行测试的 9 个要点

网站或网页的效率在很大程度上取决于测试,并且涉及对网站或相关软件的细致检查。那么我们如何该测试 Web 程序呢?在探究 Web 程序测试服务的更多细节之前,先讨论一下测试为何如此重要。

网站测试或 Web 应用测试应该是每个团队都要关注的问题。随着网站复杂性的日益增加,我们更要关注这个问题,这样才能确保在各种设备、浏览器和操作系统平台上得到最好的用户体验和性能。

以下是测试网站的十个要点,它们可以帮你用更短的时间得出更加准确可信的测试结果。

1. 将探索性测试与传统技术结合

用于网站测试最有效的技术之一是探索性测试。探索性测试有助于减少测试时间并发现其他缺陷。在探索性测试中,测试人员必须发挥创造力编写并运行测试用例。

更重要的是,可以通过将探索性测试与各种黑帽和白帽软件测试技术集成,从而解决探索性测试的缺点。这样做的另一个好处是,能够以更少的时间来生成有着更高的可靠性的测试结果。

2. 在测试中包括第三方应用、扩展和插件

每个 Web 应用都使用了很多第三方扩展、应用和插件。这些资源对于提高 Web 应用的性能和功能至关重要。但是每个插件、应用或扩展的质量差异可能会很大。

所以必须验证插件和扩展与 Web 应用的兼容性。另外还应该确定插件或扩展中的漏洞,这些漏洞可能会对网站的性能产生负面影响。这种测试对实时聊天、RSS、社交媒体、搜索或嵌入式视频功能的影响非常重要。

3. 跨浏览器兼容性测试

网站应该是针对移动设备友好的,因为通常用户喜欢在他们的移动设备上访问。许多公司通过响应式 Web 设计来确保他们的网站在有特定代码库的计算机以及移动设备上能够高效的运行。

开发人员应该使用开放技术(例如CSS3、JavaScript 和。HTML5)创建自适应网站。对于测试而言,进行跨浏览器兼容性测试至关重要。

4. 与开发团队沟通合作

许多公司选择 DevOps 来统一软件开发和测试。所以 QA 不仅应该在整个软件开发生命周期中测试 Web 应用,还要确保与开发人员、客户和需求分析师的协作。

5. 永远不要低估完整性测试

许多测试人员认为完整性测试是回归测试的辅助元素,从而破坏了完整性测试的重要性。但是,健全性测试有助于在有限的时间内评估 Web 应用中的功能。另外完整性测试还可以在代码更改或错误修复后帮助检查 Web 应用的功能。在测试时保持理智无疑是减少测试时间的可靠工具。

6. 站在黑客的角度思考

网站测试还必须侧重于最终用户的仿真,以此获得对用户体验的精确估计。但是,测试人​​员无法通过模拟最终用户来准确地评估网站的安全性。相反,通过模仿网络犯罪分子和黑客的行为来取得更好的测试结果非常重要。

7. 选择可用性测试的理想参数

在网站测试的众多方面中,可用性测试始终占据头把交椅。在进行测试时,应该始终评估网站的用户界面和用户体验。可用性测试中一些值得注意的参数是UI设计、内容可读性、速度、可访问性和可导航性。

8.确保 URL 字符串不被篡改

网站的安全功能无疑是测试的关键之一。应该评估 Web 应用在维护用户数据和企业数据安全性方面的效率。但是黑客可以通过篡改 URL 将用户重定向到恶意网站或访问敏感数据。某些恶意网站可以在用户的​​系统上安装恶意软件,或迫使它们共享敏感数据。

9. 进行持续的负载测试

压力测试在网站测试中是一个令人望而生畏的领域,它有助于评估网站在正常压力和峰值压力条件下的性能。可以利用复杂而全面的自动化测试工具来加速负载测试。

不过重要的是要通过不断的执行压力测试,来验证不同负载条件下 Web 应用的性能。应该通过逐渐增加压力的方式来确保增量执行负载测试。用户负载的缓慢而逐步的增加可以帮助我们找到由于压力而崩溃的确切位置。

查看原文

疯狂的技术宅 收藏了文章 · 4月1日

Sass / SCSS简明入门

Sass / SCSS简介

Sass 是 CSS3 的扩展,添加了嵌套规则、变量、mixins、选择器继承等。可以用命令行工具或网络框架插件将其转换为格式良好的标准 CSS。

Sass(Syntactically Awesome StyleSheets) 是 CSS 的一种扩展,是 CSS的 超集(通过编译生成浏览器可以处理传统 CSS)。Sass 的出现是为了解决在大型项目中传统 CSS 会遇到的重复、可维护性差等问题(添加了嵌套的规则、变量、mixins、选择器继承等特性)。让开发者可以编写简洁、富语意(expressive )、可复用、可维护性和可延展性性佳的 CSS 代码。

Sass 的语法分为新的 SCSS(Sassy CSSSass 3,文件名称 *.scss)和旧的 SASS( Haml 风格,由于不用大括号格式,使用了缩紧,不能直接用 CSS 语法,学习曲线较高等特性,文件名称为*.sass)。由于新的 SCSS 语法是 CSS3 的超集,所以把传统的 CSS3 直接复制过来也不会出错,学习曲线相对比较平缓,所以我们将使用SCSS语法。

SASS 初体验

在开始介绍 SASS 特性之前先来学习如何将 Sass 转译成 CSS。

首先,先按照官网先安装Sass,然后在项目文件夹建立一个 main.scss 文件,文件内容如下:

// 引用
@import url(https://fonts.googleapis.com/css?family=Pacifico);
//Add variables here:

h1 {
  font-family: Roboto, sans-serif;
  text-align: center;
}

.banner {
  font-family: 'Pacifico', cursive;
  height: 400px;
  background-image: url("lemonade.jpg");
}

.container {
  text-align: center;
  font-family: 'Pacifico', cursive;
}

在终端下用以下命令进行转译:

sass main.scss main.css

这时你就会看到文件夹中多了 main.cssmain.css.map 两个文件,前者是转译过后的 CSS 文件,大部分是方便使用浏览器调试工具在进行调试时连结原文件和转译文件1⃣️方便调试。

1. 变量:变量可以用来储存值,方便重复利用

在 Sass 中用 $ 来表示变量,变量的数据型态可以是数字、字符串、布尔值、null值、甚至可以使用 List 和 Map。

变量的使用:

$translucent-white: rgba(255,255,255,0.3);
p {
    background-color: $translucent-white;
}

List 可以用空格或逗号分隔属性值:

$font-style-2: Helvetica, Arial, sans-serif;
$standard-border: 4px solid black;

p {
    border: $standard-border;
}

// maps key:value
$font-style-2: (key1: value1, key2: value2);


2. 嵌套:降低父元素重复性

转译前:

.parent {
    color: blue;
    .child {
        font-size: 12px;
    }
}



轉譯後:

转译后:

.parent {
    color: blue;
}

.parent .child {
    font-size: 12px;
}
```



在 Nesting 中不僅只有 child selectors 可以使用,還可以使用在相同的 Properties 上:

在Nesting中且只有子选择器可以使用,还可以使用在相同的

.parent {
font : {
    family: Roboto, sans-serif;
    size: 12px;
    decoration: none;
  }
}

转译后:

.parent {
    font-family: Roboto, sans-serif;
    font-size: 12px;
    font-decoration: none;
}

3. Mixins:减少编写伪元素时的重复

如:::before:: after::hover,在 Sass 中使用 代表父元素

转译前:

.notecard{ 
    &:hover{
        @include transform (rotatey(-180deg));  
    }
}

转译后:

.notecard:hover {
    transform: rotatey(-180deg);
}

重用群组的 CSS,例如跨浏览器的前缀,使用 @include 加入群组:

转译前:

@mixin backface-visibility {
    backface-visibility: hidden;
    -webkit-backface-visibility: hidden;
    -moz-backface-visibility: hidden;
    -ms-backface-visibility: hidden;
    -o-backface-visibility: hidden;
}
.notecard {
    .front, .back {
        width: 100%;
        height: 100%;
        position: absolute;
        @include backface_visibility;
    }
}

转译后:

.notecard .front, .notecard .back {
    width: 100%;
    height: 100%;
    position: absolute;

    backface-visibility: hidden;
    -webkit-backface-visibility: hidden; 
    -moz-backface-visibility: hidden;
    -ms-backface-visibility: hidden;
    -o-backface-visibility: hidden;
}

@mixin 也可以通过 @include 使用参数,也可以使用默认值:

@mixin backface-visibility($visibility:hidden) { //Add an argument
    backface-visibility: $visibility;
    -webkit-backface-visibility: $visibility;
    -moz-backface-visibility: $visibility;
    -ms-backface-visibility: $visibility;
    -o-backface-visibility: $visibility;
}

.front, .back {
    @include backface-visibility(hidden);
}

有时我们也需要处理一些复杂的参数:

@mixin stripes($direction, $width-percent, $stripe-color, $stripe-background: #FFF) {
    background: repeating-linear-gradient(
        $direction,
        $stripe-background,
        $stripe-background ($width-percent - 1),
        $stripe-color 1%,
        $stripe-background $width-percent
    );
}

用 Map 传入变量:

$college-ruled-style: ( 
    direction: to bottom,
    width-percent: 15%,
    stripe-color: blue,
    stripe-background: white
);

变量用 ... 进行传递:

.definition {
    width: 100%;
    height: 100%;
    @include stripes($college-ruled-style...);
}

还有种情况是字符串:

转译前:

// 使用 #{$file} 接收
@mixin photo-content($file) {
    content: url(#{$file}.jpg); //string interpolation
    object-fit: cover;
}

.photo { 
    @include photo-content('titanosaur');
    width: 60%;
    margin: 0px auto; 
}

转译后:

.photo { 
    content: url(titanosaur.jpg);
    width: 60%;
    margin: 0px auto; 
}
更可以搭配 Nesting 使用:

还可以搭配 Nesting :

@mixin hover-color($color) {
    &:hover {
        color: $color;
    }
 }

 .word {
    @include hover-color(red);
 }

4. 函数

在 Sass 中也可以通过内置函数简单设定颜色、渐变等,例如:adjust-hue($color,$degrees)fade-out

$lagoon-blue: fade-out(#62fdca, 0.5);

更多的内建函数可以参考高压博文文档(http://sass-lang.com/document...

5. 操作符:通过加减乘除和求余数等运算符方便的计算所需的属性值

颜色加法:

$color: #010203 + #040506;
/*
01 + 04 = 05
02 + 05 = 07
03 + 06 = 09
color: #050709;
*/

在使用 / 时需要注意:

width: $variable/6; // 除法
line-height: (600px)/9; // 除法
margin-left: 20-10 px/ 2; // 除法
font-size: 10px/8px; //无法计算

也可以使用 @each 语法循环 list 内容:

$list: (orange, purple, teal);

@each $item in $list {
    .#{$item} {
          background: $item;
    }
}

使用 @for 循环,并加入条件判断:

@for $i from 1 through $total {
    .ray:nth-child(#{$i}) {
        background: adjust-hue(blue, $i * $step);
        // 
        width: if($i % 2 == 0, 300px, 350px);
        margin-left: if($i % 2 == 0, 0px, 50px);
    }
}

6. @include 引用:用于引入其他 Sass、SCSS 文件:

我們通常使用 `Partials` 去處理特定功能,方便管理和維護。以下是引用 `_variables.scss` 檔案範例,其中檔名前的 `_` 表示引用前要先 compile:

我们通常用 Partials 去处理特定功能,方便管理和维护。以下是引用 _variables.scss 文件,其中文件名前的 _ 表示引用前要先编译:

@import "variables";

7. @extend :

编译前:

.lemonade {
    border: 1px yellow;
    background-color: #fdd;
}
.strawberry {
    @extend .lemonade;
    border-color: pink;
}

转译后:

.lemonade, .strawberry {
    border: 1px yellow;
    background-color: #fdd;
}

.strawberry {
    @extend .lemonade;
    border-color: pink;
}

搭配占位符使用:

转译前:

a%drink {
    font-size: 2em;
    background-color: $lemon-yellow;
}

.lemonade {
    @extend %drink;
    //more rules
}

转译后

a.lemonade {
  font-size: 2em;
  background-color: $lemon-yellow;
}

.lemonade {
 //more rules
}

8.@mixin@extend 的比较

转译前:

@mixin no-variable {
    font-size: 12px;
    color: #FFF;
    opacity: .9;
}

%placeholder {
    font-size: 12px;
    color: #FFF;
    opacity: .9;
}

span {
    @extend %placeholder;
}

div {
    @extend %placeholder;
}

p {
    @include no-variable;
}

h1 {
    @include no-variable;
}

转译后:

span, div{
font-size: 12px;
color: #FFF;
opacity: .9;
}

p {
font-size: 12px;
color: #FFF;
opacity: .9;
//rules specific to ps
}

h1 {
font-size: 12px;
color: #FFF;
opacity: .9;
//rules specific to ps
}

9. Sass文件夹结构

sass/
    components/
        _buttons.scss
    helpers/
        _variables.scss
        _functions.scss
        _mixins.scss
    layout/
        _grid.scss
        _header.scss
        _footer.scss
    pages/
        _home.scss
        _contact.scss

总结

以上是 Sass/SCSS 简明入门,在这篇文章中我们大致上介绍了 Sass 使用语法。除了 Sass 外上还有很多 CSS 的变形,包括语法比较容易学的 LESS、具有组件化思想的 CSS in JS,主要解决全局问题和模块引用的 CSS Modules,取经于 JavaScript Task Runner 的 PostCSS,网格样式表单 GSS 等,这些最终都是要解决传统 CSS 不易维护,重用性差的问题。实际上有些人觉得使用预处理器更好维护,也有些人认为进行编译很麻烦,到于要不要用,用哪种类型的 CSS 预处理器,必须要在团队内部进行讨论和规范。

173382ede7319973.gif


本文首发微信公众号:前端先锋

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章


欢迎继续阅读本专栏其它高赞文章:


查看原文

疯狂的技术宅 发布了文章 · 4月1日

Sass / SCSS简明入门

Sass / SCSS简介

Sass 是 CSS3 的扩展,添加了嵌套规则、变量、mixins、选择器继承等。可以用命令行工具或网络框架插件将其转换为格式良好的标准 CSS。

Sass(Syntactically Awesome StyleSheets) 是 CSS 的一种扩展,是 CSS的 超集(通过编译生成浏览器可以处理传统 CSS)。Sass 的出现是为了解决在大型项目中传统 CSS 会遇到的重复、可维护性差等问题(添加了嵌套的规则、变量、mixins、选择器继承等特性)。让开发者可以编写简洁、富语意(expressive )、可复用、可维护性和可延展性性佳的 CSS 代码。

Sass 的语法分为新的 SCSS(Sassy CSSSass 3,文件名称 *.scss)和旧的 SASS( Haml 风格,由于不用大括号格式,使用了缩紧,不能直接用 CSS 语法,学习曲线较高等特性,文件名称为*.sass)。由于新的 SCSS 语法是 CSS3 的超集,所以把传统的 CSS3 直接复制过来也不会出错,学习曲线相对比较平缓,所以我们将使用SCSS语法。

SASS 初体验

在开始介绍 SASS 特性之前先来学习如何将 Sass 转译成 CSS。

首先,先按照官网先安装Sass,然后在项目文件夹建立一个 main.scss 文件,文件内容如下:

// 引用
@import url(https://fonts.googleapis.com/css?family=Pacifico);
//Add variables here:

h1 {
  font-family: Roboto, sans-serif;
  text-align: center;
}

.banner {
  font-family: 'Pacifico', cursive;
  height: 400px;
  background-image: url("lemonade.jpg");
}

.container {
  text-align: center;
  font-family: 'Pacifico', cursive;
}

在终端下用以下命令进行转译:

sass main.scss main.css

这时你就会看到文件夹中多了 main.cssmain.css.map 两个文件,前者是转译过后的 CSS 文件,大部分是方便使用浏览器调试工具在进行调试时连结原文件和转译文件1⃣️方便调试。

1. 变量:变量可以用来储存值,方便重复利用

在 Sass 中用 $ 来表示变量,变量的数据型态可以是数字、字符串、布尔值、null值、甚至可以使用 List 和 Map。

变量的使用:

$translucent-white: rgba(255,255,255,0.3);
p {
    background-color: $translucent-white;
}

List 可以用空格或逗号分隔属性值:

$font-style-2: Helvetica, Arial, sans-serif;
$standard-border: 4px solid black;

p {
    border: $standard-border;
}

// maps key:value
$font-style-2: (key1: value1, key2: value2);


2. 嵌套:降低父元素重复性

转译前:

.parent {
    color: blue;
    .child {
        font-size: 12px;
    }
}



轉譯後:

转译后:

.parent {
    color: blue;
}

.parent .child {
    font-size: 12px;
}
```



在 Nesting 中不僅只有 child selectors 可以使用,還可以使用在相同的 Properties 上:

在Nesting中且只有子选择器可以使用,还可以使用在相同的

.parent {
font : {
    family: Roboto, sans-serif;
    size: 12px;
    decoration: none;
  }
}

转译后:

.parent {
    font-family: Roboto, sans-serif;
    font-size: 12px;
    font-decoration: none;
}

3. Mixins:减少编写伪元素时的重复

如:::before:: after::hover,在 Sass 中使用 代表父元素

转译前:

.notecard{ 
    &:hover{
        @include transform (rotatey(-180deg));  
    }
}

转译后:

.notecard:hover {
    transform: rotatey(-180deg);
}

重用群组的 CSS,例如跨浏览器的前缀,使用 @include 加入群组:

转译前:

@mixin backface-visibility {
    backface-visibility: hidden;
    -webkit-backface-visibility: hidden;
    -moz-backface-visibility: hidden;
    -ms-backface-visibility: hidden;
    -o-backface-visibility: hidden;
}
.notecard {
    .front, .back {
        width: 100%;
        height: 100%;
        position: absolute;
        @include backface_visibility;
    }
}

转译后:

.notecard .front, .notecard .back {
    width: 100%;
    height: 100%;
    position: absolute;

    backface-visibility: hidden;
    -webkit-backface-visibility: hidden; 
    -moz-backface-visibility: hidden;
    -ms-backface-visibility: hidden;
    -o-backface-visibility: hidden;
}

@mixin 也可以通过 @include 使用参数,也可以使用默认值:

@mixin backface-visibility($visibility:hidden) { //Add an argument
    backface-visibility: $visibility;
    -webkit-backface-visibility: $visibility;
    -moz-backface-visibility: $visibility;
    -ms-backface-visibility: $visibility;
    -o-backface-visibility: $visibility;
}

.front, .back {
    @include backface-visibility(hidden);
}

有时我们也需要处理一些复杂的参数:

@mixin stripes($direction, $width-percent, $stripe-color, $stripe-background: #FFF) {
    background: repeating-linear-gradient(
        $direction,
        $stripe-background,
        $stripe-background ($width-percent - 1),
        $stripe-color 1%,
        $stripe-background $width-percent
    );
}

用 Map 传入变量:

$college-ruled-style: ( 
    direction: to bottom,
    width-percent: 15%,
    stripe-color: blue,
    stripe-background: white
);

变量用 ... 进行传递:

.definition {
    width: 100%;
    height: 100%;
    @include stripes($college-ruled-style...);
}

还有种情况是字符串:

转译前:

// 使用 #{$file} 接收
@mixin photo-content($file) {
    content: url(#{$file}.jpg); //string interpolation
    object-fit: cover;
}

.photo { 
    @include photo-content('titanosaur');
    width: 60%;
    margin: 0px auto; 
}

转译后:

.photo { 
    content: url(titanosaur.jpg);
    width: 60%;
    margin: 0px auto; 
}
更可以搭配 Nesting 使用:

还可以搭配 Nesting :

@mixin hover-color($color) {
    &:hover {
        color: $color;
    }
 }

 .word {
    @include hover-color(red);
 }

4. 函数

在 Sass 中也可以通过内置函数简单设定颜色、渐变等,例如:adjust-hue($color,$degrees)fade-out

$lagoon-blue: fade-out(#62fdca, 0.5);

更多的内建函数可以参考高压博文文档(http://sass-lang.com/document...

5. 操作符:通过加减乘除和求余数等运算符方便的计算所需的属性值

颜色加法:

$color: #010203 + #040506;
/*
01 + 04 = 05
02 + 05 = 07
03 + 06 = 09
color: #050709;
*/

在使用 / 时需要注意:

width: $variable/6; // 除法
line-height: (600px)/9; // 除法
margin-left: 20-10 px/ 2; // 除法
font-size: 10px/8px; //无法计算

也可以使用 @each 语法循环 list 内容:

$list: (orange, purple, teal);

@each $item in $list {
    .#{$item} {
          background: $item;
    }
}

使用 @for 循环,并加入条件判断:

@for $i from 1 through $total {
    .ray:nth-child(#{$i}) {
        background: adjust-hue(blue, $i * $step);
        // 
        width: if($i % 2 == 0, 300px, 350px);
        margin-left: if($i % 2 == 0, 0px, 50px);
    }
}

6. @include 引用:用于引入其他 Sass、SCSS 文件:

我們通常使用 `Partials` 去處理特定功能,方便管理和維護。以下是引用 `_variables.scss` 檔案範例,其中檔名前的 `_` 表示引用前要先 compile:

我们通常用 Partials 去处理特定功能,方便管理和维护。以下是引用 _variables.scss 文件,其中文件名前的 _ 表示引用前要先编译:

@import "variables";

7. @extend :

编译前:

.lemonade {
    border: 1px yellow;
    background-color: #fdd;
}
.strawberry {
    @extend .lemonade;
    border-color: pink;
}

转译后:

.lemonade, .strawberry {
    border: 1px yellow;
    background-color: #fdd;
}

.strawberry {
    @extend .lemonade;
    border-color: pink;
}

搭配占位符使用:

转译前:

a%drink {
    font-size: 2em;
    background-color: $lemon-yellow;
}

.lemonade {
    @extend %drink;
    //more rules
}

转译后

a.lemonade {
  font-size: 2em;
  background-color: $lemon-yellow;
}

.lemonade {
 //more rules
}

8.@mixin@extend 的比较

转译前:

@mixin no-variable {
    font-size: 12px;
    color: #FFF;
    opacity: .9;
}

%placeholder {
    font-size: 12px;
    color: #FFF;
    opacity: .9;
}

span {
    @extend %placeholder;
}

div {
    @extend %placeholder;
}

p {
    @include no-variable;
}

h1 {
    @include no-variable;
}

转译后:

span, div{
font-size: 12px;
color: #FFF;
opacity: .9;
}

p {
font-size: 12px;
color: #FFF;
opacity: .9;
//rules specific to ps
}

h1 {
font-size: 12px;
color: #FFF;
opacity: .9;
//rules specific to ps
}

9. Sass文件夹结构

sass/
    components/
        _buttons.scss
    helpers/
        _variables.scss
        _functions.scss
        _mixins.scss
    layout/
        _grid.scss
        _header.scss
        _footer.scss
    pages/
        _home.scss
        _contact.scss

总结

以上是 Sass/SCSS 简明入门,在这篇文章中我们大致上介绍了 Sass 使用语法。除了 Sass 外上还有很多 CSS 的变形,包括语法比较容易学的 LESS、具有组件化思想的 CSS in JS,主要解决全局问题和模块引用的 CSS Modules,取经于 JavaScript Task Runner 的 PostCSS,网格样式表单 GSS 等,这些最终都是要解决传统 CSS 不易维护,重用性差的问题。实际上有些人觉得使用预处理器更好维护,也有些人认为进行编译很麻烦,到于要不要用,用哪种类型的 CSS 预处理器,必须要在团队内部进行讨论和规范。

173382ede7319973.gif


本文首发微信公众号:前端先锋

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章


欢迎继续阅读本专栏其它高赞文章:


查看原文

赞 5 收藏 4 评论 0

疯狂的技术宅 发布了文章 · 3月31日

对网站进行测试的 9 个要点

网站或网页的效率在很大程度上取决于测试,并且涉及对网站或相关软件的细致检查。那么我们如何该测试 Web 程序呢?在探究 Web 程序测试服务的更多细节之前,先讨论一下测试为何如此重要。

网站测试或 Web 应用测试应该是每个团队都要关注的问题。随着网站复杂性的日益增加,我们更要关注这个问题,这样才能确保在各种设备、浏览器和操作系统平台上得到最好的用户体验和性能。

以下是测试网站的十个要点,它们可以帮你用更短的时间得出更加准确可信的测试结果。

1. 将探索性测试与传统技术结合

用于网站测试最有效的技术之一是探索性测试。探索性测试有助于减少测试时间并发现其他缺陷。在探索性测试中,测试人员必须发挥创造力编写并运行测试用例。

更重要的是,可以通过将探索性测试与各种黑帽和白帽软件测试技术集成,从而解决探索性测试的缺点。这样做的另一个好处是,能够以更少的时间来生成有着更高的可靠性的测试结果。

2. 在测试中包括第三方应用、扩展和插件

每个 Web 应用都使用了很多第三方扩展、应用和插件。这些资源对于提高 Web 应用的性能和功能至关重要。但是每个插件、应用或扩展的质量差异可能会很大。

所以必须验证插件和扩展与 Web 应用的兼容性。另外还应该确定插件或扩展中的漏洞,这些漏洞可能会对网站的性能产生负面影响。这种测试对实时聊天、RSS、社交媒体、搜索或嵌入式视频功能的影响非常重要。

3. 跨浏览器兼容性测试

网站应该是针对移动设备友好的,因为通常用户喜欢在他们的移动设备上访问。许多公司通过响应式 Web 设计来确保他们的网站在有特定代码库的计算机以及移动设备上能够高效的运行。

开发人员应该使用开放技术(例如CSS3、JavaScript 和。HTML5)创建自适应网站。对于测试而言,进行跨浏览器兼容性测试至关重要。

4. 与开发团队沟通合作

许多公司选择 DevOps 来统一软件开发和测试。所以 QA 不仅应该在整个软件开发生命周期中测试 Web 应用,还要确保与开发人员、客户和需求分析师的协作。

5. 永远不要低估完整性测试

许多测试人员认为完整性测试是回归测试的辅助元素,从而破坏了完整性测试的重要性。但是,健全性测试有助于在有限的时间内评估 Web 应用中的功能。另外完整性测试还可以在代码更改或错误修复后帮助检查 Web 应用的功能。在测试时保持理智无疑是减少测试时间的可靠工具。

6. 站在黑客的角度思考

网站测试还必须侧重于最终用户的仿真,以此获得对用户体验的精确估计。但是,测试人​​员无法通过模拟最终用户来准确地评估网站的安全性。相反,通过模仿网络犯罪分子和黑客的行为来取得更好的测试结果非常重要。

7. 选择可用性测试的理想参数

在网站测试的众多方面中,可用性测试始终占据头把交椅。在进行测试时,应该始终评估网站的用户界面和用户体验。可用性测试中一些值得注意的参数是UI设计、内容可读性、速度、可访问性和可导航性。

8.确保 URL 字符串不被篡改

网站的安全功能无疑是测试的关键之一。应该评估 Web 应用在维护用户数据和企业数据安全性方面的效率。但是黑客可以通过篡改 URL 将用户重定向到恶意网站或访问敏感数据。某些恶意网站可以在用户的​​系统上安装恶意软件,或迫使它们共享敏感数据。

9. 进行持续的负载测试

压力测试在网站测试中是一个令人望而生畏的领域,它有助于评估网站在正常压力和峰值压力条件下的性能。可以利用复杂而全面的自动化测试工具来加速负载测试。

不过重要的是要通过不断的执行压力测试,来验证不同负载条件下 Web 应用的性能。应该通过逐渐增加压力的方式来确保增量执行负载测试。用户负载的缓慢而逐步的增加可以帮助我们找到由于压力而崩溃的确切位置。

查看原文

赞 4 收藏 4 评论 0

认证与成就

  • 获得 10604 次点赞
  • 获得 32 枚徽章 获得 1 枚金徽章, 获得 4 枚银徽章, 获得 27 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2014-04-11
个人主页被 62.2k 人浏览