winbug

winbug 查看完整档案

北京编辑  |  填写毕业院校  |  填写所在公司/组织 webtools.ttwinbug.com 编辑
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

winbug 提出了问题 · 1月14日

IIS虚拟主机,如何设置Basic认证

有一台IIS的虚拟主机(无管理员权限),想要给指定目录添加Basic认证,在网上查了很多资料都没有一个像.htaccess的简便方法。请问有什么办法能够添加Basic认证么?

关注 1 回答 0

winbug 收藏了文章 · 2020-12-22

程序员接私活必备后台框架,不用重复造轮子,网友:太好用了!

我们做Web开发中,几乎都摆脱不了后台的开发。如果各位程序员朋友还是从零开始撸代码。那简直是费时费力吃力不讨好。

今天小编推荐几个后台UI框架,让各位接单赚钱不用愁!

1.vue-element-admin

vue-element-admin 是一个后台前端解决方案,它基于 vue 和 element-ui实现。它使用了最新的前端技术栈,内置了 i18n 国际化解决方案,动态路由,权限验证,提炼了典型的业务模型,提供了丰富的功能组件,它可以帮助你快速搭建企业级中后台产品原型。相信不管你的需求是什么,本项目都能帮助到你。

img

2.AdminLTE

AdminLTE是一个完全响应管理模板。基于Bootstrap3,jQuery 3.3.1 这两个框架框架,易定制模板。适合多种屏幕分辨率,从小型移动设备到大型台式机。内置了多个页面,包括仪表盘、邮箱、日历、锁屏、登录及注册、404错误、500错误等页面。对于后台站点的模板渲染,有很大的作用。

AdminLTE2.1

3.Tabler

Tabler 是一个基于 Bootstrap 4 开发的 HTML 仪表盘 UI 套件,旨在提供一个用户友好,清晰简单的管理面板,可适用于简单和复杂的网站系统。

Tabler 唯一的使用要求是具备基本的 HTML 和 CSS 知识 —— 作为奖励,你将能够以最简单的方式管理和可视化不同类型的数据。

img

img

4.Fusion Design

Fusion Design是一套旨在全面提升设计、开发效率的工作方式。 通过协助企业构建设计系统,提供系统化工具协助设计师、前端使用设计系统,提供一站式设计项目协助平台,打通互联网产品从设计到开发的工作流。

Fusion Design是阿里巴巴内部使用框架,有大厂做背书,相信不会做得太差。

img

image-20201214144506631

5.ng2-admin

基于Angular 4+,Angular CLI,Bootstrap 4,以及许多令人敬畏的模块和插件

ng2-admin的配置文件非常完善,组件也比较多,所以直接选择ng2-admin起步,适合有一定基础或者想直接上手搭建一套后台系统,搭建的后台系统会有较多动态组件,追求自动化,动态性。 例:表单将会使用service来动态生成,以及完成验证。

a

6.Ant Design

Ant Design是基于 Ant Design 设计体系的 React UI 组件库,主要用于研发企业级中后台产品。

特性
  • 🌈 提炼自企业级中后台产品的交互语言和视觉风格。
  • 📦 开箱即用的高质量 React 组件。
  • 🛡 使用 TypeScript 开发,提供完整的类型定义文件。
  • ⚙️ 全链路开发和设计工具体系。
  • 🌍 数十个国际化语言支持。
  • 🎨 深入每个细节的主题定制能力。

aa

7.layui-mini

基于Layui编写的一套最简洁、易用的后台框架模板

主要特性
  • 界面足够简洁清爽,响应式且适配手机端。
  • 一个接口几行代码而已直接初始化整个框架,无需复杂操作。
  • 页面支持多配色方案,可自行选择喜欢的配色。
  • 支持多tab,可以打开多窗口。
  • 支持无限级菜单和对font-awesome图标库的完美支持。
  • 失效以及报错菜单无法直接打开,并给出弹出层提示完美的线上用户体验
  • url地址hash定位,可以清楚看到当前tab的地址信息。
  • 刷新页面会保留当前的窗口,并且会定位当前窗口对应左侧菜单栏。
  • 支持font-awesome图标选择插件

微信截图_20201214150056

微信截图_20201214150125

8.iview-admin

iView Admin 是基于 Vue.js,搭配使用 iView UI 组件库形成的一套后台集成解决方案,由 TalkingData 前端可视化团队部分成员开发维护。iView Admin 遵守 iView 设计和开发约定,风格统一,设计考究,并且更多功能在不停开发中。

结尾

本期就分享到这里,我是小编南风吹,专注分享好玩有趣、新奇、实用的开源项目及开发者工具、学习资源!
希望能与大家共同学习交流,欢迎关注我的公众号【Github导航站】

往期推荐

3000多人访问一个html文件,多少宽带才足够支撑

程序员接私活必备后台框架,不用重复造轮子,网友:太好用了!

还在从头到尾撸项目?这6个SpringBoot项目用好了,事半功倍!

厉害了,这款程序员代码补全工具,让你的编程效率飞起来!

查看原文

winbug 收藏了文章 · 2020-07-07

从ES6到ES10的新特性万字大总结(不得不收藏)

介绍

ECMAScript是一种由Ecma国际(前身为欧洲计算机制造商协会)在标准ECMA-262中定义的脚本语言规范。这种语言在万维网上应用广泛,它往往被称为JavaScript或JScript,但实际上后两者是ECMA-262标准的实现和扩展。

历史版本

至发稿日为止有九个ECMA-262版本发表。其历史版本如下:

  1. 1997年6月:第一版
  2. 1998年6月:修改格式,使其与ISO/IEC16262国际标准一样
  3. 1999年12月:强大的正则表达式,更好的词法作用域链处理,新的控制指令,异常处理,错误定义更加明确,数据输出的格式化及其它改变
  4. 2009年12月:添加严格模式("use strict")。修改了前面版本模糊不清的概念。增加了getters,setters,JSON以及在对象属性上更完整的反射。
  5. 2011年6月:ECMAScript标5.1版形式上完全一致于国际标准ISO/IEC 16262:2011。
  6. 2015年6月:ECMAScript 2015(ES2015),第 6 版,最早被称作是 ECMAScript 6(ES6),添加了类和模块的语法,其他特性包括迭代器,Python风格的生成器和生成器表达式,箭头函数,二进制数据,静态类型数组,集合(maps,sets 和 weak maps),promise,reflection 和 proxies。作为最早的 ECMAScript Harmony 版本,也被叫做ES6 Harmony。
  7. 2016年6月:ECMAScript 2016(ES2016),第 7 版,多个新的概念和语言特性。
  8. 2017年6月:ECMAScript 2017(ES2017),第 8 版,多个新的概念和语言特性。
  9. 2018年6月:ECMAScript 2018 (ES2018),第 9 版,包含了异步循环,生成器,新的正则表达式特性和 rest/spread 语法。
  10. 2019年6月:ECMAScript 2019 (ES2019),第 10 版。

发展标准

TC39(Technical Committee 39)是一个推动JavaScript发展的委员会,它的成语来自各个主流浏览器的代表成语。会议实行多数决,每一项决策只有大部分人同意且没有强烈反对才能去实现。

TC39成员制定着ECMAScript的未来。

每一项新特性最终要进入到ECMAScript规范里,需要经历5个阶段,这5个阶段如下:

  • Stage 0: Strawperson

    只要是TC39成员或者贡献者,都可以提交想法

  • Stage 1: Proposal

    这个阶段确定一个正式的提案

  • Stage 2: draft

    规范的第一个版本,进入此阶段的提案大概率会成为标准

  • Stage 3: Candidate

    进一步完善提案细则

  • Stage 4: Finished

    表示已准备好将其添加到正式的ECMAScript标准中

由于ES6以前的属性诞生年底久远,我们使用也比较普遍,遂不进行说明,ES6之后的语言风格跟ES5以前的差异比较大,所以单独拎出来做个记录。

ES6(ES2015)

ES6是一次重大的革新,比起过去的版本,改动比较大,本文仅对常用的API以及语法糖进行讲解。

Let 和 Const

在ES6以前,JS只有var一种声明方式,但是在ES6之后,就多了letconst这两种方式。用var定义的变量没有块级作用域的概念,而letconst则会有,因为这三个关键字创建是不一样的。

区别如下:

{
    var a = 10
    let b = 20
    const c = 30
}
a // 10
b // Uncaught ReferenceError: b is not defined
c // c is not defined
let d = 40
const e = 50
d = 60
d // 60
e = 70 // VM231:1 Uncaught TypeError: Assignment to constant variable.
varletconst
变量提升××
全局变量××
重复声明××
重新赋值×
暂时死区×
块作用域×
只声明不初始化×

类(Class)

在ES6之前,如果我们要生成一个实例对象,传统的方法就是写一个构造函数,例子如下:

function Person(name, age) {
    this.name = name
    this.age = age
}
Person.prototype.information = function () {
    return 'My name is ' + this.name + ', I am ' + this.age
}

但是在ES6之后,我们只需要写成以下形式:

class Person {
    constructor(name, age) {
        this.name = name
        this.age = age
    }
    information() {
        return 'My name is ' + this.name + ', I am ' + this.age
    }
}

箭头函数(Arrow function)

箭头函数表达式的语法比函数表达式更简洁,并且没有自己的thisargumentssupernew.target。这些函数表达式更适用于那些本来需要匿名函数的地方,并且它们不能用作构造函数。

在ES6以前,我们写函数一般是:

var list = [1, 2, 3, 4, 5, 6, 7]
var newList = list.map(function (item) {
    return item * item
})

但是在ES6里,我们可以:

const list = [1, 2, 3, 4, 5, 6, 7]
const newList = list.map(item => item * item)

看,是不是简洁了不少

函数参数默认值(Function parameter defaults)

在ES6之前,如果我们写函数需要定义初始值的时候,需要这么写:

function config (data) {
    var data = data || 'data is empty'
}

这样看起来也没有问题,但是如果参数的布尔值为falsy时就会出问题,例如我们这样调用config:

config(0)
config('')

那么结果就永远是后面的值

如果我们用函数参数默认值就没有这个问题,写法如下:

const config = (data = 'data is empty') => {}

模板字符串(Template string)

在ES6之前,如果我们要拼接字符串,则需要像这样:

var name = 'kris'
var age = 24
var info = 'My name is ' + this.name + ', I am ' + this.age

但是在ES6之后,我们只需要写成以下形式:

const name = 'kris'
const age = 24
const info = `My name is ${name}, I am ${age}`

解构赋值(Destructuring assignment)

我们通过解构赋值, 可以将属性/值从对象/数组中取出,赋值给其他变量。

比如我们需要交换两个变量的值,在ES6之前我们可能需要:

var a = 10
var b = 20
var temp = a
a = b
b = temp

但是在ES6里,我们有:

let a = 10
let b = 20
[a, b] = [b, a]

是不是方便很多

模块化(Module)

在ES6之前,JS并没有模块化的概念,有的也只是社区定制的类似CommonJS和AMD之类的规则。例如基于CommonJS的NodeJS:

// circle.js
// 输出
const { PI } = Math
exports.area = (r) => PI * r ** 2
exports.circumference = (r) => 2 * PI * r

// index.js
// 输入
const circle = require('./circle.js')
console.log(`半径为 4 的圆的面积是 ${circle.area(4)}`)

在ES6之后我们则可以写成以下形式:

// circle.js
// 输出
const { PI } = Math
export const area = (r) => PI * r ** 2
export const circumference = (r) => 2 * PI * r

// index.js
// 输入
import {
    area
} = './circle.js'
console.log(`半径为 4 的圆的面积是: ${area(4)}`)

扩展操作符(Spread operator)

扩展操作符可以在函数调用/数组构造时, 将数组表达式或者string在语法层面展开;还可以在构造字面量对象时, 将对象表达式按key-value的方式展开。

比如在ES5的时候,我们要对一个数组的元素进行相加,在不使用reduce或者reduceRight的场合,我们需要:

function sum(x, y, z) {
    return x + y + z;
}
var list = [5, 6, 7]
var total = sum.apply(null, list)

但是如果我们使用扩展操作符,只需要如下:

const sum = (x, y, z) => x + y + z
const list = [5, 6, 7]
const total = sum(...list)

非常的简单,但是要注意的是扩展操作符只能用于可迭代对象

如果是下面的情况,是会报错的:

var obj = {'key1': 'value1'}
var array = [...obj] // TypeError: obj is not iterable

对象属性简写(Object attribute shorthand)

在ES6之前,如果我们要将某个变量赋值为同样名称的对象元素,则需要:

var cat = 'Miaow'
var dog = 'Woof'
var bird = 'Peet peet'

var someObject = {
  cat: cat,
  dog: dog,
  bird: bird
}

但是在ES6里我们就方便很多:

let cat = 'Miaow'
let dog = 'Woof'
let bird = 'Peet peet'

let someObject = {
  cat,
  dog,
  bird
}

console.log(someObject)

//{
//  cat: "Miaow",
//  dog: "Woof",
//  bird: "Peet peet"
//}

非常方便

Promise

Promise 是ES6提供的一种异步解决方案,比回调函数更加清晰明了。

Promise 翻译过来就是承诺的意思,这个承诺会在未来有一个确切的答复,并且该承诺有三种状态,分别是:

  1. 等待中(pending)
  2. 完成了 (resolved)
  3. 拒绝了(rejected)

这个承诺一旦从等待状态变成为其他状态就永远不能更改状态了,也就是说一旦状态变为 resolved 后,就不能再次改变

new Promise((resolve, reject) => {
  resolve('success')
  // 无效
  reject('reject')
})

当我们在构造 Promise 的时候,构造函数内部的代码是立即执行的

new Promise((resolve, reject) => {
  console.log('new Promise')
  resolve('success')
})
console.log('finifsh')
// new Promise -> finifsh

Promise 实现了链式调用,也就是说每次调用 then 之后返回的都是一个 Promise,并且是一个全新的 Promise,原因也是因为状态不可变。如果你在 then 中 使用了 return,那么 return 的值会被 Promise.resolve() 包装

Promise.resolve(1)
  .then(res => {
    console.log(res) // => 1
    return 2 // 包装成 Promise.resolve(2)
  })
  .then(res => {
    console.log(res) // => 2
  })

当然了,Promise 也很好地解决了回调地狱的问题,例如:

ajax(url, () => {
    // 处理逻辑
    ajax(url1, () => {
        // 处理逻辑
        ajax(url2, () => {
            // 处理逻辑
        })
    })
})

可以改写成:

ajax(url)
  .then(res => {
      console.log(res)
      return ajax(url1)
  }).then(res => {
      console.log(res)
      return ajax(url2)
  }).then(res => console.log(res))

for...of

for...of语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。

例子如下:

const array1 = ['a', 'b', 'c'];

for (const element of array1) {
      console.log(element)
}

// "a"
// "b"
// "c"

Symbol

symbol 是一种基本数据类型,Symbol()函数会返回symbol类型的值,该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的symbol注册,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持语法:"new Symbol()"。

每个从Symbol()返回的symbol值都是唯一的。一个symbol值能作为对象属性的标识符;这是该数据类型仅有的目的。

例子如下:

const symbol1 = Symbol();
const symbol2 = Symbol(42);
const symbol3 = Symbol('foo');

console.log(typeof symbol1); // "symbol"
console.log(symbol3.toString()); // "Symbol(foo)"
console.log(Symbol('foo') === Symbol('foo')); // false

迭代器(Iterator)/ 生成器(Generator)

迭代器(Iterator)是一种迭代的机制,为各种不同的数据结构提供统一的访问机制。任何数据结构只要内部有 Iterator 接口,就可以完成依次迭代操作。

一旦创建,迭代器对象可以通过重复调用next()显式地迭代,从而获取该对象每一级的值,直到迭代完,返回{ value: undefined, done: true }

虽然自定义的迭代器是一个有用的工具,但由于需要显式地维护其内部状态,因此需要谨慎地创建。生成器函数提供了一个强大的选择:它允许你定义一个包含自有迭代算法的函数, 同时它可以自动维护自己的状态。 生成器函数使用 function*语法编写。 最初调用时,生成器函数不执行任何代码,而是返回一种称为Generator的迭代器。 通过调用生成器的下一个方法消耗值时,Generator函数将执行,直到遇到yield关键字。

可以根据需要多次调用该函数,并且每次都返回一个新的Generator,但每个Generator只能迭代一次。

所以我们可以有以下例子:

function* makeRangeIterator(start = 0, end = Infinity, step = 1) {
    for (let i = start; i < end; i += step) {
        yield i;
    }
}
var a = makeRangeIterator(1,10,2)
a.next() // {value: 1, done: false}
a.next() // {value: 3, done: false}
a.next() // {value: 5, done: false}
a.next() // {value: 7, done: false}
a.next() // {value: 9, done: false}
a.next() // {value: undefined, done: true}

Set/WeakSet

Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。

所以我们可以通过Set实现数组去重

const numbers = [2,3,4,4,2,3,3,4,4,5,5,6,6,7,5,32,3,4,5]
console.log([...new Set(numbers)]) 
// [2, 3, 4, 5, 6, 7, 32]

WeakSet 结构与 Set 类似,但区别有以下两点:

  • WeakSet 对象中只能存放对象引用, 不能存放值, 而 Set 对象都可以。
  • WeakSet 对象中存储的对象值都是被弱引用的, 如果没有其他的变量或属性引用这个对象值, 则这个对象值会被当成垃圾回收掉. 正因为这样, WeakSet 对象是无法被枚举的, 没有办法拿到它包含的所有元素。

所以代码如下:

var ws = new WeakSet()
var obj = {}
var foo = {}

ws.add(window)
ws.add(obj)

ws.has(window) // true
ws.has(foo)    // false, 对象 foo 并没有被添加进 ws 中 

ws.delete(window) // 从集合中删除 window 对象
ws.has(window)    // false, window 对象已经被删除了

ws.clear() // 清空整个 WeakSet 对象

Map/WeakMap

Map 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。

例子如下,我们甚至可以使用NaN来作为键值:

var myMap = new Map();
myMap.set(NaN, "not a number");

myMap.get(NaN); // "not a number"

var otherNaN = Number("foo");
myMap.get(otherNaN); // "not a number"

WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。

Map的区别与SetWeakSet的区别相似,具体代码如下:

var wm1 = new WeakMap(),
    wm2 = new WeakMap(),
    wm3 = new WeakMap();
var o1 = {},
    o2 = function(){},
    o3 = window;

wm1.set(o1, 37);
wm1.set(o2, "azerty");
wm2.set(o1, o2); // value可以是任意值,包括一个对象
wm2.set(o3, undefined);
wm2.set(wm1, wm2); // 键和值可以是任意对象,甚至另外一个WeakMap对象
wm1.get(o2); // "azerty"
wm2.get(o2); // undefined,wm2中没有o2这个键
wm2.get(o3); // undefined,值就是undefined

wm1.has(o2); // true
wm2.has(o2); // false
wm2.has(o3); // true (即使值是undefined)

wm3.set(o1, 37);
wm3.get(o1); // 37
wm3.clear();
wm3.get(o1); // undefined,wm3已被清空
wm1.has(o1);   // true
wm1.delete(o1);
wm1.has(o1);   // false

Proxy/Reflect

Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 Proxy 的方法相同。Reflect不是一个函数对象,因此它是不可构造的。

ProxyReflect是非常完美的配合,例子如下:

const observe = (data, callback) => {
      return new Proxy(data, {
            get(target, key) {
                return Reflect.get(target, key)
            },
            set(target, key, value, proxy) {
                  callback(key, value);
                  target[key] = value;
                    return Reflect.set(target, key, value, proxy)
            }
      })
}

const FooBar = { open: false };
const FooBarObserver = observe(FooBar, (property, value) => {
  property === 'open' && value 
          ? console.log('FooBar is open!!!') 
          : console.log('keep waiting');
});
console.log(FooBarObserver.open) // false
FooBarObserver.open = true // FooBar is open!!!

当然也不是什么都可以被代理的,如果对象带有configurable: falsewritable: false 属性,则代理失效。

Regex对象的扩展

正则新增符号

  • i 修饰符

    // i 修饰符
    /[a-z]/i.test('\u212A') // false
    /[a-z]/iu.test('\u212A') // true
  • y修饰符

    // y修饰符
    var s = 'aaa_aa_a';
    var r1 = /a+/g;
    var r2 = /a+/y;
    
    r1.exec(s) // ["aaa"]
    r2.exec(s) // ["aaa"]
    
    r1.exec(s) // ["aa"]
    r2.exec(s) // null
  • String.prototype.flags

    // 查看RegExp构造函数的修饰符
    var regex = new RegExp('xyz', 'i')
    regex.flags // 'i'
  • unicode模式

    var s = '𠮷'
    /^.$/.test(s) // false
    /^.$/u.test(s) // true
  • u转义

    // u转义
    /\,/ // /\,/
    /\,/u // 报错 没有u修饰符时,逗号前面的反斜杠是无效的,加了u修饰符就报错。
  • 引用

    const RE_TWICE = /^(?<word>[a-z]+)!\k<word>$/;
    RE_TWICE.test('abc!abc') // true
    RE_TWICE.test('abc!ab') // false
    
    const RE_TWICE = /^(?<word>[a-z]+)!\1$/;
    RE_TWICE.test('abc!abc') // true
    RE_TWICE.test('abc!ab') // false

字符串方法的实现改为调用RegExp方法

  • String.prototype.match 调用 RegExp.prototype[Symbol.match]
  • String.prototype.replace 调用 RegExp.prototype[Symbol.replace]
  • String.prototype.search 调用 RegExp.prototype[Symbol.search]
  • String.prototype.split 调用 RegExp.prototype[Symbol.split]

正则新增属性

  • RegExp.prototype.sticky 表示是否有y修饰符

    /hello\d/y.sticky // true
  • RegExp.prototype.flags获取修饰符

    /abc/ig.flags // 'gi'

Math对象的扩展

  • 二进制表示法 : 0b或0B开头表示二进制(0bXX0BXX)
  • 二进制表示法 : 0b或0B开头表示二进制(0bXX0BXX)
  • 八进制表示法 : 0o或0O开头表示二进制(0oXX0OXX)
  • Number.EPSILON : 数值最小精度
  • Number.MIN_SAFE_INTEGER : 最小安全数值(-2^53)
  • Number.MAX_SAFE_INTEGER : 最大安全数值(2^53)
  • Number.parseInt() : 返回转换值的整数部分
  • Number.parseFloat() : 返回转换值的浮点数部分
  • Number.isFinite() : 是否为有限数值
  • Number.isNaN() : 是否为NaN
  • Number.isInteger() : 是否为整数
  • Number.isSafeInteger() : 是否在数值安全范围内
  • Math.trunc() : 返回数值整数部分
  • Math.sign() : 返回数值类型(正数1负数-1零0)
  • Math.cbrt() : 返回数值立方根
  • Math.clz32() : 返回数值的32位无符号整数形式
  • Math.imul() : 返回两个数值相乘
  • Math.fround() : 返回数值的32位单精度浮点数形式
  • Math.hypot() : 返回所有数值平方和的平方根
  • Math.expm1() : 返回e^n - 1
  • Math.log1p() : 返回1 + n的自然对数(Math.log(1 + n))
  • Math.log10() : 返回以10为底的n的对数
  • Math.log2() : 返回以2为底的n的对数
  • Math.sinh() : 返回n的双曲正弦
  • Math.cosh() : 返回n的双曲余弦
  • Math.tanh() : 返回n的双曲正切
  • Math.asinh() : 返回n的反双曲正弦
  • Math.acosh() : 返回n的反双曲余弦
  • Math.atanh() : 返回n的反双曲正切

Array对象的扩展

  • Array.prototype.from:转换具有Iterator接口的数据结构为真正数组,返回新数组。

    console.log(Array.from('foo')) // ["f", "o", "o"]
    console.log(Array.from([1, 2, 3], x => x + x)) // [2, 4, 6]
  • Array.prototype.of():转换一组值为真正数组,返回新数组。

    Array.of(7)       // [7] 
    Array.of(1, 2, 3) // [1, 2, 3]
    
    Array(7)          // [empty, empty, empty, empty, empty, empty]
    Array(1, 2, 3)    // [1, 2, 3]
  • Array.prototype.copyWithin():把指定位置的成员复制到其他位置,返回原数组

    const array1 = ['a', 'b', 'c', 'd', 'e']
    
    console.log(array1.copyWithin(0, 3, 4)) // ["d", "b", "c", "d", "e"]
    
    console.log(array1.copyWithin(1, 3)) // ["d", "d", "e", "d", "e"]
  • Array.prototype.find():返回第一个符合条件的成员

    const array1 = [5, 12, 8, 130, 44]
    
    const found = array1.find(element => element > 10)
    
    console.log(found) // 12
  • Array.prototype.findIndex():返回第一个符合条件的成员索引值

    const array1 = [5, 12, 8, 130, 44]
    
    const isLargeNumber = (element) => element > 13
    
    console.log(array1.findIndex(isLargeNumber)) // 3
  • Array.prototype.fill():根据指定值填充整个数组,返回原数组

    const array1 = [1, 2, 3, 4]
    
    console.log(array1.fill(0, 2, 4)) // [1, 2, 0, 0]
    
    console.log(array1.fill(5, 1)) // [1, 5, 5, 5]
    
    console.log(array1.fill(6)) // [6, 6, 6, 6]
  • Array.prototype.keys():返回以索引值为遍历器的对象

    const array1 = ['a', 'b', 'c']
    const iterator = array1.keys()
    
    for (const key of iterator) {
          console.log(key)
    }
    
    // 0
    // 1
    // 2
  • Array.prototype.values():返回以属性值为遍历器的对象

    const array1 = ['a', 'b', 'c']
    const iterator = array1.values()
    
    for (const key of iterator) {
          console.log(key)
    }
    
    // a
    // b
    // c
  • Array.prototype.entries():返回以索引值和属性值为遍历器的对象

    const array1 = ['a', 'b', 'c']
    const iterator = array1.entries()
    
    console.log(iterator.next().value) // [0, "a"]
    console.log(iterator.next().value) // [1, "b"]
  • 数组空位:ES6明确将数组空位转为undefined或者empty

    Array.from(['a',,'b']) // [ "a", undefined, "b" ]
    [...['a',,'b']] // [ "a", undefined, "b" ]
    Array(3) //  [empty × 3]
    [,'a'] // [empty, "a"]

ES7(ES2016)

Array.prototype.includes()

includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。

代码如下:

const array1 = [1, 2, 3]
console.log(array1.includes(2)) // true

const pets = ['cat', 'dog', 'bat']
console.log(pets.includes('cat')) // true
console.log(pets.includes('at')) // false

幂运算符**

幂运算符**,具有与Math.pow()一样的功能,代码如下:

console.log(2**10) // 1024
console.log(Math.pow(2, 10)) // 1024

模板字符串(Template string)

自ES7起,带标签的模版字面量遵守以下转义序列的规则:

  • Unicode字符以"u"开头,例如\u00A9
  • Unicode码位用"u{}"表示,例如\u{2F804}
  • 十六进制以"x"开头,例如\xA9
  • 八进制以""和数字开头,例如\251

这表示类似下面这种带标签的模版是有问题的,因为对于每一个ECMAScript语法,解析器都会去查找有效的转义序列,但是只能得到这是一个形式错误的语法:

latex`\unicode`
// 在较老的ECMAScript版本中报错(ES2016及更早)
// SyntaxError: malformed Unicode character escape sequence

ES8(ES2017)

async/await

虽然Promise可以解决回调地狱的问题,但是链式调用太多,则会变成另一种形式的回调地狱 —— 面条地狱,所以在ES8里则出现了Promise的语法糖async/await,专门解决这个问题。

我们先看一下下面的Promise代码:

fetch('coffee.jpg')
    .then(response => response.blob())
    .then(myBlob => {
          let objectURL = URL.createObjectURL(myBlob)
          let image = document.createElement('img')
          image.src = objectURL
          document.body.appendChild(image)
    })
    .catch(e => {
          console.log('There has been a problem with your fetch operation: ' + e.message)
    })

然后再看看async/await版的,这样看起来是不是更清晰了。

async function myFetch() {
      let response = await fetch('coffee.jpg')
      let myBlob = await response.blob()

      let objectURL = URL.createObjectURL(myBlob)
      let image = document.createElement('img')
      image.src = objectURL
      document.body.appendChild(image)
}

myFetch()

当然,如果你喜欢,你甚至可以两者混用

async function myFetch() {
      let response = await fetch('coffee.jpg')
      return await response.blob()
}

myFetch().then((blob) => {
      let objectURL = URL.createObjectURL(blob)
      let image = document.createElement('img')
      image.src = objectURL
      document.body.appendChild(image)
})

Object.values()

Object.values()方法返回一个给定对象自身的所有可枚举属性值的数组,值的顺序与使用for...in循环的顺序相同 ( 区别在于 for-in 循环枚举原型链中的属性 )。

代码如下:

const object1 = {
      a: 'somestring',
      b: 42,
      c: false
}
console.log(Object.values(object1)) // ["somestring", 42, false]

Object.entries()

Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for...in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环还会枚举原型链中的属性)。

代码如下:

const object1 = {
      a: 'somestring',
      b: 42
}

for (let [key, value] of Object.entries(object1)) {
      console.log(`${key}: ${value}`)
}

// "a: somestring"
// "b: 42"

padStart()

padStart() 方法用另一个字符串填充当前字符串(重复,如果需要的话),以便产生的字符串达到给定的长度。填充从当前字符串的开始(左侧)应用的。

代码如下:

const str1 = '5'
console.log(str1.padStart(2, '0')) // "05"

const fullNumber = '2034399002125581'
const last4Digits = fullNumber.slice(-4)
const maskedNumber = last4Digits.padStart(fullNumber.length, '*') 
console.log(maskedNumber) // "************5581"

padEnd()

padEnd() 方法会用一个字符串填充当前字符串(如果需要的话则重复填充),返回填充后达到指定长度的字符串。从当前字符串的末尾(右侧)开始填充。

const str1 = 'Breaded Mushrooms'
console.log(str1.padEnd(25, '.')) // "Breaded Mushrooms........"
const str2 = '200'
console.log(str2.padEnd(5)) // "200  "

函数参数结尾逗号(Function parameter lists and calls trailing commas)

在ES5里就添加了对象的尾逗号,不过并不支持函数参数,但是在ES8之后,便开始支持这一特性,代码如下:

// 参数定义
function f(p) {}
function f(p,) {} 

(p) => {}
(p,) => {}

class C {
  one(a,) {},
  two(a, b,) {},
}

var obj = {
  one(a,) {},
  two(a, b,) {},
};

// 函数调用
f(p)
f(p,)

Math.max(10, 20)
Math.max(10, 20,)

但是以下的方式是不合法的:

仅仅包含逗号的函数参数定义或者函数调用会抛出 SyntaxError。 而且,当使用剩余参数的时候,并不支持尾后逗号,例子如下:

function f(,) {} // SyntaxError: missing formal parameter
(,) => {}       // SyntaxError: expected expression, got ','
f(,)             // SyntaxError: expected expression, got ','

function f(...p,) {} // SyntaxError: parameter after rest parameter
(...p,) => {}        // SyntaxError: expected closing parenthesis, got ','

在解构里也可以使用,代码如下:

// 带有尾后逗号的数组解构
[a, b,] = [1, 2]

// 带有尾后逗号的对象解构
var o = {
  p: 42, 
  q: true,
}
var {p, q,} = o

同样地,在使用剩余参数时,会抛出 SyntaxError,代码如下:

var [a, ...b,] = [1, 2, 3] // SyntaxError: rest element may not have a trailing comma

ShareArrayBuffer(因安全问题,暂时在Chrome跟FireFox中被禁用)

SharedArrayBuffer 对象用来表示一个通用的,固定长度的原始二进制数据缓冲区,类似于 ArrayBuffer 对象,它们都可以用来在共享内存(shared memory)上创建视图。与 ArrayBuffer 不同的是,SharedArrayBuffer 不能被分离。

代码如下:

let sab = new SharedArrayBuffer(1024) // 必须实例化
worker.postMessage(sab)

Atomics对象

Atomics对象 提供了一组静态方法用来对 SharedArrayBuffer 对象进行原子操作。

方法如下:

  • Atomics.add() :将指定位置上的数组元素与给定的值相加,并返回相加前该元素的值。
  • Atomics.and():将指定位置上的数组元素与给定的值相与,并返回与操作前该元素的值。
  • Atomics.compareExchange():如果数组中指定的元素与给定的值相等,则将其更新为新的值,并返回该元素原先的值。
  • Atomics.exchange():将数组中指定的元素更新为给定的值,并返回该元素更新前的值。
  • Atomics.load():返回数组中指定元素的值。
  • Atomics.or():将指定位置上的数组元素与给定的值相或,并返回或操作前该元素的值。
  • Atomics.store():将数组中指定的元素设置为给定的值,并返回该值。
  • Atomics.sub():将指定位置上的数组元素与给定的值相减,并返回相减前该元素的值。
  • Atomics.xor():将指定位置上的数组元素与给定的值相异或,并返回异或操作前该元素的值。
  • Atomics.wait():检测数组中某个指定位置上的值是否仍然是给定值,是则保持挂起直到被唤醒或超时。返回值为 "ok"、"not-equal" 或 "time-out"。调用时,如果当前线程不允许阻塞,则会抛出异常(大多数浏览器都不允许在主线程中调用 wait())。
  • Atomics.wake():唤醒等待队列中正在数组指定位置的元素上等待的线程。返回值为成功唤醒的线程数量。
  • Atomics.isLockFree(size):可以用来检测当前系统是否支持硬件级的原子操作。对于指定大小的数组,如果当前系统支持硬件级的原子操作,则返回 true;否则就意味着对于该数组,Atomics 对象中的各原子操作都只能用锁来实现。此函数面向的是技术专家。

Object.getOwnPropertyDescriptors()

Object.getOwnPropertyDescriptors() 方法用来获取一个对象的所有自身属性的描述符。代码如下:

const object1 = {
  property1: 42
}

const descriptors1 = Object.getOwnPropertyDescriptors(object1)

console.log(descriptors1.property1.writable) // true

console.log(descriptors1.property1.value) // 42

// 浅拷贝一个对象
Object.create(
  Object.getPrototypeOf(obj), 
  Object.getOwnPropertyDescriptors(obj) 
)

// 创建子类
function superclass() {}
superclass.prototype = {
  // 在这里定义方法和属性
}
function subclass() {}
subclass.prototype = Object.create(superclass.prototype, Object.getOwnPropertyDescriptors({
  // 在这里定义方法和属性
}))

ES9(ES2018)

for await...of

for await...of 语句会在异步或者同步可迭代对象上创建一个迭代循环,包括 StringArrayArray-like 对象(比如arguments 或者NodeList),TypedArrayMapSet和自定义的异步或者同步可迭代对象。其会调用自定义迭代钩子,并为每个不同属性的值执行语句。

配合迭代异步生成器,例子如下:

async function* asyncGenerator() {
      var i = 0
      while (i < 3) {
            yield i++
      }
}

(async function() {
      for await (num of asyncGenerator()) {
            console.log(num)
      }
})()
// 0
// 1
// 2

模板字符串(Template string)

ES9开始,模板字符串允许嵌套支持常见转义序列,移除对ECMAScript在带标签的模版字符串中转义序列的语法限制。

不过,非法转义序列在"cooked"当中仍然会体现出来。它们将以undefined元素的形式存在于"cooked"之中,代码如下:

function latex(str) { 
 return { "cooked": str[0], "raw": str.raw[0] }
} 

latex`\unicode` // { cooked: undefined, raw: "\\unicode" }

正则表达式反向(lookbehind)断言

首先我们得先知道什么是断言(Assertion)

断言(Assertion)是一个对当前匹配位置之前或之后的字符的测试, 它不会实际消耗任何字符,所以断言也被称为“非消耗性匹配”或“非获取匹配”。

正则表达式的断言一共有 4 种形式:

  • (?=pattern) 零宽正向肯定断言(zero-width positive lookahead assertion)
  • (?!pattern) 零宽正向否定断言(zero-width negative lookahead assertion)
  • (?<=pattern) 零宽反向肯定断言(zero-width positive lookbehind assertion)
  • (?<!pattern) 零宽反向否定断言(zero-width negative lookbehind assertion)

在ES9之前,JavaScript 正则表达式,只支持正向断言。正向断言的意思是:当前位置后面的字符串应该满足断言,但是并不捕获。例子如下:

'fishHeadfishTail'.match(/fish(?=Head)/g) // ["fish"]

反向断言和正向断言的行为一样,只是方向相反。例子如下:

'abc123'.match(/(?<=(\d+)(\d+))$/) //  ["", "1", "23", index: 6, input: "abc123", groups: undefined]

正则表达式 Unicode 转义

正则表达式中的Unicode转义符允许根据Unicode字符属性匹配Unicode字符。 它允许区分字符类型,例如大写和小写字母,数学符号和标点符号。

部分例子代码如下:

// 匹配所有数字
const regex = /^\p{Number}+$/u;
regex.test('²³¹¼½¾') // true
regex.test('㉛㉜㉝') // true
regex.test('ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ') // true

// 匹配所有空格
\p{White_Space}

// 匹配各种文字的所有字母,等同于 Unicode 版的 \w
[\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]

// 匹配各种文字的所有非字母的字符,等同于 Unicode 版的 \W
[^\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]

// 匹配 Emoji
/\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F/gu

// 匹配所有的箭头字符
const regexArrows = /^\p{Block=Arrows}+$/u;
regexArrows.test('←↑→↓↔↕↖↗↘↙⇏⇐⇑⇒⇓⇔⇕⇖⇗⇘⇙⇧⇩') // true

具体的属性列表可查看:https://developer.mozilla.org...

正则表达式 s/dotAll 模式

在以往的版本里,JS的正则的.只能匹配emoji跟行终结符以外的所有文本,例如:

let regex = /./;

regex.test('\n');       // false
regex.test('\r');       // false
regex.test('\u{2028}'); // false
regex.test('\u{2029}'); // false

regex.test('\v');       // true
regex.test('\f');       // true
regex.test('\u{0085}'); // true

/foo.bar/.test('foo\nbar');     // false
/foo[^]bar/.test('foo\nbar');   // true

/foo.bar/.test('foo\nbar');     // false
/foo[\s]bar/.test('foo\nbar');   // true

但是在ES9之后,JS正则增加了一个新的标志 s 用来表示 dotAll,这可以匹配任意字符。代码如下:

/foo.bar/s.test('foo\nbar');    // true

const re = /foo.bar/s;  //  等价于 const re = new RegExp('foo.bar', 's');
re.test('foo\nbar');    // true
re.dotAll;      // true
re.flags;       // "s"

正则表达式命名捕获组

在以往的版本里,JS的正则分组是无法命名的,所以容易混淆。例如下面获取年月日的例子,很容易让人搞不清哪个是月份,哪个是年份:

const matched = /(\d{4})-(\d{2})-(\d{2})/.exec('2019-01-01')
console.log(matched[0]);    // 2019-01-01
console.log(matched[1]);    // 2019
console.log(matched[2]);    // 01
console.log(matched[3]);    // 01

ES9引入了命名捕获组,允许为每一个组匹配指定一个名字,既便于阅读代码,又便于引用。代码如下:

const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;

const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj.groups.year; // 1999
const month = matchObj.groups.month; // 12
const day = matchObj.groups.day; // 31

const RE_OPT_A = /^(?<as>a+)?$/;
const matchObj = RE_OPT_A.exec('');

matchObj.groups.as // undefined
'as' in matchObj.groups // true

对象扩展操作符

ES6中添加了数组的扩展操作符,让我们在操作数组时更加简便,美中不足的是并不支持对象扩展操作符,但是在ES9开始,这一功能也得到了支持,例如:

var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };

var clonedObj = { ...obj1 };
// 克隆后的对象: { foo: "bar", x: 42 }

var mergedObj = { ...obj1, ...obj2 };
// 合并后的对象: { foo: "baz", x: 42, y: 13 }

上面便是一个简便的浅拷贝。这里有一点小提示,就是Object.assign() 函数会触发 setters,而展开语法则不会。所以不能替换也不能模拟Object.assign()

如果存在相同的属性名,只有最后一个会生效。

Promise.prototype.finally()

finally()方法会返回一个Promise,当promise的状态变更,不管是变成rejected或者fulfilled,最终都会执行finally()的回调。

例子如下:

fetch(url)
      .then((res) => {
        console.log(res)
      })
      .catch((error) => { 
        console.log(error)
      })
      .finally(() => { 
        console.log('结束')
    })

ES10(ES2019)

Array.prototype.flat() / flatMap()

flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。

flatMap()map() 方法和深度为1的 flat() 几乎相同.,不过它会首先使用映射函数映射每个元素,然后将结果压缩成一个新数组,这样效率会更高。

例子如下:

var arr1 = [1, 2, 3, 4]

arr1.map(x => [x * 2]) // [[2], [4], [6], [8]]

arr1.flatMap(x => [x * 2]) // [2, 4, 6, 8]

// 深度为1
arr1.flatMap(x => [[x * 2]]) // [[2], [4], [6], [8]]

flatMap()可以代替reduce()concat(),例子如下:

var arr = [1, 2, 3, 4]
arr.flatMap(x => [x, x * 2]) // [1, 2, 2, 4, 3, 6, 4, 8]
// 等价于
arr.reduce((acc, x) => acc.concat([x, x * 2]), []) // [1, 2, 2, 4, 3, 6, 4, 8]

但这是非常低效的,在每次迭代中,它创建一个必须被垃圾收集的新临时数组,并且它将元素从当前的累加器数组复制到一个新的数组中,而不是将新的元素添加到现有的数组中。

String.prototype.trimStart() / trimLeft() / trimEnd() / trimRight()

在ES5中,我们可以通过trim()来去掉字符首尾的空格,但是却无法只去掉单边的,但是在ES10之后,我们可以实现这个功能。

如果我们要去掉开头的空格,可以使用trimStart()或者它的别名trimLeft()

同样的,如果我们要去掉结尾的空格,我们可以使用trimEnd()或者它的别名trimRight()

例子如下:

const Str = '   Hello world!  '
console.log(Str) // '   Hello world!  '
console.log(Str.trimStart()) // 'Hello world!  '
console.log(Str.trimLeft()) // 'Hello world!  '
console.log(Str.trimEnd()) // '   Hello world!'
console.log(Str.trimRight()) // '   Hello world!'

不过这里有一点要注意的是,trimStart()trimEnd()才是标准方法,trimLeft()trimRight()只是别名。

在某些引擎里(例如Chrome),有以下的等式:

String.prototype.trimLeft.name === "trimStart"

String.prototype.trimRight.name === "trimEnd"

Object.fromEntries()

Object.fromEntries() 方法把键值对列表转换为一个对象,它是Object.entries()的反函数。

例子如下:

const entries = new Map([
  ['foo', 'bar'],
  ['baz', 42]
])

const obj = Object.fromEntries(entries)

console.log(obj) // Object { foo: "bar", baz: 42 }

Symbol.prototype.description

description 是一个只读属性,它会返回Symbol对象的可选描述的字符串。与 Symbol.prototype.toString() 不同的是它不会包含Symbol()的字符串。例子如下:

Symbol('desc').toString();   // "Symbol(desc)"
Symbol('desc').description;  // "desc"
Symbol('').description;      // ""
Symbol().description;        // undefined

// 具名 symbols
Symbol.iterator.toString();  // "Symbol(Symbol.iterator)"
Symbol.iterator.description; // "Symbol.iterator"

//全局 symbols
Symbol.for('foo').toString();  // "Symbol(foo)"
Symbol.for('foo').description; // "foo"

String.prototype.matchAll

matchAll() 方法返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。并且返回一个不可重启的迭代器。例子如下:

var regexp = /t(e)(st(\d?))/g
var str = 'test1test2'

str.match(regexp) // ['test1', 'test2']
str.matchAll(regexp) // RegExpStringIterator {}
[...str.matchAll(regexp)] // [['test1', 'e', 'st1', '1', index: 0, input: 'test1test2', length: 4], ['test2', 'e', 'st2', '2', index: 5, input: 'test1test2', length: 4]]

Function.prototype.toString() 返回注释与空格

在以往的版本中,Function.prototype.toString()得到的字符串是去掉空白符号的,但是从ES10开始会保留这些空格,如果是原生函数则返回你控制台看到的效果,例子如下:

function sum(a, b) {
      return a + b;
}

console.log(sum.toString())
// "function sum(a, b) {
//         return a + b;
//  }"

console.log(Math.abs.toString()) // "function abs() { [native code] }"

try-catch

在以往的版本中,try-catchcatch后面必须带异常参数,例如:

    // ES10之前
try {
      // tryCode
} catch (err) {
      // catchCode
}

但是在ES10之后,这个参数却不是必须的,如果用不到,我们可以不用传,例如:

try {
      console.log('Foobar')
} catch {
      console.error('Bar')
}

BigInt

BigInt 是一种内置对象,它提供了一种方法来表示大于 253 - 1 的整数。这原本是 Javascript中可以用 Number 表示的最大数字。BigInt 可以表示任意大的整数。

可以用在一个整数字面量后面加 n 的方式定义一个 BigInt ,如:10n,或者调用函数BigInt()

在以往的版本中,我们有以下的弊端:

// 大于2的53次方的整数,无法保持精度
2 ** 53 === (2 ** 53 + 1)
// 超过2的1024次方的数值,无法表示
2 ** 1024 // Infinity

但是在ES10引入BigInt之后,这个问题便得到了解决。

以下操作符可以和 BigInt 一起使用: +*-**% 。除 >>> (无符号右移)之外的位操作也可以支持。因为 BigInt 都是有符号的, >>> (无符号右移)不能用于 BigIntBigInt 不支持单目 (+) 运算符。

/ 操作符对于整数的运算也没问题。可是因为这些变量是 BigInt 而不是 BigDecimal ,该操作符结果会向零取整,也就是说不会返回小数部分。

BigIntNumber不是严格相等的,但是宽松相等的。

所以在BigInt出来以后,JS的原始类型便增加到了7个,如下:

  • Boolean
  • Null
  • Undefined
  • Number
  • String
  • Symbol (ES6)
  • BigInt (ES10)

globalThis

globalThis属性包含类似于全局对象 this值。所以在全局环境下,我们有:

globalThis === this // true

import()

静态的import 语句用于导入由另一个模块导出的绑定。无论是否声明了 严格模式,导入的模块都运行在严格模式下。在浏览器中,import 语句只能在声明了 type="module"script 的标签中使用。

但是在ES10之后,我们有动态 import(),它不需要依赖 type="module" 的script标签。

所以我们有以下例子:

const main = document.querySelector("main")
for (const link of document.querySelectorAll("nav > a")) {
      link.addEventListener("click", e => {
            e.preventDefault()

            import('/modules/my-module.js')
              .then(module => {
                    module.loadPageInto(main);
              })
              .catch(err => {
                    main.textContent = err.message;
              })
      })
}

私有元素与方法

在ES10之前,如果我们要实现一个简单的计数器组件,我们可能会这么写:

// web component 写法
class Counter extends HTMLElement {
      get x() { 
              return this.xValue
      }
      set x(value) {
              this.xValue = value
              window.requestAnimationFrame(this.render.bind(this))
      }

      clicked() {
            this.x++
      }

      constructor() {
            super()
            this.onclick = this.clicked.bind(this)
            this.xValue = 0
      }

      connectedCallback() { 
              this.render()
      }

      render() {
            this.textContent = this.x.toString()
      }
}
window.customElements.define('num-counter', Counter)

但是在ES10之后我们可以使用私有变量进行组件封装,如下:

class Counter extends HTMLElement {
      #xValue = 0

      get #x() { 
          return #xValue
      }
      set #x(value) {
            this.#xValue = value
            window.requestAnimationFrame(this.#render.bind(this))
      }

      #clicked() {
            this.#x++
      }

      constructor() {
            super();
            this.onclick = this.#clicked.bind(this)
      }

      connectedCallback() { 
              this.#render()
      }

      #render() {
            this.textContent = this.#x.toString()
      }
}
window.customElements.define('num-counter', Counter)

参考资料

  1. ECMAScript 6 入门
  2. 1.5万字概括ES6全部特性
  3. MDN
  4. ES2018 新特征之:非转义序列的模板字符串
  5. 正则表达式反向(lookbehind)断言
  6. Unicode property escapes
  7. exnext提案
  8. ES7、ES8、ES9、ES10新特性大盘点
  9. Ecma TC39
  10. [[ECMAScript] TC39 process](https://www.jianshu.com/p/b08...
  11. The TC39 Process
查看原文

winbug 收藏了文章 · 2020-03-17

55 个提高你 CSS 开发效率的必备片段

max-kolganov-vY2aqF6RoKI-unsplash.jpg

这篇文章会记录我们平时常用到的 CSS 片段,使用这些 CSS 可以帮助我们解决许多实际项目问题中遇到的,墙裂建议点赞收藏再看,方便日后查找?

  • 附笔记链接,阅读往期更多优质文章可移步查看:前端笔记本

清除浮动

浮动给我们的代码带来的麻烦,想必不需要多说,我们会用很多方式来避免这种麻烦,其中我觉得最方便也是兼容性最好的一种是,在同级目录下再创建一个<div style="clear:both;"></div>;不过这样会增加很多无用的代码。此时我们用:after这个伪元素来解决浮动的问题,如果当前层级有浮动元素,那么在其父级添加上 clearfix 类即可。

// 清除浮动
.clearfix:after {
  content: "\00A0";
  display: block;
  visibility: hidden;
  width: 0;
  height: 0;
  clear: both;
  font-size: 0;
  line-height: 0;
  overflow: hidden;
}
.clearfix {
  zoom: 1;
}

垂直水平居中

在 css 的世界里水平居中比垂直居中来的简单一些,经过了多年的演化,依然没有好的方式来让元素垂直居中(各种方式各有优缺点,但都不能达到兼容性好,破坏力小的目标),以下是几种常见的实现方式

绝对定位方式且已知宽高

position: absolute;
top: 50%;
left: 50%;
margin-top: -3em;
margin-left: -7em;
width: 14em;
height: 6em;

绝对定位 + 未知宽高 + translate

position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
//需要补充浏览器前缀

flex 轻松搞定水平垂直居中(未知宽高)

display: flex;
align-items: center;
justify-content: center;

文本末尾添加省略号

当文本的内容超出容器的宽度的时候,我们希望在其默认添加省略号以达到提示用户内容省略显示的效果。

宽度固定,适合单行显示...

overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;

宽度不固定,适合多行以及移动端显示

overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;

制造文本的模糊效果

当我们希望给文本制造一种模糊效果感觉的时候,可以这样做

color: transparent;
text-shadow: 0 0 2px rgba(0, 0, 0, 0.5);

动画实现简洁 loading 效果

我们来实现一个非常简洁的 loading 效果

.loading:after {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  content: "\2026";
  -webkit-animation: ellipsis 2s infinite;
}

// 动画部分
@-webkit-keyframes ellipsis {
  from {
    width: 2px;
  }
  to {
    width: 15px;
  }
}

自定义文本选中样式

默认情况下,我们在网页上选中文字的时候,会给选中的部分一个深蓝色背景颜色(可以自己拿起鼠标试试),如果我们想自己定制被选中的部分的样式呢?

// 注意只能修改这两个属性 字体颜色 选中背景颜色

element::selection {
  color: green;
  background-color: pink;
}
element::-moz-selection {
  color: green;
  background-color: pink;
}

顶角贴纸效果

有时候我们会有这样的需求,在一个列表展示页面,有一些列表项是新添加的、或者热度比较高的,就会要求在其上面添加一个贴纸效果的小条就像 hexo 默认博客的 fork me on github 那个效果一样。

接下来我们开始一步步完成最左边的这个效果

html

<div class="wrap">
  <div class="ribbon">
    <a href="#">Fork me on GitHub</a>
  </div>
</div>

css

/* 外层容器几本设置  */
.wrap {
  width: 160px;
  height: 160px;
  overflow: hidden;
  position: relative;
  background-color: #f3f3f3;
}

.ribbon {
  background-color: #a00;
  overflow: hidden;
  white-space: nowrap;
  position: absolute;
  /* shadom */
  -webkit-box-shadow: 0 0 10px #888;
  -moz-box-shadow: 0 0 10px #888;
  box-shadow: 0 0 10px #888;
  /* rotate */
  -webkit-transform: rotate(-45deg);
  -moz-transform: rotate(-45deg);
  -ms-transform: rotate(-45deg);
  -o-transform: rotate(-45deg);
  transform: rotate(-45deg);
  /* position */
  left: -50px;
  top: 40px;
}

.ribbon a {
  border: 1px solid #faa;
  color: #fff;
  display: block;
  font: bold 81.25% "Helvetica Neue", Helvetica, Arial, sans-serif;
  margin: 1px 0;
  padding: 10px 50px;
  text-align: center;
  text-decoration: none;
  /* shadow */
  text-shadow: 0 0 5px #444;
}

input 占位符

当我们给部分 input 类型的设置 placeholder 属性的时候,有的时候需要修改其默认的样式。

input::-webkit-input-placeholder {
  color: green;
  background-color: #f9f7f7;
  font-size: 14px;
}
input::-moz-input-placeholder {
  color: green;
  background-color: #f9f7f7;
  font-size: 14px;
}
input::-ms-input-placeholder {
  color: green;
  background-color: #f9f7f7;
  font-size: 14px;
}

移动端可点击元素去处默认边框

在移动端浏览器上,当你点击一个链接或者通过 Javascript 定义的可点击元素的时候,会出现蓝色边框,说实话,这是很恶心的,怎么去掉呢?

-webkit-tap-highlight-color: rgba(255, 255, 255, 0);

首字下沉

要实现类似 word 中首字下沉的效果可以使用以下代码

element:first-letter {
  float: left;
  color: green;
  font-size: 30px;
}

小三角

在很多地方我们可以用得上小三角,接下来我们画一下四个方向的三角形

.triangle {
  /* 基础样式 */
  border: solid 10px transparent;
}
/*下*/
.triangle.bottom {
  border-top-color: green;
}
/*上*/
.triangle.top {
  border-bottom-color: green;
}
/*左*/
.triangle.top {
  border-right-color: green;
}
/*右*/
.triangle.top {
  border-left-color: green;
}

可以看出画一个小三角非常简单,只要两行样式就可以搞定,对于方向只要想着画哪个方向则设置反方向的样式属性就可以

鼠标手型

一般情况下,我们希望在以下元素身上添加鼠标手型

  • a
  • submit
  • input[type="iamge"]
  • input[type="button"]
  • button
  • label
  • select
a[href],
input[type="submit"],
input[type="image"],
input[type="button"],
label[for],
select,
button {
  cursor: pointer;
}

屏蔽 Webkit 移动浏览器中元素高亮效果

在访问移动网站时,你会发现,在选中的元素周围会出现一些灰色的框框,使用以下代码屏蔽这种样式

-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;

移除常用标签的浏览器默认的 margin 和 padding

pre、code、legend、fieldset、blockquote … 等标签不是很常用,所以就不一一列举,如果项目中使用到,可以自己单独写

body,
p,
h1,
h2,
h3,
h4,
h5,
h6,
dl,
dd,
ul,
ol,
th,
td,
button,
figure,
input,
textarea,
form {
  margin: 0;
  padding: 0;
}

统一 input、select、textarea 宽度

不同浏览器的 input、select、textarea 的盒子模型宽度计算方式不同,统一为最常见的 content-box

input,
select,
textarea {
  -webkit-box-sizing: content-box;
  -moz-box-sizing: content-box;
  box-sizing: content-box;
}

table {
  /*table 相邻单元格的边框间的距离设置为 0*/
  border-spacing: 0;
  /*默认情况下给 tr 设置 border 没有效果,如果 table 设置了边框为合并模式:「border-collapse: collapse;」就可以了*/
  border-collapse: collapse;
}

移除浏览器部分元素的默认边框

acronym、fieldset … 等其他标签不是很常用,就不会一一列举;如果项目中用到,可以自己单独写

img,
input,
button,
textarea {
  border: none;
  -webkit-appearance: none;
}

input {
  /*由于 input 默认不继承父元素的居中样式,所以设置:「text-align: inherit」*/
  text-align: inherit;
}

textarea {
  /*textarea 默认不可以放缩*/
  resize: none;
}

取消元素 outline 样式

由于以下元素的部分属性没有继承父节点样式,所以声明这些元素的这些属性为父元素的属性

a,
h1,
h2,
h3,
h4,
h5,
h6,
input,
select,
button,
option,
textarea,
optgroup {
  font-family: inherit;
  font-size: inherit;
  font-weight: inherit;
  font-style: inherit;
  line-height: inherit;
  color: inherit;
  outline: none;
}

取消超链接元素的默认文字装饰

另外 del、ins 标签的中划线、下划线还是挺好的,就不去掉

a {
  text-decoration: none;
}

ol,
ul {
  /*开发中 UI 设计的列表都是和原生的样式差太多,所以直接给取消 ol,ul 默认列表样式*/
  list-style: none;
}

button,
input[type="submit"],
input[type="button"] {
  /*鼠标经过是「小手」形状表示可点击*/
  cursor: pointer;
}

input::-moz-focus-inner {
  /*取消火狐浏览器部分版本 input 聚焦时默认的「padding、border」*/
  padding: 0;
  border: 0;
}

取消部分浏览器数字输入控件的操作按钮

input[type="number"] {
  -moz-appearance: textfield;
}

input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
  margin: 0;
  -webkit-appearance: none;
}

输入控件 placeholder 色设置 #999

input::-webkit-input-placeholder,
textarea::-webkit-input-placeholder {
  color: #999;
}

input:-moz-placeholder,
textarea:-moz-placeholder {
  color: #999;
}

input::-moz-placeholder,
textarea::-moz-placeholder {
  color: #999;
}

input:-ms-input-placeholder,
textarea:-ms-input-placeholder {
  color: #999;
}

template {
  /*由于部分浏览 template 会显示出来,所以要隐*/
  display: none;
}

position: fixed 的缩写

.pf {
  position: fixed;
  /*chrome 内核 浏览器 position: fixed 防止抖动*/
  -webkit-transform: translateZ(0);
}

利用绝对定位宽高拉升原理,中心居中元素

.middle {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  margin: auto;
}

利用相对定位于 CSS3 使元素垂直居中

.v-middle {
  position: relative;
  top: 50%;
  -webkit-transform: -webkit-translateY(-50%);
  -moz-transform: -moz-translateY(-50%);
  -o-transform: -o-translateY(-50%);
  transform: translateY(-50%);
}

元素计算宽高的盒子模型以 border 为外界限「bb ==> border-box」

.bb {
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}

单行文本溢出显示省略号「to ==> text-overflow」

.to {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

初始化样式

不同的浏览器对各个标签默认的样式是不一样的,而且有时候我们也不想使用浏览器给出的默认样式,我们就可以用 reset.css 去掉其默认样式

body,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
p,
blockquote,
dl,
dt,
dd,
ul,
ol,
li,
pre,
form,
fieldset,
legend,
button,
input,
textarea,
th,
td {
  margin: 0;
  padding: 0;
}
body,
button,
input,
select,
textarea {
  font: 12px/1.5 tahoma, arial, \5b8b\4f53;
}
h1,
h2,
h3,
h4,
h5,
h6 {
  font-size: 100%;
}
address,
cite,
dfn,
em,
var {
  font-style: normal;
}
code,
kbd,
pre,
samp {
  font-family: couriernew, courier, monospace;
}
small {
  font-size: 12px;
}
ul,
ol {
  list-style: none;
}
a {
  text-decoration: none;
}
a:hover {
  text-decoration: underline;
}
sup {
  vertical-align: text-top;
}
sub {
  vertical-align: text-bottom;
}
legend {
  color: #000;
}
fieldset,
img {
  border: 0;
}
button,
input,
select,
textarea {
  font-size: 100%;
}
table {
  border-collapse: collapse;
  border-spacing: 0;
}

强制换行/自动换行/强制不换行

/* 强制不换行 */
div {
  white-space: nowrap;
}

/* 自动换行 */
div {
  word-wrap: break-word;
  word-break: normal;
}

/* 强制英文单词断行 */
div {
  word-break: break-all;
}

table 边界的样式

table {
  border: 1px solid #000;
  padding: 0;
  border-collapse: collapse;
  table-layout: fixed;
  margin-top: 10px;
}
table td {
  height: 30px;
  border: 1px solid #000;
  background: #fff;
  font-size: 15px;
  padding: 3px 3px 3px 8px;
  color: #000;
  width: 160px;
}

绝对定位与 margin

当我们提前知道要居中元素的长度和宽度时,可以使用这种方式:

.container {
  position: relative;
  width: 300px;
  height: 200px;
  border: 1px solid #333333;
}
.content {
  background-color: #ccc;
  width: 160px;
  height: 100px;
  position: absolute;
  top: 50%;
  left: 50%;
  margin-left: -80px; /* 宽度的一半 */
  margin-top: -50px; /* 高度的一半 */
}

绝对定位与 transform

当要居中的元素不定宽和定高时,我们可以使用 transform 来让元素进行偏移。

.container {
  position: relative;
  width: 300px;
  height: 200px;
  border: 1px solid #333333;
}
.content {
  background-color: #ccc;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate3d(-50%, -50%, 0);
  text-align: center;
}

line-height

line-height其实是行高,我们可以用行高来调整布局!

不过这个方案有一个比较大的缺点是:文案必须是单行的,多行的话,设置的行高就会有问题。

.container {
  width: 300px;
  height: 200px;
  border: 1px solid #333333;
}
.content {
  line-height: 200px;
}

table 布局

给容器元素设置display: table,当前元素设置display: table-cell

.container {
  width: 300px;
  height: 200px;
  border: 1px solid #333333;
  display: table;
}
.content {
  display: table-cell;
  vertical-align: middle;
  text-align: center;
}

flex 布局

我们可以给父级元素设置为display: flex,利用 flex 中的align-itemsjustify-content设置垂直方向和水平方向的居中。这种方式也不限制中间元素的宽度和高度。

同时,flex 布局也能替换line-height方案在某些 Android 机型中文字不居中的问题。

.container {
  width: 300px;
  height: 200px;
  border: 1px solid #333333;
  display: flex;
  align-items: center;
  justify-content: center;
}
.content {
  background-color: #ccc;
  text-align: center;
}

图片上下左右居中

一种常用的方式是把外层的 div 设置为 table-cell;然后让内部的元素上下左右居中。当然也还有一种方式,就是把 img 当做 div,参考 6 中的代码进行设置。
CSS 代码如下:

.content {
  width: 400px;
  height: 400px;
  border: 1px solid #ccc;
  text-align: center;
  display: table-cell;
  vertical-align: middle;
}

html 代码如下:

<div class="content">
  <img data-original="./4.jpg" alt="img" />
</div>

标题两边的小横杠

我们经常会遇到这样的 UI 需求,就是标题两边有两个小横岗,之前是怎么实现的呢?比如用个border-top属性,然后再把中间的文字进行绝对定位,同时给这个文字一个背景颜色,把中间的这部分盖住。

现在我们可以使用伪元素来实现!

<div class="title">标题</div>
title {
  color: #e1767c;
  font-size: 0.3rem;
  position: relative;

  &:before,
  &:after {
    content: "";
    position: absolute;
    display: block;
    left: 50%;
    top: 50%;
    -webkit-transform: translate3d(-50%, -50%, 0);
    transform: translate3d(-50%, -50%, 0);
    border-top: 0.02rem solid #e1767c;
    width: 0.4rem;
  }
  &:before {
    margin-left: -1.2rem;
  }
  &:after {
    margin-left: 1.2rem;
  }
}

用 border 属性绘制元素

border 除了作为简单的绘制边框以外,还可以绘制三角形,梯形,星形等任意的多边形,以下为绘制的两个三角形和梯形

<div class="triangle1"></div>
<div class="triangle2"></div>
<div class="trapezoid"></div>
.triangle1 {
  /*锐角三角形*/
  width: 0;
  height: 0;
  border-top: 50px solid transparent;
  border-bottom: 100px solid #249ff1;
  border-left: 30px solid transparent;
  border-right: 100px solid transparent;
}
.triangle2 {
  /*直角三角形*/
  width: 0;
  height: 0;
  border-top: 80px solid transparent;
  border-bottom: 80px solid #ff5b01;
  border-left: 50px solid #ff5b01;
  border-right: 50px solid transparent;
}
.trapezoid {
  /*梯形*/
  width: 0;
  height: 0;
  border-top: none;
  border-right: 80px solid transparent;
  border-bottom: 60px solid #13dbed;
  border-left: 80px solid #13dbed;
}

用 border-radius 绘制元素

border-radius主要用于绘制圆点、圆形、椭圆、圆角矩形等形状,以下为简单绘制的两个图形。

<div class="circle"></div>
<div class="ellipse"><div></div></div>
.circle,
.ellipse {
  width: 100px;
  height: 100px;
  background: #249ff1;
  border-radius: 50%;
}
.ellipse {
  width: 150px;
  background: #ff9e01;
}

border-radius属性实际上可以设置最多 8 个值,通过改变 8 个值可以得到许多意想不到的图像

用 box-shadow 绘制元素

对于box-shadow,其完整的声明为box-shadow: h-shadow v-shadow blur spread color inset,各个值表示的意义分别为:s 水平方向的偏移,垂直方向的便宜,模糊的距离(羽化值),阴影的大小(不设置或为 0 时阴影与主体的大小一致),阴影的颜色和是否使用内阴影。实际应用时可以接收 3-6 个值,对应分别如下:

  • 3 个值: h-shadow v-shadow color
  • 4 个值: h-shadow v-shadow blur color
  • 5 个值: h-shadow v-shadow blur spread color
  • 6 个值: h-shadow v-shadow blur spread color inset

同时,border-shadow接受由多个以上各种值组成的以逗号分隔的值,通过这一特性,我们可以实现如多重边框的等效果。以下我们用该属性来实现一个单标签且不借助伪元素的添加图标代表目标的的图标

<div class="plus"></div>
<div class="target"></div>
.plus {
  width: 30px;
  height: 30px;
  margin-left: 50px; /*由于box-shadow不占空间,常常需要添加margin来校正位置*/
  background: #000;
  box-shadow: 0 -30px 0 red, 0 30px 0 red, -30px 0 0 red, 30px 0 0 red;
}
.target {
  width: 30px;
  height: 30px;
  background: red;
  border-radius: 50%;
  margin-left: 50px;
  box-shadow: 0 0 0 10px #fff, 0 0 0 20px red, 0 0 0 30px #fff, 0 0 0 40px red;
}

使用 CSS 渐变来绘制图标

CSS3 的渐变属性十分强大,理论上通过渐变可以绘制出任何的图形,渐变的特性和使用足足可以写一篇长文,以下为一个例子

<div class="gradient"></div>
.gradient {
  position: relative;
  width: 300px;
  height: 300px;
  border-radius: 50%;
  background-color: silver;
  background-image: linear-gradient(335deg, #b00 23px, transparent 23px),
    linear-gradient(155deg, #d00 23px, transparent 23px), linear-gradient(
      335deg,
      #b00 23px,
      transparent 23px
    ), linear-gradient(155deg, #d00 23px, transparent 23px);
  background-size: 58px 58px;
  background-position: 0px 2px, 4px 35px, 29px 31px, 34px 6px;
}
  • 杯子
.cup {
  display: inline-block;
  width: 0.9em;
  height: 0.4em;
  border: 0.25em solid;
  border-bottom: 1.1em solid;
  border-radius: 0 0 0.25em 0.25em;
}
cup:before {
  position: absolute;
  right: -0.6em;
  top: 0;
  width: 0.3em;
  height: 0.8em;
  border: 0.25em solid;
  border-left: none;
  border-radius: 0 0.25em 0.25em 0;
  content: "";
}
  • 心形
.heart {
  display: inline-block;
  margin-top: 1.5em;
  width: 50px;
  height: 50px;
  background: green;
}
.heart:before,
.heart:after {
  position: absolute;
  width: 1em;
  height: 1.6em;
  background: #000;
  border-radius: 50% 50% 0 0;
  content: "";
  bottom: 0;
}
.heart:before {
  -webkit-transform: rotate(45deg);
  -webkit-transform-origin: 100% 100%;
  right: 0;
  background: red;
  opacity: 0.5;
  z-index: 5;
}
.:after {
  -webkit-transform: rotate(-45deg);
  -webkit-transform-origin: 0 100%;
  left: 0;
  opacity: 0.8;
}
  • 相机
.camera {
  display: inline-block;
  border-style: solid;
  border-width: 0.65em 0.9em;
  border-radius: 0.1em;
}
.camera:before {
  position: absolute;
  top: -0.3em;
  left: -0.3em;
  width: 0.4em;
  height: 0.4em;
  border-radius: 50%;
  border: 0.1em solid #fff;
  box-shadow: 0 0 0 0.08em, 0 0 0 0.16em #fff;
  content: "";
}
.camera:after {
  position: absolute;
  top: -0.5em;
  left: 0.5em;
  width: 0.2em;
  border-top: 0.125em solid #fff;
  content: "";
}
  • 月亮
.moon {
  display: inline-block;
  height: 1.5em;
  width: 1.5em;
  box-shadow: inset -0.4em 0 0;
  border-radius: 2em;
  transform: rotate(20deg);
}

浮动类

常规浮动 list 浮动 image 浮动

.float-left {
  float: left;
}
.float-right {
  float: right;
}
.float-li li,/*定义到li父元素或祖先元素上*/ li.float-li {
  float: left;
}
.float-img img,/*定义到img父元素或祖先元素上*/ img.float-li {
  float: left;
}
.float-span span,/*定义到span父元素或祖先元素上*/ span.float-span {
  float: right;
}

背景图片嵌入与定位

.bg-img {
  background-image: url("../img/bg.png");
  background-position: center top;
  background-repeat: no-repeat;
}
.bg01-img {
  background-image: url("../img/bg01.png");
  background-position: center top;
  background-repeat: no-repeat;
}
.bg02-img {
  background-image: url("../img/bg02.png");
  background-position: center top;
  background-repeat: no-repeat;
}
.bg03-img {
  background-image: url("../img/bg03.png");
  background-position: center top;
  background-repeat: no-repeat;
}
.bg04-img {
  background-image: url("../img/bg04.png");
  background-position: center top;
  background-repeat: no-repeat;
}

继承类

.inherit-width {
  width: inherit;
}
.inherit-min-width {
  min-width: inherit;
}
.inherit-height {
  height: inherit;
}
.inherit-min-height {
  min-height: inherit;
}
.inherit-color {
  color: inherit;
}

文本缩进

.text-indent {
  text-indent: 2rem;
}
/*16px*/
.text-indent-xs {
  text-indent: 1.5rem;
}
/*12px*/
.text-indent-sm {
  text-indent: 1.7rem;
}
/*14px*/
.text-indent-md {
  text-indent: 2rem;
}
/*18px*/
.text-indent-lg {
  text-indent: 2.4rem;
}
/*20px*/

行高

.line-height-xs {
  line-height: 1.3rem;
}
.line-height-sm {
  line-height: 1.5rem;
}
.line-height-md {
  line-height: 1.7rem;
}
.line-height-lg {
  line-height: 2rem;
}

.line-height-25x {
  line-height: 25px;
}
.line-height-30x {
  line-height: 30px;
}

ul 缩进

.ul-indent-xs {
  margin-left: 0.5rem;
}
.ul-indent-sm {
  margin-left: 1rem;
}
.ul-indent-md {
  margin-left: 1.5rem;
}
.ul-indent-lg {
  margin-left: 2rem;
}
.ol-list,
.ul-list {
  list-style: disc;
}

文本截断

.truncate {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.hide {
  display: none;
}

图片、视频规范

.img-max,
.video-max {
  width: 100%;
  height: auto;
}
/*display显示方式*/
.inline {
  display: inline;
}
.inline-block {
  display: inline-block;
}
.block {
  display: block;
}

边框样式

.border-xs-black {
  border: 1px solid #000;
}
.border-sm-black {
  border: 2px solid #000;
}
.border-md-black {
  border: 3px solid #000;
}
.border-lg-black {
  border: 5px solid #000;
}

.border-xs-gray {
  border: 1px solid #9c9c9c;
}
.border-sm-gray {
  border: 2px solid #9c9c9c;
}
.border-md-gray {
  border: 3px solid #9c9c9c;
}
.border-lg-gray {
  border: 5px solid #9c9c9c;
}

背景颜色

.bg-white {
  background: #fff !important;
}
.bg-black {
  background: #1b1c1d !important;
}
.bg-gray {
  background: #767676 !important;
}
.bg-light-gray {
  background: #f8f7f7 !important;
}
.bg-yellow {
  background: #fbbd08 !important;
}
.bg-orange {
  background: #f2711c !important;
}
.bg-red {
  background: #db2828 !important;
}
.bg-olive {
  background: #b5cc18 !important;
}
.bg-green {
  background: #21ba45 !important;
}
.bg-teal {
  background: #00b5ad !important;
}
.bg-darkGreen {
  background: #19a97b !important;
}
.bg-threeGreen {
  background: #097c25 !important;
}
.bg-blue {
  background: #2185d0 !important;
}
.bg-violet {
  background: #6435c9 !important;
}
.bg-purple {
  background: #a333c8 !important;
}
.bg-brown {
  background: #a5673f !important;
}

分割线预设

hr,
.hr-xs-Silver,
.hr-sm-black,
.hr-sm-Silver,
.hr-xs-gray,
.hr-sm-gray {
  margin: 20px 0;
}
hr {
  border: none;
  border-top: 1px solid #000;
}
.hr-xs-Silver {
  border: none;
  border-top: 1px solid #c0c0c0;
}
.hr-sm-black {
  border: none;
  border-top: 2px solid #000;
}
.hr-sm-Silver {
  border: none;
  border-top: 2px solid #c0c0c0;
}
.hr-xs-gray {
  border: none;
  border-top: 1px solid #767676;
}
.hr-sm-gray {
  border: none;
  border-top: 2px solid #767676;
}

鼠标 a 标签 hover 效果

.hover-red a:hover,/*为a标签祖先元素添加类名 默认无智能提醒*/ a.hover-red:hover {
  color: red;
} /*单独为a标签添加类名*/
.hover-yellow a:hover,/*为a标签祖先元素添加类名 默认无智能提醒*/ a.hover-yellow:hover {
  color: #ffd700;
} /*单独为a标签添加类名*/
.hover-green a:hover,/*为a标签祖先元素添加类名 默认无智能提醒*/ a.hover-green:hover {
  color: #70aa39;
} /*单独为a标签添加类名*/
.hover-blue a:hover,/*为a标签祖先元素添加类名 默认无智能提醒*/ a.hover-blue:hover {
  color: blue;
} /*单独为a标签添加类名*/
.hover-gray a:hover,/*为a标签祖先元素添加类名 默认无智能提醒*/ a.hover-gray:hover {
  color: #9c9c9c;
} /*单独为a标签添加类名*/
.underline a:hover,
a.underline:hover {
  text-decoration: underline;
}

阴影效果

.shadow-text-xs {
  text-shadow: 4px 3px 0 #1d9d74, 9px 8px 0 rgba(0, 0, 0, 0.15);
} /*智能兼容ie10以上 暂不考虑*/

.shadow-xs {
  -ms-filter: "progid:DXImageTransform.Microsoft.Shadow(Strength=1, Direction=100, Color='#cccccc')"; /* For IE 8 */
  filter: progid:DXImageTransform.Microsoft.Shadow(Strength=1, Direction=100, Color='#cccccc'); /* For IE 5.5 - 7 */
  -moz-box-shadow: 1px 1px 2px #cccccc; /* for firefox */
  -webkit-box-shadow: 1px 1px 2px #cccccc; /* for safari or chrome */
  box-shadow: 1px 1px 2px #cccccc; /* for opera or ie9 */
}
.shadow-sm {
  -ms-filter: "progid:DXImageTransform.Microsoft.Shadow(Strength=2, Direction=120, Color='#cccccc')"; /* For IE 8 */
  filter: progid:DXImageTransform.Microsoft.Shadow(Strength=2, Direction=120, Color='#cccccc'); /* For IE 5.5 - 7 */
  -moz-box-shadow: 2px 2px 3px #cccccc; /* for firefox */
  -webkit-box-shadow: 2px 2px 3px #cccccc; /* for safari or chrome */
  box-shadow: 2px 2px 3px #cccccc; /* for opera or ie9 */
}
.shadow-md {
  -ms-filter: "progid:DXImageTransform.Microsoft.Shadow(Strength=3, Direction=135, Color='#cccccc')"; /* For IE 8 */
  filter: progid:DXImageTransform.Microsoft.Shadow(Strength=3, Direction=135, Color='#cccccc'); /* For IE 5.5 - 7 */
  -moz-box-shadow: 3px 3px 5px #cccccc; /* for firefox */
  -webkit-box-shadow: 3px 3px 5px #cccccc; /* for safari or chrome */
  box-shadow: 3px 3px 5px #cccccc; /* for opera or ie9 */
}
.shadow-lg {
  -ms-filter: "progid:DXImageTransform.Microsoft.Shadow(Strength=5, Direction=150, Color='#cccccc')"; /* For IE 8 */
  filter: progid:DXImageTransform.Microsoft.Shadow(Strength=5, Direction=150, Color='#cccccc'); /* For IE 5.5 - 7 */
  -moz-box-shadow: 5px 5px 8px #cccccc; /* for firefox */
  -webkit-box-shadow: 5px 5px 8px #cccccc; /* for safari or chrome */
  box-shadow: 5px 5px 8px #cccccc; /* for opera or ie9 */
}

圆角

.border-radius-xs {
  -webkit-border-radius: 3px;
  -moz-border-radius: 3px;
  border-radius: 3px;
}
.border-radius-sm {
  -webkit-border-radius: 5px;
  -moz-border-radius: 5px;
  border-radius: 5px;
}
.border-radius-md {
  -webkit-border-radius: 7px;
  -moz-border-radius: 5px;
  border-radius: 5px;
}
.border-radius-lg {
  -webkit-border-radius: 9px;
  -moz-border-radius: 9px;
  border-radius: 9px;
}

如果文章和笔记能带您一丝帮助或者启发,请不要吝啬你的赞和收藏,你的肯定是我前进的最大动力?

参考文章

查看原文

winbug 赞了文章 · 2020-03-03

阿里等大厂的研发流程,进去前先了解一下

本文 GitHubhttps://github.com/JavaFamily 已收录,有一线大厂面试完整考点和系列文章。

前言

我的读者好像学生居多,然后大家最近问的比较多的一个话题就是大厂的研发流程,都比较好奇,整个流程是怎么操作的。

我也不多BB了,那下面就跟随暖男的脚步,走进大厂研发流程吧。

正文

我们先看看一个产品有哪些研发流程,帅丙就用自己接触的阿里系的研发流程举例了,这也基本上是互联网大厂的研发流程了,可能细节有出入,但是绝对大同小异。

我问了下字节,多多,腾讯的朋友出入不大,所以还是具有代表性。

看完流程我们就一个个点的去看看每个环节干了些啥,我们开发同学在这个环节需要做啥,以及在每个环节的职能。

需求提出:

这个环节主要是产品爸爸给我们提需求,每个需求都是他们从用户,或者自己绞尽脑汁想出来的,但是产品爸爸还拿不准,不能直接敲定,所以就需要我们大家(产品,UI,前端,后端,客户端和测试)一起讨论一下,看看这个需求是否合理,或者这个需求是否有意义,能否达到预期,技术实现的成本,周期等等。

一旦聊成了,他们就会进入下一个阶段,聊不成他会想方设法让你答应,然后进入下个阶段,知道我为啥叫产品爸爸了吧?

需求PRD提出:

这个阶段,产品爸爸会根据第一版聊下来的结果,大致出一个Demo版本的PRD,会画出初版的原型图,并且配上文字说明,所有涉及到的业务,还有交互细节都会罗列出来。

大致就是下图这样:

这个时候大家又会围绕这一版本去开会讨论,敲定细节,这个环节会久点,因为细节比较认真,逻辑也不能出错,还有UI稿子也得敲定,这里如果不敲定逻辑,UI提前去画原型图,后面假如逻辑推翻,一切重来就会浪费大量时间。

这一环节大家都会把细节问清楚,不了解的点也会去了解,测试,开发,UI我们都会在会议上提出自己的观点,自己的意见,然后等产品反馈,最后意见一致之后,产品当天就回改出敲定版本。

UI就会按照产品爸爸的意思去作图,接下来就是交互设计评审了。

交互设计评审:

UI会画出客户端,前端,H5开发所需要的UI图,基本上就是我们看到的产品的样子了,不过还是要敲定细节,比如按钮合理不,或者上面数据是否在这展示,或者这里展示的数据是否合理。

这个环节会比较快,只要UI按照之前敲定的逻辑开发,出入不会很大,一般都是小改。

但是也不乏很多,之前敲定了情况,等UI按照敲定版本出了图,但是却发现出图之后有些不合理的点,比如是否应该在这里展示GMV(销售总额),或者是否这样展示活动规则啥的,会有这种情况,不过是小概率事件,改动也不会特别大。

UI界面:

大家看到的这种操作界面,按钮,图标的各种位置和图案,都是UI在这个阶段设计好的。(我什么都没暗示,不用关注我的B站

大家敲定后就进入我们开发人员的回合了。

概要设计:

概要设计,这个是大厂程序员需求下来之后基本上都会做的一步,不过看需求大小,可能很多小需求直接就详细设计了,也有啥设计都不用做的小改动,具体需求具体分析嘛。

很多不了解的同学可能会问,需要设计什么呢?为什么要设计呢?

问得好,经常看我文章的都知道,技术是把双刃剑,你用了技术之后你是不是需要列出他的优点缺点,出问题之后的解决方案,还有可能出现的问题注意点等等。

这么是为了让你能有把控力,比如你这个需求接入了新技术EsElasticsearch)你什么都不管你就是要接入它,你把他开发好了上线了,但是有啥坑你知道么?上线崩了怎么办?

不主动,不拒绝,不负责,这是渣男的行径,我们需要负起责任。

这个环节你需要考虑这个需求涉及到哪些服务了,需要新增哪些接口,修改哪些接口,表有现场的还是要新建表,字段要新建么?

其实远远不止这些问题,这就是我们做设计的主要原因,也是大家工作里面能成长的途径之一,你以为大佬们的经验是怎么来的?

推荐工具:Xmind/ProcessOn

ProcessOn是我使用最频繁的工具了,我身边也有很多小伙伴在用,也推荐大家都使用:

大家在学习,看书等等的时候做个脑图,后面学习和复习的时候思路会很清晰,而且效率瞬间很多,形成知识体系。

概要设计一般就是做个大概,给大家看一下我自己在设计ES相关的需求的时候的概设,比较粗糙看个大概就好了:

这个设计好了,就需要给Leader看,看理解程度,一两次返工是有可能的,如果你像或者像敖丙一样笨的话,是有可能会被打回N次的,这里我得提一下,好好做设计好处大大的有,自己体会。

然后会进行一轮测试用例评审,比如你涉及哪些服务,新增了哪些接口,改了哪些接口,都是要同步出来的,至于为啥?

是因为测试会依据这个数据,评估影响范围,方便他写测试用例,后面会提到。

详细设计

小伙伴又要问了啥是详细设计呀帅丙

傻瓜,简单呀,见名知意嘛,概要设计是大概的设计,详细设计是详细的设计。

我们研发的时候整个流程往往很复杂,如果你理解不对直接就写代码,最后容易造成返工,延期,加班,被骂,心情差,回家吵架,离家出走,露宿街头,饥寒交迫,被迫吃野味,然后全国。。。。

看到不做详细设计的后果了吧,其实大家花点时间做详细设计很有必要,你思路完全清晰了,写代码那就是分分钟的事情,不是嘛?

那再看看帅丙的一个小设计吧,之前文章中大量的流程图,时序图都来自它,主要是这玩意还是在线的,都不用下载很方便啊。

详细设计的工具我用的就是之前提到的在线作图神器:ProcessOn https://www.processon.com

还是我自己之前设计的一些流程图,大家可以看看:

这个环节一样重要,这个地方如果你能想好很多细节,开发的时候效率会高很多,像我上面的一些点,基本上就是看着图开发了。

这个环节一般上不需要Leader参与,但是如果你有疑问或者不了解的点还是要提出来的。

测试用例评审:

上面我们说过,测试会根据你的概要设计,评估你的影响范围,你的影响点,新增和改动的接口啥的,去编写自己的测试用例。

测试用例,主要是为了把改动点影响点都考虑到,测全一点,免得上线了影响别的现有业务,也是为了把你开发的功能可能出现的bug给排除了。

我拿个小破站的小用例大家看看,这个比较粗糙但是也有点那味了。

这个环节也会开会讨论,也是细节的确定,比如他写的是否合理,或者有什么点没考虑到,大家有没有补充的。

接口定义&开发&前后端联调

这个环节其实比较好理解,啥都敲定了,那就开发呗,开发差不多了,就得前后端联调了。

这里有个小细节还是想说一下,一般开发前我们都会提前定义数据类型,接口名称,然后在公司的接口工具上给出链接和参数,方便前端爸爸mock数据。

他总不能等我们后端开发完了,才去开发嘛,这样效率打折扣,所以都是后端先定义好,然后前后端并行开发的。

后端开发好,一般都是会发布到联调环境,我们有哪些环境,联调环境在我们所有的环境中处于哪个地位呢?

大家可以看到我列出了我们开发的所有环境。

Tip:日常环境不能由开发人员发布,是因为测试流程比较久,所以不能中断,如果你一直发布会影响测试的效率,在发布期间他们是没办法干活的,而且很多部门涉及相同的服务,你发布还会影响别人。

测试发布之前,在测试群里问问可以发某个服务么,大家觉得不影响,那么就可以发了,懂了吧。

预发环境,也叫灰度环境,这是跟线上数据一样的一个环境,只是只能内网访问,一般这一步是防止很多是因为日常的数据量不够真实,数据级别达不到线上的量级无法测出的bug。

扯远了,联调完了就是代码Review了。

代码Review:

codeReview环节,画一下重点,这可能是整个研发流程中,让你成长最快的一个环节,让组员和Leader Review你的代码,往往他们能给你很多业务上和技术上的建议和意见。

过来人的经验你就说香不香吧,以前老大经常没时间,但是我就是烦着他要Review,后来他说不用review了,但是我还是要组员大佬review,因为我很享受别人对我提建议的时候,这不就是成长,扫盲的好时机嘛。

提测&灰度发布&产品第一次验收

这一阶段就是把代码都发到日常环境,然后等测试爸爸测试,这个环节开发同学如果没BUG是比较轻松的,等着就好了,可以看看丙丙的文章啊,看看丙丙的B站视频什么的。

但是如果你BUG多,那我觉得你可能会生不如死,因为有的bug真的找很久很久的,调用链路又长,特别是跨服务又涉及消息队列,或者第三方的接口什么的。

img

img

总之你也不知道会出现什么bug,我看身边的大神也只能用经验避免常见的吭,孰能生巧吧。

发布计划

敲黑板,这个确实是比较重要的环节,这个环节主要是开发同学和前端同学说好一个发布时间,然后制定一个发布计划,为啥要发布计划呢?

我们开发一个需求,可能涉及到N个服务,这些服务是有依赖关系的,那就需要打包,比如订单系统,依赖人员系统。优惠券系统,也依赖人员系统,然后订单系统还依赖优惠券系统,是不是有点乱了?

我们看图:

打包和发布顺序原则上是一样的,从没完全依赖的服务按照顺序发布到最后一个服务。

生成环境上线:

这就是神圣而庄严的上线环节,一般在这个环节丙丙都是要洗手洗澡,然后才点下那个神圣的发布按钮。

一般现在都是自动化发布,界面上点点就好了,记得丙丙大学发布都是进服务器一个个kill进程,替换jar包然后重启。

现在都是分布式的集群,这样发无疑会累死,我之前负责的系统有50多台机器,一般都是4台4台发布。

日志观察&产品第二次验收

一般发布第一批之后不会马上发布第二批,而是观察错误日志,看看是否正常,有时候会发现还是会出现异常情况的,那就保留错误日志,然后回滚。

知道解决了再发布,顺利的话就没啥错误,一口气发完了,看了下时间凌晨了,那发完差不多也得回家了。

一次发布可能涉及服务多的话,真的有可能发布这么久,但是没办法,线上出问题就是掉脑袋的事情。

日志观察一般公司都有错误日志搜集系统,或者自己登录跳板机查看就好了。

没问题,发完之后告诉产品大大就好了。

需求结束

至此基本上一个需求可能就结束了,其实还是很不容易的,短的需求几天,长的需求几个月,中间涂涂改改,BUG,技术难点都是你要面对的,不过没啥大问题,我们技术人嘛皮实能顶。

总结

产品研发流程大家是不是觉得有点复杂,或者觉得很多点有点小题大做了,不瞒你说,刚开始我也这么认为的,但是随着时间的推移,你会发现有时候越是这样规范,越是提升了效率,也提升了产品质量。

对自己设计的严苛也会让你的业务能力提升,开发考虑的点也越来越广泛,我想大佬应该都是这样走过去的,那没啥好说的,我们也走。

最后给大家看看我自己搞的一个项目管理模板吧,基本上能适用大部分项目了,要xmind格式的公众号回复【项目】即可。

我是敖丙,一个在互联网苟且偷生的程序员。

创作不易,不想被白嫖,各位的「三连」就是丙丙创作的最大动力,我们下篇文章见!


本文 GitHubhttps://github.com/JavaFamily 已经收录,有大厂面试完整考点,欢迎Star。

你知道的越多,你不知道的越多

查看原文

赞 165 收藏 107 评论 8

winbug 收藏了文章 · 2019-11-05

灵活运用CSS开发技巧

作者:JowayYoung
仓库:GithubCodePen
博客:掘金思否知乎简书头条CSDN
公众号:IQ前端
联系我:关注公众号后有我的微信
特别声明:原创不易,未经授权不得对此文章进行转载或抄袭,否则按侵权处理,如需转载或开通公众号白名单可联系我,希望各位尊重原创的知识产权

系列

前言

何为技巧,意指表现在文学、工艺、体育等方面的巧妙技能。代码作为一门现代高级工艺,推动着人类科学技术的发展,同时犹如文字一样承托着人类文化的进步。

每写好一篇文章,都会使用大量的写作技巧。烘托、渲染、悬念、铺垫、照应、伏笔、联想、想象、抑扬结合、点面结合、动静结合、叙议结合、情景交融、首尾呼应、衬托对比、白描细描、比喻象征、借古讽今、卒章显志、承上启下、开门见山、动静相衬、虚实相生、实写虚写、托物寓意、咏物抒情等,这些应该都是我们从小到大写文章而接触到的写作技巧。

作为程序猿的我们,写代码同样也需要大量的写作技巧。一份良好的代码能让人耳目一新,让人容易理解,让人舒服自然,同时也让自己成就感满满(哈哈,这个才是重点)。因此,我整理下三年来自己使用到的一些CSS开发技巧,希望能让你写出耳目一新、容易理解、舒服自然的代码。

目录

既然写文章有这么多的写作技巧,那么我也需要对CSS开发技巧整理一下,起个易记的名字。

  • Layout Skill:布局技巧
  • Behavior Skill:行为技巧
  • Color Skill:色彩技巧
  • Figure Skill:图形技巧
  • Component Skill:组件技巧

备注

  • 代码只作演示用途,不会详细说明语法
  • 部分技巧示例代码过长,使用CodePen进行保存,点击在线演示即可查看
  • 兼容项点击链接即可查看当前属性的浏览器兼容数据,自行根据项目兼容需求考虑是否使用
  • 以下代码全部基于CSS进行书写,没有任何JS代码,没有特殊说明的情况下所有属性和方法都是CSS类型
  • 一部分技巧是自己探讨出来的,另一部分技巧是参考各位前端大神们的,都是一个互相学习的过程,大家一起进步

Layout Skill

使用vw定制rem自适应布局
  • 要点:移动端使用rem布局需要通过JS设置不同屏幕宽高比的font-size,结合vw单位和calc()可脱离JS的控制
  • 场景:rem页面布局(不兼容低版本移动端系统)
  • 兼容:vwcalc())
/* 基于UI width=750px DPR=2的页面 */
html {
    font-size: calc(100vw / 7.5);
}
使用:nth-child()选择指定元素
  • 要点:通过:nth-child()筛选指定的元素设置样式
  • 场景:表格着色边界元素排版(首元素、尾元素、左右两边元素)
  • 兼容::nth-child())
  • 代码:在线演示

在线演示

使用writing-mode排版竖文
  • 要点:通过writing-mode调整文本排版方向
  • 场景:竖行文字文言文诗词
  • 兼容:writing-mode
  • 代码:在线演示

在线演示

使用text-align-last对齐两端文本
  • 要点:通过text-align-last:justify设置文本两端对齐
  • 场景:未知字数中文对齐
  • 兼容:text-align-last
  • 代码:在线演示

在线演示

使用:not()去除无用属性
  • 要点:通过:not()排除指定元素不使用设置样式
  • 场景:符号分割文字边界元素排版(首元素、尾元素、左右两边元素)
  • 兼容::not())
  • 代码:在线演示

在线演示

使用object-fit规定图像尺寸
  • 要点:通过object-fit使图像脱离background-size的约束,使用<img>来标记图像背景尺寸
  • 场景:图片尺寸自适应
  • 兼容:object-fit
  • 代码:在线演示

在线演示

使用overflow-x排版横向列表
  • 要点:通过flexboxinline-block的形式横向排列元素,对父元素设置overflow-x:auto横向滚动查看
  • 场景:横向滚动列表元素过多但位置有限的导航栏
  • 兼容:overflow-x
  • 代码:在线演示

在线演示

使用text-overflow控制文本溢出

在线演示

使用transform描绘1px边框
  • 要点:分辨率比较低的屏幕下显示1px的边框会显得模糊,通过::before::aftertransform模拟细腻的1px边框
  • 场景:容器1px边框
  • 兼容:transform
  • 代码:在线演示

在线演示

使用transform翻转内容
  • 要点:通过transform:scale3d()对内容进行翻转(水平翻转、垂直翻转、倒序翻转)
  • 场景:内容翻转
  • 兼容:transform
  • 代码:在线演示

在线演示

使用letter-spacing排版倒序文本
  • 要点:通过letter-spacing设置负值字体间距将文本倒序
  • 场景:文言文诗词
  • 兼容:letter-spacing
  • 代码:在线演示

在线演示

使用margin-left排版左重右轻列表
  • 要点:使用flexbox横向布局时,最后一个元素通过margin-left:auto实现向右对齐
  • 场景:右侧带图标的导航栏
  • 兼容:margin
  • 代码:在线演示

在线演示

Behavior Skill

使用overflow-scrolling支持弹性滚动
  • 要点:iOS页面非body元素的滚动操作会非常卡(Android不会出现此情况),通过overflow-scrolling:touch调用Safari原生滚动来支持弹性滚动,增加页面滚动的流畅度
  • 场景:iOS页面滚动
  • 兼容:iOS自带-webkit-overflow-scrolling
body {
    -webkit-overflow-scrolling: touch;
}
.elem {
    overflow: auto;
}
使用transform启动GPU硬件加速
  • 要点:有时执行动画可能会导致页面卡顿,可在特定元素中使用硬件加速来避免这个问题
  • 场景:动画元素(绝对定位、同级中超过6个以上使用动画)
  • 兼容:transform
.elem {
    transform: translate3d(0, 0, 0); /* translateZ(0)亦可 */
}
使用attr()抓取data-*
  • 要点:在标签上自定义属性data-*,通过attr()获取其内容赋值到content
  • 场景:提示框
  • 兼容:data-*attr())
  • 代码:在线演示

在线演示

使用:valid和:invalid校验表单

在线演示

使用pointer-events禁用事件触发
  • 要点:通过pointer-events:none禁用事件触发(默认事件、冒泡事件、鼠标事件、键盘事件等),相当于<button>disabled
  • 场景:限时点击按钮(发送验证码倒计时)、事件冒泡禁用(多个元素重叠且自带事件、a标签跳转)
  • 兼容:pointer-events
  • 代码:在线演示

在线演示

使用+或~美化选项框
  • 要点:<label>使用+~配合for绑定radiocheckbox的选择行为
  • 场景:选项框美化选中项增加选中样式
  • 兼容:+~
  • 代码:在线演示

在线演示

使用:focus-within分发冒泡响应

在线演示

使用:hover描绘鼠标跟随
  • 要点:将整个页面等比划分成小的单元格,每个单元格监听:hover,通过:hover触发单元格的样式变化来描绘鼠标运动轨迹
  • 场景:鼠标跟随轨迹水波纹怪圈
  • 兼容::hover
  • 代码:在线演示

在线演示

使用max-height切换自动高度
  • 要点:通过max-height定义收起的最小高度和展开的最大高度,设置两者间的过渡切换
  • 场景:隐藏式子导航栏悬浮式折叠面板
  • 兼容:max-height
  • 代码:在线演示

在线演示

使用transform模拟视差滚动

在线演示

使用animation-delay保留动画起始帧
  • 要点:通过transform-delayanimation-delay设置负值时延保留动画起始帧,让动画进入页面不用等待即可运行
  • 场景:开场动画
  • 兼容:transformanimation
  • 代码:在线演示

在线演示

使用resize拉伸分栏
  • 要点:通过resize设置横向自由拉伸来调整目标元素的宽度
  • 场景:富文本编辑器分栏阅读
  • 兼容:resize
  • 代码:在线演示

在线演示

Color Skill

使用color改变边框颜色
  • 要点:border没有定义border-color时,设置color后,border-color会被定义成color
  • 场景:边框颜色与文字颜色相同
  • 兼容:color
.elem {
    border: 1px solid;
    color: #f66;
}

在线演示

使用filter开启悼念模式
  • 要点:通过filter:grayscale()设置灰度模式来悼念某位去世的仁兄或悼念因灾难而去世的人们
  • 场景:网站悼念
  • 兼容:filter
  • 代码:在线演示

在线演示

使用::selection改变文本选择颜色
  • 要点:通过::selection根据主题颜色自定义文本选择颜色
  • 场景:主题化
  • 兼容:::selection
  • 代码:在线演示

在线演示

使用linear-gradient控制背景渐变
  • 要点:通过linear-gradient设置背景渐变色并放大背景尺寸,添加背景移动效果
  • 场景:主题化彩虹背景墙
  • 兼容:gradientanimation
  • 代码:在线演示

在线演示

使用linear-gradient控制文本渐变

在线演示

使用caret-color改变光标颜色
  • 要点:通过caret-color根据主题颜色自定义光标颜色
  • 场景:主题化
  • 兼容:caret-color
  • 代码:在线演示

在线演示

使用::scrollbar改变滚动条样式
  • 要点:通过scrollbarscrollbar-trackscrollbar-thumb等属性来自定义滚动条样式
  • 场景:主题化页面滚动
  • 兼容:::scrollbar
  • 代码:在线演示

在线演示

使用filter模拟Instagram滤镜
  • 要点:通过filter的滤镜组合起来模拟Instagram滤镜
  • 场景:图片滤镜
  • 兼容:filter
  • 代码:在线演示css-gram

在线演示

Figure Skill

使用div描绘各种图形
  • 要点:<div>配合其伪元素(::before::after)通过cliptransform等方式绘制各种图形
  • 场景:各种图形容器
  • 兼容:cliptransform
  • 代码:在线演示
使用mask雕刻镂空背景

在线演示

使用linear-gradient描绘波浪线
  • 要点:通过linear-gradient绘制波浪线
  • 场景:文字强化显示文字下划线内容分割线
  • 兼容:gradient
  • 代码:在线演示

在线演示

使用linear-gradient描绘彩带
  • 要点:通过linear-gradient绘制间断颜色的彩带
  • 场景:主题化
  • 兼容:gradient
  • 代码:在线演示

在线演示

使用conic-gradient描绘饼图
  • 要点:通过conic-gradient绘制多种色彩的饼图
  • 场景:项占比饼图
  • 兼容:gradient
  • 代码:在线演示

在线演示

使用linear-gradient描绘方格背景
  • 要点:使用linear-gradient绘制间断颜色的彩带进行交互生成方格
  • 场景:格子背景占位图
  • 兼容:gradient
  • 代码:在线演示

在线演示

使用box-shadow描绘单侧投影

在线演示

使用filter描绘头像彩色阴影
  • 要点:通过filter:blur() brightness() opacity()模拟阴影效果
  • 场景:头像阴影
  • 兼容:filter
  • 代码:在线演示

在线演示

使用box-shadow裁剪图像
  • 要点:通过box-shadow模拟蒙层实现中间镂空
  • 场景:图片裁剪新手引导背景镂空投射定位
  • 兼容:box-shadow
  • 代码:在线演示

在线演示

使用outline描绘内边框
  • 要点:通过outline设置轮廓进行描边,可设置outline-offset设置内描边
  • 场景:内描边外描边
  • 兼容:outline
  • 代码:在线演示

在线演示

Component Skill

迭代计数器

在线演示

下划线跟随导航栏
  • 要点:下划线跟随鼠标移动的导航栏
  • 场景:动态导航栏
  • 兼容:+
  • 代码:在线演示

在线演示

气泡背景墙

在线演示

滚动指示器

在线演示

故障文本

在线演示

换色器

在线演示

状态悬浮球

在线演示

粘粘球

在线演示

商城票券
  • 要点:边缘带孔和中间折痕的票劵
  • 场景:电影票代金券消费卡
  • 兼容:gradient
  • 代码:在线演示

在线演示

倒影加载条

在线演示

三维立方体

在线演示

动态边框
  • 要点:鼠标悬浮时动态渐变显示的边框
  • 场景:悬浮按钮边框动画
  • 兼容:gradient
  • 代码:在线演示

在线演示

标签页

在线演示

标签导航栏
  • 要点:可切换内容的导航栏
  • 场景:页面切换
  • 兼容:~
  • 代码:在线演示

在线演示

折叠面板
  • 要点:可折叠内容的面板
  • 场景:隐藏式子导航栏
  • 兼容:~
  • 代码:在线演示

在线演示

星级评分
  • 要点:点击星星进行评分的按钮
  • 场景:评分
  • 兼容:~
  • 代码:在线演示

在线演示

加载指示器

在线演示

自适应相册

在线演示

圆角进度条

在线演示

螺纹进度条

在线演示

立体按钮

在线演示

混沌加载圈

在线演示

蛇形边框

在线演示

自动打字
  • 要点:逐个字符自动打印出来的文字
  • 场景:代码演示文字输入动画
  • 兼容:chanimation
  • 代码:在线演示

在线演示

总结

写到最后总结得差不多了,如果后续我想起还有哪些遗漏的CSS开发技巧,会继续在这篇文章上补全。

最后送大家一个键盘!

(_=>[..."`1234567890-=~~QWERTYUIOP[]\\~ASDFGHJKL;'~~ZXCVBNM,./~"].map(x=>(o+=`/${b='_'.repeat(w=x<y?2:' 667699'[x=["Bs","Tab","Caps","Enter"][p++]||'Shift',p])}\\|`,m+=y+(x+'    ').slice(0,w)+y+y,n+=y+b+y+y,l+=' __'+b)[73]&&(k.push(l,m,n,o),l='',m=n=o=y),m=n=o=y='|',p=l=k=[])&&k.join`
`)()

结语

❤️关注+点赞+收藏+评论+转发❤️,原创不易,鼓励笔者创作更好的文章

关注公众号IQ前端,一个专注于CSS/JS开发技巧的前端公众号,更多前端小干货等着你喔

  • 关注后回复关键词免费领取视频教程
  • 关注后添加我微信拉你进技术交流群
  • 欢迎关注IQ前端,更多CSS/JS开发技巧只在公众号推送

查看原文

winbug 收藏了文章 · 2019-09-24

如何写出优雅耐看的JavaScript代码

前言

在我们平时的工作开发中,大多数都是大人协同开发的公共项目;在我们平时开发中代码codeing的时候我们考虑代码的可读性复用性扩展性

干净的代码,既在质量上较为可靠,也为后期维护、升级奠定了良好基础。

我们从以下几个方面进行探讨:

变量

1、变量命名

一般我们在定义变量是要使用有意义的词汇命令,要做到见面知义

//bad code
const yyyymmdstr = moment().format('YYYY/MM/DD');
//better code
const currentDate = moment().format('YYYY/MM/DD');

2、可描述

通过一个变量生成了一个新变量,也需要为这个新变量命名,也就是说每个变量当你看到他第一眼你就知道他是干什么的。

//bad code
const ADDRESS = 'One Infinite Loop, Cupertino 95014';
const CITY_ZIP_CODE_REGEX = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
saveCityZipCode(ADDRESS.match(CITY_ZIP_CODE_REGEX)[1], ADDRESS.match(CITY_ZIP_CODE_REGEX)[2]);

//better code
const ADDRESS = 'One Infinite Loop, Cupertino 95014';
const CITY_ZIP_CODE_REGEX = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
const [, city, zipCode] = ADDRESS.match(CITY_ZIP_CODE_REGEX) || [];
saveCityZipCode(city, zipCode);

3、形参命名

在for、forEach、map的循环中我们在命名时要直接

//bad code
const locations = ['Austin', 'New York', 'San Francisco'];
locations.map((l) => {
  doStuff();
  doSomeOtherStuff();
  // ...
  // ...
  // ...
  // 需要看其他代码才能确定 'l' 是干什么的。
  dispatch(l);
});

//better code
const locations = ['Austin', 'New York', 'San Francisco'];
locations.forEach((location) => {
  doStuff();
  doSomeOtherStuff();
  // ...
  // ...
  // ...
  dispatch(location);
});

4、避免无意义的前缀

例如我们只创建一个对象是,没有必要再把每个对象的属性上再加上对象名

//bad code
const car = {
  carMake: 'Honda',
  carModel: 'Accord',
  carColor: 'Blue'
};

function paintCar(car) {
  car.carColor = 'Red';
}

//better code
const car = {
  make: 'Honda',
  model: 'Accord',
  color: 'Blue'
};

function paintCar(car) {
  car.color = 'Red';
}

5、默认值

//bad code
function createMicrobrewery(name) {
  const breweryName = name || 'Hipster Brew Co.';
  // ...
}

//better code
function createMicrobrewery(name = 'Hipster Brew Co.') {
  // ...
}

函数

1、参数

一般参数多的话要使用ES6的解构传参的方式

//bad code
function createMenu(title, body, buttonText, cancellable) {
  // ...
}

//better code
function createMenu({ title, body, buttonText, cancellable }) {
  // ...
}

//better code
createMenu({
  title: 'Foo',
  body: 'Bar',
  buttonText: 'Baz',
  cancellable: true
});

2、单一化处理

一个方法里面最好只做一件事,不要过多的处理,这样代码的可读性非常高

//bad code
function emailClients(clients) {
  clients.forEach((client) => {
    const clientRecord = database.lookup(client);
    if (clientRecord.isActive()) {
      email(client);
    }
  });
}

//better code
function emailActiveClients(clients) {
  clients
    .filter(isActiveClient)
    .forEach(email);
}
function isActiveClient(client) {
  const clientRecord = database.lookup(client);    
  return clientRecord.isActive();
}

3、对象设置默认属性

//bad code
const menuConfig = {
  title: null,
  body: 'Bar',
  buttonText: null,
  cancellable: true
};
function createMenu(config) {
  config.title = config.title || 'Foo';
  config.body = config.body || 'Bar';
  config.buttonText = config.buttonText || 'Baz';
  config.cancellable = config.cancellable !== undefined ? config.cancellable : true;
}
createMenu(menuConfig);


//better code
const menuConfig = {
  title: 'Order',
  // 'body' key 缺失
  buttonText: 'Send',
  cancellable: true
};

function createMenu(config) {
  config = Object.assign({
    title: 'Foo',
    body: 'Bar',
    buttonText: 'Baz',
    cancellable: true
  }, config);

  // config 就变成了: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
  // ...
}

createMenu(menuConfig);

4、避免副作用

函数接收一个值返回一个新值,除此之外的行为我们都称之为副作用,比如修改全局变量、对文件进行 IO 操作等。

当函数确实需要副作用时,比如对文件进行 IO 操作时,请不要用多个函数/类进行文件操作,有且仅用一个函数/类来处理。也就是说副作用需要在唯一的地方处理。

副作用的三大天坑:随意修改可变数据类型、随意分享没有数据结构的状态、没有在统一地方处理副作用。

//bad code
// 全局变量被一个函数引用
// 现在这个变量从字符串变成了数组,如果有其他的函数引用,会发生无法预见的错误。
var name = 'Ryan McDermott';
function splitIntoFirstAndLastName() {
  name = name.split(' ');
}
splitIntoFirstAndLastName();
console.log(name); // ['Ryan', 'McDermott'];


//better code
var name = 'Ryan McDermott';
var newName = splitIntoFirstAndLastName(name)

function splitIntoFirstAndLastName(name) {
  return name.split(' ');
}

console.log(name); // 'Ryan McDermott';
console.log(newName); // ['Ryan', 'McDermott'];

在 JavaScript 中,基本类型通过赋值传递,对象和数组通过引用传递。以引用传递为例:

假如我们写一个购物车,通过 addItemToCart()方法添加商品到购物车,修改 购物车数组。此时调用 purchase()方法购买,由于引用传递,获取的 购物车数组正好是最新的数据。

看起来没问题对不对?

如果当用户点击购买时,网络出现故障, purchase()方法一直在重复调用,与此同时用户又添加了新的商品,这时网络又恢复了。那么 purchase()方法获取到 购物车数组就是错误的。

为了避免这种问题,我们需要在每次新增商品时,克隆 购物车数组并返回新的数组。

//bad code
const addItemToCart = (cart, item) => {
  cart.push({ item, date: Date.now() });
};

//better code
const addItemToCart = (cart, item) => {
  return [...cart, {item, date: Date.now()}]
};

5、全局方法

在 JavaScript 中,永远不要污染全局,会在生产环境中产生难以预料的 bug。举个例子,比如你在 Array.prototype上新增一个 diff方法来判断两个数组的不同。而你同事也打算做类似的事情,不过他的 diff方法是用来判断两个数组首位元素的不同。很明显你们方法会产生冲突,遇到这类问题我们可以用 ES2015/ES6 的语法来对 Array进行扩展。

//bad code
Array.prototype.diff = function diff(comparisonArray) {
  const hash = new Set(comparisonArray);
  return this.filter(elem => !hash.has(elem));
};

//better code
class SuperArray extends Array {
  diff(comparisonArray) {
    const hash = new Set(comparisonArray);
    return this.filter(elem => !hash.has(elem));        
  }
}

6、避免类型检查

JavaScript 是无类型的,意味着你可以传任意类型参数,这种自由度很容易让人困扰,不自觉的就会去检查类型。仔细想想是你真的需要检查类型还是你的 API 设计有问题?

//bad code
function travelToTexas(vehicle) {
  if (vehicle instanceof Bicycle) {
    vehicle.pedal(this.currentLocation, new Location('texas'));
  } else if (vehicle instanceof Car) {
    vehicle.drive(this.currentLocation, new Location('texas'));
  }
}

//better code
function travelToTexas(vehicle) {
  vehicle.move(this.currentLocation, new Location('texas'));
}

如果你需要做静态类型检查,比如字符串、整数等,推荐使用 TypeScript,不然你的代码会变得又臭又长。

//bad code
function combine(val1, val2) {
  if (typeof val1 === 'number' && typeof val2 === 'number' ||
      typeof val1 === 'string' && typeof val2 === 'string') {
    return val1 + val2;
  }

  throw new Error('Must be of type String or Number');
}

//better code
function combine(val1, val2) {
  return val1 + val2;
}

复杂条件判断

我们编写js代码时经常遇到复杂逻辑判断的情况,通常大家可以用if/else或者switch来实现多个条件判断,但这样会有个问题,随着逻辑复杂度的增加,代码中的if/else/switch会变得越来越臃肿,越来越看不懂,那么如何更优雅的写判断逻辑

1、if/else

点击列表按钮事件

/**
 * 按钮点击事件
 * @param {number} status 活动状态:1 开团进行中 2 开团失败 3 商品售罄 4 开团成功 5 系统取消
 */
const onButtonClick = (status)=>{
  if(status == 1){
    sendLog('processing')
    jumpTo('IndexPage')
  }else if(status == 2){
    sendLog('fail')
    jumpTo('FailPage')
  }else if(status == 3){
    sendLog('fail')
    jumpTo('FailPage')
  }else if(status == 4){
    sendLog('success')
    jumpTo('SuccessPage')
  }else if(status == 5){
    sendLog('cancel')
    jumpTo('CancelPage')
  }else {
    sendLog('other')
    jumpTo('Index')
  }
}

从上面我们可以看到的是通过不同的状态来做不同的事情,代码看起来非常不好看,大家可以很轻易的提出这段代码的改写方案,switch出场:

2、switch/case

/**
 * 按钮点击事件
 * @param {number} status 活动状态:1 开团进行中 2 开团失败 3 商品售罄 4 开团成功 5 系统取消
 */
const onButtonClick = (status)=>{
  switch (status){
    case 1:
      sendLog('processing')
      jumpTo('IndexPage')
      break
    case 2:
    case 3:
      sendLog('fail')
      jumpTo('FailPage')
      break  
    case 4:
      sendLog('success')
      jumpTo('SuccessPage')
      break
    case 5:
      sendLog('cancel')
      jumpTo('CancelPage')
      break
    default:
      sendLog('other')
      jumpTo('Index')
      break
  }
}

这样看起来比if/else清晰多了,细心的同学也发现了小技巧,case 2和case 3逻辑一样的时候,可以省去执行语句和break,则case 2的情况自动执行case 3的逻辑。

3、存放到Object

将判断条件作为对象的属性名,将处理逻辑作为对象的属性值,在按钮点击的时候,通过对象属性查找的方式来进行逻辑判断,这种写法特别适合一元条件判断的情况。

const actions = {
  '1': ['processing','IndexPage'],
  '2': ['fail','FailPage'],
  '3': ['fail','FailPage'],
  '4': ['success','SuccessPage'],
  '5': ['cancel','CancelPage'],
  'default': ['other','Index'],
}
/**
 * 按钮点击事件
 * @param {number} status 活动状态:1开团进行中 2开团失败 3 商品售罄 4 开团成功 5 系统取消
 */
const onButtonClick = (status)=>{
  let action = actions[status] || actions['default'],
      logName = action[0],
      pageName = action[1]
  sendLog(logName)
  jumpTo(pageName)
}

4、存放到Map

const actions = new Map([
  [1, ['processing','IndexPage']],
  [2, ['fail','FailPage']],
  [3, ['fail','FailPage']],
  [4, ['success','SuccessPage']],
  [5, ['cancel','CancelPage']],
  ['default', ['other','Index']]
])
/**
 * 按钮点击事件
 * @param {number} status 活动状态:1 开团进行中 2 开团失败 3 商品售罄 4 开团成功 5 系统取消
 */
const onButtonClick = (status)=>{
  let action = actions.get(status) || actions.get('default')
  sendLog(action[0])
  jumpTo(action[1])
}

这样写用到了es6里的Map对象,是不是更爽了?Map对象和Object对象有什么区别呢?

  1. 一个对象通常都有自己的原型,所以一个对象总有一个"prototype"键。
  2. 一个对象的键只能是字符串或者Symbols,但一个Map的键可以是任意值。
  3. 你可以通过size属性很容易地得到一个Map的键值对个数,而对象的键值对个数只能手动确认。

代码风格

常量大写

//bad code
const DAYS_IN_WEEK = 7;
const daysInMonth = 30;

const songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];
const Artists = ['ACDC', 'Led Zeppelin', 'The Beatles'];

function eraseDatabase() {}
function restore_database() {}

class animal {}
class Alpaca {}

//better code
const DAYS_IN_WEEK = 7;
const DAYS_IN_MONTH = 30;

const SONGS = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];
const ARTISTS = ['ACDC', 'Led Zeppelin', 'The Beatles'];

function eraseDatabase() {}
function restoreDatabase() {}

class Animal {}
class Alpaca {}

先声明后调用

//bad code
class PerformanceReview {
  constructor(employee) {
    this.employee = employee;
  }

  lookupPeers() {
    return db.lookup(this.employee, 'peers');
  }

  lookupManager() {
    return db.lookup(this.employee, 'manager');
  }

  getPeerReviews() {
    const peers = this.lookupPeers();
    // ...
  }

  perfReview() {
    this.getPeerReviews();
    this.getManagerReview();
    this.getSelfReview();
  }

  getManagerReview() {
    const manager = this.lookupManager();
  }

  getSelfReview() {
    // ...
  }
}

const review = new PerformanceReview(employee);
review.perfReview();

//better code
class PerformanceReview {
  constructor(employee) {
    this.employee = employee;
  }

  perfReview() {
    this.getPeerReviews();
    this.getManagerReview();
    this.getSelfReview();
  }

  getPeerReviews() {
    const peers = this.lookupPeers();
    // ...
  }

  lookupPeers() {
    return db.lookup(this.employee, 'peers');
  }

  getManagerReview() {
    const manager = this.lookupManager();
  }

  lookupManager() {
    return db.lookup(this.employee, 'manager');
  }

  getSelfReview() {
    // ...
  }
}

const review = new PerformanceReview(employee);
review.perfReview();
查看原文

winbug 收藏了文章 · 2019-09-24

用CSS新属性实现特殊的图片显示效果

1 概述

1.1 前言

使用一个或多个图像相关的CSS属性(background-blend-mode, mix-blend-mode, or filter)可以实现许多特殊的图片显示效果。本文转载自Bennett Feely的个人网站,文中共列举了20种图片显示效果。

详细代码及英文原文请访问Bennett Feely的主页

2 效果列表

2.1 铅笔画效果

效果示例

铅笔画效果

SCSS代码

.pencil-effect {
    $url : url(photo.jpg);
    background-image: $url;
    background-size: cover;
    background-position: center;
    @supports (filter: invert(1)) and (background-blend-mode: difference) {
        background-image: $url, $url;
        background-blend-mode: difference;
        background-position:
            calc(50% - 1px) calc(50% - 1px),
            calc(50% + 1px) calc(50% + 1px);
        filter: brightness(2) invert(1) grayscale(1);
        box-shadow: inset 0 0 0 1px black;
    }
}

查看示例程序

2.2 水彩效果

效果示例

水彩效果

SCSS代码

.watercolor-effect {
    $url : url(photo.jpg);
    background-image: $url;
    background-size: cover;
    background-position: center;
    @supports (filter: blur(2px)) and (mix-blend-mode: multiply) {
        position: relative;
        overflow: hidden;
        &:before, &:after {
            display: block;
            content: "";
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-size: cover;
        }
        &:before {
            background-image: $url, $url;
            background-blend-mode: difference;
            background-position:
                calc(50% - 1px) calc(50% - 1px),
                calc(50% + 1px) calc(50% + 1px);
            filter: brightness(2) invert(1) grayscale(1);
            box-shadow: inset 0 0 0 1px black;
        }
        &:after {
            background-image: $url;
            background-position: center;
            mix-blend-mode: multiply;
            filter: brightness(1.3) blur(2px) contrast(2);
        }
    }
}

查看示例程序

2.3 浮雕效果

效果示例

浮雕效果

SCSS代码

.emboss-effect {
    $url : url(photo.jpg);
    background-image: $url;
    background-size: cover;
    background-position: center;
    @supports (filter: invert(1)) and (background-blend-mode: difference, screen) {
        background-image: $url, $url, $url;
        background-blend-mode: difference, screen;
        background-position:
            calc(50% - 1px) calc(50% - 1px),
            calc(50% + 1px) calc(50% + 1px),
            center;
        filter: brightness(2) invert(1) grayscale(1);
    }
}

查看示例程序

2.4 彩铅效果

效果示例

彩铅效果

SCSS代码

.colored-pencil-effect {
    $url : url(photo.jpg);
    background-image: $url;
    background-size: cover;
    background-position: center;
    @supports (filter: invert(1)) and (mix-blend-mode: color) {
        position: relative;
        &:before,
        &:after {
            display: block;
            content: "";
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-size: cover;
            box-shadow: inset 0 0 0 1px black;
        }
        &:before {
            background-image: $url, $url;
            background-blend-mode: difference;
            background-position:
                calc(50% - 1px) calc(50% - 1px),
                calc(50% + 1px) calc(50% + 1px);
            filter: brightness(2) invert(1) grayscale(1);
        }
        &:after {
            background: inherit;
            mix-blend-mode: color;
        }
    }
}

查看示例程序

2.5 黑板效果

效果示例

黑板效果

SCSS代码

.chalkboard-effect {
    $url : url(photo.jpg);
    background-image: $url;
    background-size: cover;
    background-position: center;
    @supports (filter: grayscale(1)) and (background-blend-mode: difference) {
        background-image: $url, $url;
        background-blend-mode: difference;
        background-position:
            calc(50% - 1px) calc(50% - 1px),
            calc(50% + 1px) calc(50% + 1px);
        filter: brightness(1.5) grayscale(1);
    }
}

查看示例程序

2.6 彩色黑板效果

效果示例

彩色黑板效果

SCSS代码

.colored-chalkboard-effect {
    $url : url(photo.jpg);
    background-image: $url;
    background-size: cover;
    background-position: center;
    @supports (filter: brightness(2)) and (background-blend-mode: color, difference) {
        background-image: $url, $url, $url;
        background-size: cover;
        background-position:
            calc(50% - 1px) calc(50% - 1px),
            calc(50% + 1px) calc(50% + 1px),
            center;
        background-blend-mode: color, difference;
        filter: brightness(2);
    }
}

查看示例程序

2.7 喷枪效果

效果示例

喷枪效果

SCSS代码

.airbrush-effect {
  $url : url(photo.jpg);
  background-image: $url;
    background-size: cover;
    background-position: center;
  @supports (filter: blur(5px) contrast(5)) and (mix-blend-mode: multiply) {
        position: relative;
        overflow: hidden;
        &:after {
            display: block;
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: inherit;
            filter: brightness(1.5) saturate(100) blur(5px) contrast(5);
            mix-blend-mode: multiply;
        }
    }
}

查看示例程序

2.8 绚烂效果

效果示例

绚烂效果

SCSS代码

.hallucination-effect {
  $url : url(photo.jpg);
  $offset : 5px;
  background-image: $url;
  background-size: cover;
  background-position: center;
  @supports (mix-blend-mode: multiply) {
    position: relative;
    overflow: hidden;
    background-color: magenta;
    background-blend-mode: screen;
    &:before, &:after {
      display: block;
      content: "";
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: inherit;
      mix-blend-mode: multiply;
      transform: scale(1.05);
    }
    &:before {
      background-color: yellow;
      background-blend-mode: screen;
      transform-origin: top left;
    }
    &:after {
      background-color: cyan;
      background-blend-mode: screen;
      transform-origin: bottom right;
    }
  }
}

查看示例程序

2.9 绒布效果

效果示例

绒布效果

SCSS代码

.flannel-effect {
    $url : url(photo.jpg);
    background-image: $url;
    background-size: cover;
    background-position: center;
    @supports (background-blend-mode: overlay) {
        background-image: $url, $url, $url;
      background-position: center;
      background-size: 100%, 100000% 100%, 100% 100000%;
      background-blend-mode: overlay;
    }
}

查看示例程序

2.10 水平低墨

效果示例

水平低墨

SCSS代码

.low-ink-x-effect {
    $url : url(photo.jpg);
    background-image: $url;
    background-size: cover;
    background-position: center;
    @supports (background-blend-mode: screen, overlay) {
        background-image:     $url, $url, $url;
        background-size: 100% 100%, 10000% 100%;
        background-blend-mode: screen, overlay;
    }
}

查看示例程序

2.11 垂直低墨效果

效果示例

垂直低墨效果

SCSS代码

.low-ink-y-effect {
    $url : url(photo.jpg);
    background-image: $url;
    background-size: cover;
    background-position: center;
    @supports (background-blend-mode: screen, overlay) {
        background-image:     $url, $url, $url;
        background-size: 100% 100%, 100% 1000%;
        background-blend-mode: screen, overlay;
    }
}

查看示例程序

2.12 拼贴效果

效果示例

拼贴效果

SCSS代码

.collage-effect {
    $url : url(photo.jpg);
    background-image: $url;
    background-size: cover;
    background-position: center;
    @supports (background-blend-mode: overlay) {
        background-image: $url, $url, $url, $url, $url, $url;
        background-size: 200%, 80%, 60%, 50%, 40%, 100%;
        background-position: 50%, 80%, 30%, 0;
        background-blend-mode: overlay;
        background-repeat: no-repeat;
    }
}

查看示例程序

2.13 马赛克效果

效果示例

马赛克效果

SCSS代码

.mosaic-effect {
    $url : url(photo.jpg);
    background-image: $url, $url;
    background-size: cover, 5% 5%;
    background-position: center;
  background-blend-mode: overlay;
}

查看示例程序

2.14 图片边框效果

效果示例

图片边框效果

SCSS代码

.photo-border-effect {
  $url : url(photo.jpg);
  background-image: $url, $url;
  background-position: center;
  background-size: 60%, 20%;
  background-repeat: no-repeat, repeat;
}

查看示例程序

2.15 红外效果

效果示例

红外效果

SCSS代码

.infrared-effect {
  $url : url(photo.jpg);
  background-image: $url;
  background-size: cover;
  background-position: center;
  filter: hue-rotate(180deg) saturate(2);
}

查看示例程序

2.16 夜视效果

效果示例

夜视效果

SCSS代码

.night-vision-effect {
  $url : url(photo.jpg);
  $line-width: 5px;
  background-image:
    $url , radial-gradient(
      #0F0,
      #000
    ),
    repeating-linear-gradient(
      transparent 0,
      rgba(0,0,0,0.1) $line-width/2,
      transparent $line-width
    );
  background-size: cover;
  background-position: center;
  background-blend-mode: overlay;
}

查看示例程序

2.17 沃霍尔效果

效果示例

沃霍尔效果

SCSS代码

.warhol-effect {
  $url : url(photo.jpg);
  background-image: $url;
    background-size: cover;
    background-position: center;
  @supports (background-blend-mode: color) {
        background-image:
        linear-gradient(
          #14EBFF 0,
          #14EBFF 50%,
          #FFFF70 50%,
          #FFFF70 100%
        ),
        linear-gradient(
          #FF85DA 0,
          #FF85DA 50%,
          #AAA 50%,
          #AAA 100%
        ),
        $url;
        background-size: 50% 100%, 50% 100%, 50% 50%;
      background-position: top left, top right;
      background-repeat: no-repeat, no-repeat, repeat;
      background-blend-mode: color;
    }
}

查看示例程序

2.18 颜色校正效果

效果示例

颜色校正效果

SCSS代码

.selective-color-effect {
  $url : url(photo.jpg);
  background-image: $url;
  background-size: cover;
  background-position: center;
  @supports (filter: brightness(3)) and (mix-blend-mode: color) {
    position: relative;
    &:before, &:after {
      display: block;
      content: "";
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: inherit;
      background-color: red;
      background-blend-mode: screen;
      mix-blend-mode: color;
      filter: brightness(3);
    }
  }
}

查看示例程序

2.19 水平镜像效果

效果示例

水平镜像效果

SCSS代码

.mirror-x-effect {
    $url : url(photo.jpg);
    background-image: $url;
    background-size: cover;
    background-position: center;
    @supports (transform: scaleX(-1)) {
        position: relative;
        &:before, &:after {
            display: block;
            content: "";
            position: absolute;
            top: 0;
            bottom: 0;
            background: inherit;
        }
        &:before {
            left: 0;
            right: 50%;
            transform: scaleX(-1);
        }
        &:after {
            left: 50%;
            right: 0;
        }
    }
}

查看示例程序

2.20 垂直镜像效果

效果示例

垂直镜像效果

SCSS代码

.mirror-y-effect {
    $url : url(photo.jpg);
    background-image: $url;
    background-size: cover;
    background-position: center;
    @supports (transform: scaleY(-1)) {
        position: relative;
        &:before, &:after {
            display: block;
            content: "";
            position: absolute;
            left: 0;
            right: 0;
            background: inherit;
        }
        &:before {
            top: 0;
            bottom: 50%;
            transform: scaleY(-1);
        }
        &:after {
            top: 50%;
            bottom: 0;
        }
    }
}

查看示例程序

3 结尾

3.1 结语

详细代码及英文原文请访问Bennett Feely的主页

本文转载自Bennett Feely的个人网站,只做学习和交流使用。

查看原文

winbug 赞了文章 · 2019-06-04

详解vue组件三大核心概念

前言

本文主要介绍属性、事件和插槽这三个vue基础概念、使用方法及其容易被忽略的一些重要细节。如果你阅读别人写的组件,也可以从这三个部分展开,它们可以帮助你快速了解一个组件的所有功能。

clipboard.png

本文的代码请猛戳github博客,纸上得来终觉浅,大家动手多敲敲代码!

一、属性

1.自定义属性props

prop 定义了这个组件有哪些可配置的属性,组件的核心功能也都是它来确定的。写通用组件时,props 最好用对象的写法,这样可以针对每个属性设置类型、默认值或自定义校验属性的值,这点在组件开发中很重要,然而很多人却忽视,直接使用 props 的数组用法,这样的组件往往是不严谨的。

// 父组件
 <props name='属性'
           :type='type'
           :is-visible="false"
           :on-change="handlePropChange"
           :list=[22,33,44]
           title="属性Demo"
           class="test1"
           :class="['test2']"
           :style="{ marginTop: '20px' }" //注意:style 的优先级是要高于 style
           style="margin-top: 10px">
  </props>
// 子组件
  props: {
    name: String,
    type: {
   //从父级传入的 type,它的值必须是指定的 'success', 'warning', 'danger'中的一个,如果传入这三个以外的值,都会抛出一条警告
      validator: (value) => {
        return ['success', 'warning', 'danger'].includes(value)
      }
    },
    onChange: {
    //对于接收的数据,可以是各种数据类型,同样也可以传递一个函数
      type: Function,
      default: () => { }
    },
    isVisible: {
      type: Boolean,
      default: false
    },
    list: {
      type: Array,
      // 对象或数组默认值必须从一个工厂函数获取
      default: () => []
    }
  }

从上面的例中,可以得出props 可以显示定义一个或一个以上的数据,对于接收的数据,可以是各种数据类型,同样也可以传递一个函数。通过一般属性实现父向子通信;通过函数属性实现子向父通信

2.inheritAttrs

这是2.4.0 新增的一个API,默认情况下父作用域的不被认作 props 的特性绑定将会“回退”且作为普通的 HTML 特性应用在子组件的根元素上。可通过设置 inheritAttrs 为 false,这些默认行为将会被去掉。注意:这个选项不影响 class 和 style 绑定
上个例中,title属性没有在子组件中props中声明,就会默认挂在子组件的根元素上,如下图所示:

clipboard.png

3. data与props区别

  • 相同点

两者选项里都可以存放各种类型的数据,当行为操作改变时,所有行为操作所用到和模板所渲染的数据同时都会发生同步变化。

  • 不同点

data 被称之为动态数据,在各自实例中,在任何情况下,我们都可以随意改变它的数据类型和数据结构,不会被任何环境所影响。

props 被称之为静态数据,在各自实例中,一旦在初始化被定义好类型时,基于 Vue 是单向数据流,在数据传递时始终不能改变它的数据类型,而且不允许在子组件中直接操作 传递过来的props数据,而是需要通过别的手段,改变传递源中的数据。至于如何改变,我们接下去详细介绍:

4.单向数据流

这个概念出现在组件通信。props的数据都是通过父组件或者更高层级的组件数据或者字面量的方式进行传递的,不允许直接操作改变各自实例中的props数据,而是需要通过别的手段,改变传递源中的数据。那如果有时候我们想修改传递过来的prop,有哪些办法呢?

  • 方法1:过渡到 data 选项中

在子组件的 data 中拷贝一份 prop,data 是可以修改的

export default {
  props: {
    type: String
  },
  data () {
    return {
      currentType: this.type
    }
  }
}

在 data 选项里通过 currentType接收 props中type数据,相当于对 currentType= type进行一个赋值操作,不仅拿到了 currentType的数据,而且也可以改变 currentType数据。

  • 方法2:利用计算属性
export default {
  props: {
    type: String
  },
  computed: {
    normalizedType: function () {
      return this.type.toUpperCase();
    }
  }
}

以上两种方法虽可以在子组件间接修改props的值,但如果子组件想修改数据并且同步更新到父组件,却无济于事。在一些情况下,我们可能会需要对一个 prop 进行『双向绑定』,此时就推荐以下这两种方法:

  • 方法3:使用.sync
// 父组件
<template>
  <div class="hello">
    <div>
      <p>父组件msg:{{ msg }}</p>
      <p>父组件数组:{{ arr }}</p>
    </div>
    <button @click="show = true">打开model框</button>
    <br />
    <demo :show.sync="show" :msg.sync="msg" :arr="arr"></demo>
  </div>
</template>

<script>
import Demo from "./demo.vue";
export default {
  name: "Hello",
  components: {
    Demo
  },
  data() {
    return {
      show: false,
      msg: "模拟一个model框",
      arr: [1, 2, 3]
    };
  }
};
</script>
// 子组件
<template>
  <div v-if="show" class="border">
    <div>子组件msg:{{ msg }}</div>
    <div>子组件数组:{{ arr }}</div>
    <button @click="closeModel">关闭model框</button>
    <button @click="$emit('update:msg', '浪里行舟')">
      改变文字
    </button>
    <button @click="arr.push('前端工匠')">改变数组</button> 
  </div>
</template>
<script>
export default {
  props: {
    msg: {
      type: String
    },
    show: {
      type: Boolean
    },
    arr: {
      type: Array //在子组件中改变传递过来数组将会影响到父组件的状态
    }
  },
  methods: {
    closeModel() {
      this.$emit("update:show", false);
    }
  }
};

clipboard.png

父组件向子组件 props 里传递了 msg 和 show 两个值,都用了.sync 修饰符,进行双向绑定。
不过.sync 虽好,但也有限制,比如:

1)不能和表达式一起使用(如 v-bind:title.sync="doc.title + '!'" 是无效的);
2)不能用在字面量对象上(如 v-bind.sync="{ title: doc.title }" 是无法正常工作的)。

  • 方法4:将父组件中的数据包装成对象传递给子组件

这是因为在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变这个对象或数组本身将会影响到父组件的状态。比如上例中在子组件中修改父组件传递过来的数组arr,从而改变父组件的状态。

5.向子组件中传递数据时加和不加 v-bind?

对于字面量语法和动态语法,初学者可能在父组件模板中向子组件中传递数据时到底加和不加 v-bind 会感觉迷惑。

v-bind:msg = 'msg'

这是通过 v-bind 进行传递数据并且传递的数据并不是一个字面量,双引号里的解析的是一个表达式,同样也可以是实例上定义的数据和方法(其实就是引用一个变量)。

msg='浪里行舟'

这种在没有 v-bind 的模式下只能传递一个字面量,这个字面量只限于 String 类量,字符串类型。那如果想通过字面量进行数据传递时,如果想传递非String类型,必须props名前要加上v-bind,内部通过实例寻找,如果实例方没有此属性和方法,则默认为对应的数据类型。

:msg='11111' //Number 
:msg='true' //Bootlean 
:msg='()=>{console.log(1)}' //Function
:msg='{a:1}' //Object

二、事件

1.事件驱动与数据驱动

用原生JavaScript事件驱动通常是这样的流程:

  • 先通过特定的选择器查找到需要操作的节点 -> 给节点添加相应的事件监听
  • 然后用户执行某事件(点击,输入,后退等等) -> 调用 JavaScript 来修改节点

这种模式对业务来说是没有什么问题,但是从开发成本和效率来说会比较不理想,特别是在业务系统越来越庞大的时候。另一方面,找节点和修改节点这件事,效率本身就很低,因此出现了数据驱动模式。

Vue的一个核心思想是数据驱动。所谓数据驱动,是指视图是由数据驱动生成的,我们对视图的修改,不会直接操作 DOM,而是通过修改数据,其流程如下:

用户执行某个操作 -> 反馈到 VM 处理(可以导致 Model 变动) -> VM 层改变,通过绑定关系直接更新页面对应位置的数据

可以简单地理解:数据驱动不是操作节点的,而是通过虚拟的抽象数据层来直接更新页面。主要就是因为这一点,数据驱动框架才得以有较快的运行速度(因为不需要去折腾节点),并且可以应用到大型项目。

2.修饰符事件

Vue事件分为普通事件和修饰符事件,这里我们主要介绍修饰符事件。

Vue 提供了大量的修饰符封装了这些过滤和判断,让开发者少写代码,把时间都投入的业务、逻辑上,只需要通过一个修饰符去调用。我们先来思考这样问题:怎样给这个自定义组件 custom-component 绑定一个原生的 click 事件?

<custom-component>组件内容</custom-component>

如果你的回答是<custom-component @click="xxx">,那就错了。这里的 @click 是自定义事件 click,并不是原生事件 click。绑定原生的 click 是这样的:

<custom-component @click.native="xxx">组件内容</custom-component>

实际开发过程中离不开事件修饰符,常见事件修饰符有以下这些:

  • 表单修饰符

1).lazy

在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 。你可以添加 lazy 修饰符,从而转变为使用 change事件进行同步。适用于输入完所有内容后,光标离开才更新视图的场景。

2).trim

如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:

<input v-model.trim="msg">

这个修饰符可以过滤掉输入完密码不小心多敲了一下空格的场景。需要注意的是,它只能过滤首尾的空格!首尾,中间的是不会过滤的。

3).number

如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:

<input v-model.number="value" type="text" />

clipboard.png

从上面例子,可以得到如果你先输入数字,那它就会限制你输入的只能是数字。如果你先输入字符串,那它就相当于没有加.number

  • 事件修饰符
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>

<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>

三、插槽

插槽分为普通插槽和作用域插槽,其实两者很类似,只不过作用域插槽可以接受子组件传递过来的参数。

1.作用域插槽

我们不妨通过一个todolist的例子来了解作用域插槽。如果当item选中后,文字变为黄色(如下图所示),该如何实现呢?

clipboard.png

// 父组件
<template>
  <div class="toList">
    <input v-model="info" type="text" /> <button @click="addItem">添加</button>
    <ul>
      <TodoItem v-for="(item, index) in listData" :key="index">
        <template v-slot:item="itemProps"> // 这是个具名插槽
        // 其中itemProps的值就是子组件传递过来的对象
          <span
            :style="{
              fontSize: '20px',
              color: itemProps.checked ? 'yellow' : 'blue'
            }"
            >{{ item }}</span
          >
        </template>
      </TodoItem>
    </ul>
  </div>
</template>
<script>
import TodoItem from "./TodoItem";
export default {
  components: {
    TodoItem
  },
  data() {
    return {
      info: "",
      listData: []
    };
  },
  methods: {
    addItem() {
      this.listData.push(this.info);
      this.info = "";
    }
  }
};
</script>
// 子组件
<template>
  <div>
    <li class="item">
      <input v-model="checked" type="checkbox" />
      <slot name="item" :checked="checked"></slot> // 将checked的值传递给父组件
    </li>
  </div>
</template>
<script>
export default {
  data() {
    return {
      checked: false
    };
  }
};
</script>

值得注意:v-bind:style 的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。CSS 属性名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名。

2.v-slot新语法

在 2.6.0 中,我们为具名插槽和作用域插槽引入了一个新的统一的语法 (即 v-slot 指令)。它取代了 slot 和 slot-scope 。我们来思考个问题:相同名称的插槽是合并还是替换

  • Vue2.5版本,普通插槽合并、作用域插槽替换
  • Vue2.6版本,都是替换(见下面例子)

我们通过一个例子介绍下Vue2.6版本默认插槽、具名插槽和作用域插槽的新语法:

// 父组件
<template>
  <div class="helloSlot">
    <h2>2.6 新语法</h2>
    <SlotDemo>
      <p>默认插槽:default slot</p>
      <template v-slot:title>
        <p>具名插槽:title slot1</p>
        <p>具名插槽:title slot2</p>
      </template>
      <template v-slot:title>
        <p>new具名插槽:title slot1</p>
        <p>new具名插槽:title slot2</p>
      </template>
      <template v-slot:item="props">
        <p>作用域插槽:item slot-scope {{ props }}</p>
      </template>
    </SlotDemo>
  </div>
</template>
<script>
import Slot from "./slot";
export default {
  components: {
    SlotDemo: Slot
  }
};
</script>
// 子组件
<template>
  <div>
    <slot />
    <slot name="title" />
    <slot name="item" :propData="propData" />  // propData这个属性名可以任意取
  </div>
</template>
<script>
export default {
  data() {
    return {
      propData: {
        value: "浪里行舟"
      }
    };
  }
};
</script>

slot

给大家推荐一个好用的BUG监控工具Fundebug,欢迎免费试用!

欢迎关注公众号:前端工匠,你的成长我们一起见证!
image

参考文章

查看原文

赞 152 收藏 120 评论 4

winbug 关注了用户 · 2019-03-20

关注 25

认证与成就

  • 获得 3 次点赞
  • 获得 32 枚徽章 获得 0 枚金徽章, 获得 9 枚银徽章, 获得 23 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2014-06-24
个人主页被 545 人浏览