原文连接:http://caibaojian.com/8-javascript-attention.htmljavascript
基于 Class 的组件是状态化的,包含有自身方法、生命周期函数、组件内状态等。最佳实践包括但不限于如下一些内容:php
1)引入 CSS 依赖 (Importing CSS)css
我很喜欢 CSS in JavaScript 这一理念。在 React 中,咱们能够为每个 React 组件引入相应的 CSS 文件,这一“梦想”成为了现实。在下面的代码示例,我把 CSS 文件的引入与其余依赖隔行分开,以示区别:html
import React, {Component} from 'react' import {observer} from 'mobx-react' import ExpandableForm from './ExpandableForm' import './styles/ProfileContainer.css'
固然,这并非真正意义上的 CSS in JS,具体实现其实社区上有不少方案。个人 Github 上 fork 了一份各类 CSS in JS 方案的多维度对比,感兴趣的读者能够点击这里。java
2)设定初始状态(Initializing State)react
在编写组件过程当中,必定要注意初始状态的设定。利用 ES6 模块化的知识,咱们确保该组件暴露都是 “export default” 形式,方便其余模块(组件)的调用和团队协做。jquery
import React, {Component} from 'react' import {observer} from 'mobx-react' import ExpandableForm from './ExpandableForm' import './styles/ProfileContainer.css' export default class ProfileContainer extends Component { state = { expanded: false } ......
3)设定 propTypes 和 defaultPropsgit
propTypes 和 defaultProps 都是组件的静态属性。在组件的代码中,这两个属性的设定位置越高越好。由于这样方便其余阅读代码者或者开发者本身 review,一眼就能看到这些信息。这些信息就如同组件文档同样,对于理解或熟悉当前组件很是重要。es6
一样,原则上,你编写的组件都须要有 propTypes 属性。如同如下代码:github
export default class ProfileContainer extends Component { state = { expanded: false } static propTypes = { model: React.PropTypes.object.isRequired, title: React.PropTypes.string } static defaultProps = { model: { id: 0 }, title: 'Your Name' }
Functional Components 是指没有状态、没有方法,纯组件。咱们应该最大限度地编写和使用这一类组件。这类组件做为函数,其参数就是 props, 咱们能够合理设定初始状态和赋值。
function ExpandableForm({ onExpand, expanded = false, children, onSubmit }) { const formStyle = expanded ? {height: 'auto'} : {height: 0} return ( <form style={formStyle} onSubmit={onSubmit}> {children} <button onClick={onExpand}>Expand</button> </form> ) }
4)组件方法(Methods)
在编写组件方法时,尤为是你将一个方法做为 props 传递给子组件时,须要确保 this 的正确指向。咱们一般使用 bind 或者 ES6 箭头函数来达到此目的。
export default class ProfileContainer extends Component { state = { expanded: false } handleSubmit = (e) => { e.preventDefault() this.props.model.save() } handleNameChange = (e) => { this.props.model.changeName(e.target.value) } handleExpand = (e) => { e.preventDefault() this.setState({ expanded: !this.state.expanded }) }
固然,这并非惟一作法。实现方式多种多样,我专门有一片文章来对比 React 中对于组件 this 的绑定,能够点击此处参考。
5)setState 接受一个函数做为参数(Passing setState a Function)
在上面的代码示例中,咱们使用了:
this.setState({ expanded: !this.state.expanded })
这里,关于 setState hook 函数,其实有一个很是“有意思”的问题。React 在设计时,为了性能上的优化,采用了 Batch 思想,会收集“一波” state 的变化,统一进行处理。就像浏览器绘制文档的实现同样。因此 setState 以后,state 也许不会立刻就发生变化,这是一个异步的过程。
这说明,咱们要谨慎地在 setState 中使用当前的 state,由于当前的state 也许并不可靠。
为了规避这个问题,咱们能够这样作:
this.setState(prevState => ({ expanded: !prevState.expanded }))
咱们给 setState 方法传递一个函数,函数参数为上一刻 state,便保证setState 可以马上执行。
关于 React setState 的设计, Eric Elliott 也曾经这么喷过:setState() Gate,并由此展开了多方“撕逼”。做为围观群众,咱们在吃瓜的同时,必定会在大神论道当中收获不少思想,建议阅读。
若是你对 setState 方法的异步性还有困惑,能够同我讨论,这里再也不展开。
6)合理利用解构(Destructuring Props)
这个其实没有太多可说的,仔细观察代码吧:咱们使用了解构赋值。除此以外,若是一个组件有不少的 props 的话,每一个 props 应该都另起一行,这样书写上和阅读性上都有更好的体验。
export default class ProfileContainer extends Component { state = { expanded: false } handleSubmit = (e) => { e.preventDefault() this.props.model.save() } handleNameChange = (e) => { this.props.model.changeName(e.target.value) } handleExpand = (e) => { e.preventDefault() this.setState(prevState => ({ expanded: !prevState.expanded })) } render() { const {model, title} = this.props return ( <ExpandableForm onSubmit={this.handleSubmit} expanded={this.state.expanded} onExpand={this.handleExpand}> <div> <h1>{title}</h1> <input type="text" value={model.name} onChange={this.handleNameChange} placeholder="Your Name"/> </div> </ExpandableForm> ) } }
7)使用修饰器(Decorators)
这一条是对使用 mobx 的开发者来讲的。若是你不懂 mobx,能够大致扫一眼。
咱们强调使用 ES next decorate 来修饰咱们的组件,如同:
@observer
export default class ProfileContainer extends Component {
使用修饰器更加灵活且可读性更高。即使你不使用修饰器,也须要如此暴露你的组件:
class ProfileContainer extends Component { // Component code } export default observer(ProfileContainer)
8)闭包(Closures)
必定要尽可能避免如下用法:
<input
type="text" value={model.name} // onChange={(e) => { model.name = e.target.value }} // ^ Not this. Use the below: onChange={this.handleChange} placeholder="Your Name"/>
不要:
onChange = {(e) => { model.name = e.target.value }}
而是:
onChange = {this.handleChange}
缘由其实很简单,每次父组件 render 的时候,都会新建一个新的函数并传递给 input。
若是 input 是一个 React 组件,这会粗暴地直接致使这个组件的 re-render,须要知道,Reconciliation 但是 React 成本最高的部分。
另外,咱们推荐的方法,会使得阅读、调试和更改更加方便。
9)JSX中的条件判别(Conditionals in JSX)
真正写过 React 项目的同窗必定会明白,JSX 中可能会存在大量的条件判别,以达到根据不一样的状况渲染不一样组件形态的效果。
就像下图这样:
返例
这样的结果是不理想的。咱们丢失了代码的可读性,也使得代码组织显得混乱异常。多层次的嵌套也是应该避免的。
针对于此,有很对类库来解决 JSX-Control Statements 此类问题,可是与其引入第三方类库的依赖,还不如咱们先本身尝试探索解决问题。
此时,是否是有点怀念if...else?
咱们可使用大括号内包含当即执行函数IIFE,来达到使用 if...else 的目的:
解决思路
固然,大量使用当即执行函数会形成性能上的损失。因此,考虑代码可读性上的权衡,仍是有必要好好斟酌的。
我更加建议的作法是分解此组件,由于这个组件的逻辑已通过于复杂而臃肿了。如何分解?请看我这篇文章。
其实所谓 React “最佳实践”,想必每一个团队都有本身的一套“心得”,哪里有一个统一套? 本文指出的几种方法未必对任何读者都适用。针对不一样的代码风格,开发习惯,拥有本身团队一套“最佳实践”是颇有必要的。从另外一方面,也说明了 React 技术栈自己的灵活于强大。
这里咱们针对JavaScript初学者给出一些技巧和列出一些陷阱。若是你已是一个砖家,也能够读一读。
1. 你是否尝试过对数组元素进行排序?
JavaScript默认使用字典序(alphanumeric)来排序。所以,[1,2,5,10].sort()
的结果是[1, 10, 2, 5]
。
若是你想正确的排序,应该这样作:[1,2,5,10].sort((a, b) => a - b)
2. new Date() 十分好用
new Date()
的使用方法有:
x
: 返回1970年1月1日 + x
毫秒的值。new Date(1, 1, 1)
返回1901年2月1号。new Date(2016, 1, 1)
不会在1900年的基础上加2016,而只是表示2016年。3. 替换函数没有真的替换?
let s = "bob" const replaced = s.replace('b', 'l') replaced === "lob" // 只会替换掉第一个b s === "bob" // 而且s的值不会变
若是你想把全部的b都替换掉,要使用正则:
"bob".replace(/b/g, 'l') === 'lol'
4. 谨慎对待比较运算
// 这些能够 'abc' === 'abc' // true 1 === 1 // true // 然而这些不行 [1,2,3] === [1,2,3] // false {a: 1} === {a: 1} // false {} === {} // false
由于[1,2,3]和[1,2,3]是两个不一样的数组,只是它们的元素碰巧相同。所以,不能简单的经过===
来判断。·
5. 数组不是基础类型
typeof {} === 'object' // true typeof 'a' === 'string' // true typeof 1 === number // true // 可是.... typeof [] === 'object' // true
若是要判断一个变量var
是不是数组,你须要使用Array.isArray(var)
。
6. 闭包
这是一个经典的JavaScript面试题:
const Greeters = [] for (var i = 0 ; i < 10 ; i++) { Greeters.push(function () { return console.log(i) }) } Greeters[0]() // 10 Greeters[1]() // 10 Greeters[2]() // 10
虽然指望输出0,1,2,…,然而实际上却不会。知道如何Debug嘛?
有两种方法:
let
而不是var
。备注:能够参考Fundebug的另外一篇博客 ES6之”let”能替代”var”吗?bind
函数。备注:能够参考Fundebug的另外一篇博客 JavaScript初学者必看“this” Greeters.push(console.log.bind(null, i))
固然,还有不少解法。这两种是我最喜欢的!
7. 关于bind
下面这段代码会输出什么结果?
//code from http://caibaojian.com/8-javascript-attention.html class Foo { constructor(name) { this.name = name } greet() { console.log('hello, this is ', this.name) } someThingAsync() { return Promise.resolve() } asyncGreet() { this.someThingAsync().then(this.greet) } } new Foo('dog').asyncGreet()
若是你说程序会崩溃,而且报错:Cannot read property ‘name’ of undefined。
一、由于第16行的geet
没有在正确的环境下执行。固然,也有不少方法解决这个BUG!
我喜欢使用bind
函数来解决问题:
asyncGreet () {
this.someThingAsync() .then(this.greet.bind(this)) }
这样会确保greet
会被Foo的实例调用,而不是局部的函数的this
。
二、若是你想要greet
永远不会绑定到错误的做用域,你能够在构造函数里面使用bind
来绑 。
class Foo { constructor(name) { this.name = name this.greet = this.greet.bind(this) } }
三、你也可使用箭头函数(=>)来防止做用域被修改。备注:能够参考Fundebug的另外一篇博客 JavaScript初学者必看“箭头函数”。
asyncGreet() {
this.someThingAsync().then(() = >{ this.greet() }) }
Math.min() < Math.max() // false
由于Math.min() 返回 Infinity, 而 Math.max()返回 -Infinity。
这里咱们针对JavaScript初学者给出一些技巧和列出一些陷阱。若是你已是一个砖家,也能够读一读。
1. 你是否尝试过对数组元素进行排序?
JavaScript默认使用字典序(alphanumeric)来排序。所以,[1,2,5,10].sort()
的结果是[1, 10, 2, 5]
。
若是你想正确的排序,应该这样作:[1,2,5,10].sort((a, b) => a - b)
2. new Date() 十分好用
new Date()
的使用方法有:
x
: 返回1970年1月1日 + x
毫秒的值。new Date(1, 1, 1)
返回1901年2月1号。new Date(2016, 1, 1)
不会在1900年的基础上加2016,而只是表示2016年。3. 替换函数没有真的替换?
let s = "bob" const replaced = s.replace('b', 'l') replaced === "lob" // 只会替换掉第一个b s === "bob" // 而且s的值不会变
若是你想把全部的b都替换掉,要使用正则:
"bob".replace(/b/g, 'l') === 'lol'
4. 谨慎对待比较运算
// 这些能够 'abc' === 'abc' // true 1 === 1 // true // 然而这些不行 [1,2,3] === [1,2,3] // false {a: 1} === {a: 1} // false {} === {} // false
由于[1,2,3]和[1,2,3]是两个不一样的数组,只是它们的元素碰巧相同。所以,不能简单的经过===
来判断。
5. 数组不是基础类型
typeof {} === 'object' // true typeof 'a' === 'string' // true typeof 1 === number // true // 可是.... typeof [] === 'object' // true
若是要判断一个变量var
是不是数组,你须要使用Array.isArray(var)
。
6. 闭包
这是一个经典的JavaScript面试题:
const Greeters = [] for (var i = 0 ; i < 10 ; i++) { Greeters.push(function () { return console.log(i) }) } Greeters[0]() // 10 Greeters[1]() // 10 Greeters[2]() // 10
虽然指望输出0,1,2,...,然而实际上却不会。知道如何Debug嘛?
有两种方法:
let
而不是var
。备注:能够参考Fundebug的另外一篇博客 ES6之"let"能替代"var"吗?bind
函数。备注:能够参考Fundebug的另外一篇博客 JavaScript初学者必看“this” Greeters.push(console.log.bind(null, i))
固然,还有不少解法。这两种是我最喜欢的!7. 关于bind
下面这段代码会输出什么结果?
class Foo { constructor (name) { this.name = name } greet () { console.log('hello, this is ', this.name) } someThingAsync () { return Promise.resolve() } asyncGreet () { this.someThingAsync() .then(this.greet) } } new Foo('dog').asyncGreet()
若是你说程序会崩溃,而且报错:Cannot read property 'name' of undefined。
由于第16行的geet
没有在正确的环境下执行。固然,也有不少方法解决这个BUG!
bind
函数来解决问题: asyncGreet () {
this.someThingAsync() .then(this.greet.bind(this)) }
这样会确保greet
会被Foo的实例调用,而不是局部的函数的this
。greet
永远不会绑定到错误的做用域,你能够在构造函数里面使用bind
来绑 。 class Foo { constructor (name) { this.name = name this.greet = this.greet.bind(this) } }
asyncGreet () {
this.someThingAsync() .then(() => { this.greet() }) }
Math.min() < Math.max() // false
由于Math.min() 返回 Infinity, 而 Math.max()返回 -Infinity。
继承是面向对象编程中又一很是重要的概念,JavaScript支持实现继承,不支持接口继承,实现继承主要依靠原型链来实现的
原型链
首先得要明白什么是原型链,在一篇文章看懂proto和prototype的关系及区别中讲得很是详细
原型链继承基本思想就是让一个原型对象指向另外一个类型的实例
function SuperType() { this.property = true } SuperType.prototype.getSuperValue = function() { return this.property } function SubType() { this.subproperty = false } SubType.prototype = new SuperType() SubType.prototype.getSubValue = function() { return this.subproperty } var instance = new SubType() console.log(instance.getSuperValue()) // true
代码定义了两个类型SuperType和SubType,每一个类型分别有一个属性和一个方法,SubType继承了SuperType,而继承是经过建立SuperType的实例,并将该实例赋给SubType.prototype实现的
实现的本质是重写原型对象,代之以一个新类型的实例,那么存在SuperType的实例中的全部属性和方法,如今也存在于SubType.prototype中了
咱们知道,在建立一个实例的时候,实例对象中会有一个内部指针指向建立它的原型,进行关联起来,在这里代码SubType.prototype = new SuperType()
,也会在SubType.prototype建立一个内部指针,将SubType.prototype与SuperType关联起来
因此instance指向SubType的原型,SubType的原型又指向SuperType的原型,继而在instance在调用getSuperValue()方法的时候,会顺着这条链一直往上找
添加方法
在给SubType原型添加方法的时候,若是,父类上也有一样的名字,SubType将会覆盖这个方法,达到从新的目的。 可是这个方法依然存在于父类中
记住不能以字面量的形式添加,由于,上面说过经过实例继承本质上就是重写,再使用字面量形式,又是一次重写了,但此次重写没有跟父类有任何关联,因此就会致使原型链截断·
function SuperType() { this.property = true } SuperType.prototype.getSuperValue = function() { return this.property } function SubType() { this.subproperty = false } SubType.prototype = new SuperType() SubType.prototype = { getSubValue: function() { return this.subproperty } } var instance = new SubType() console.log(instance.getSuperValue()) // error
问题
单纯的使用原型链继承,主要问题来自包含引用类型值的原型。
function SuperType() { this.colors = ['red', 'blue', 'green'] } function SubType() {} SubType.prototype = new SuperType() var instance1 = new SubType() var instance2 = new SubType() instance1.colors.push('black') console.log(instance1.colors) // ["red", "blue", "green", "black"] console.log(instance2.colors) // ["red", "blue", "green", "black"]
在SuperType构造函数定义了一个colors属性,当SubType经过原型链继承后,这个属性就会出现SubType.prototype中,就跟专门建立了SubType.prototype.colors同样,因此会致使SubType的全部实例都会共享这个属性,因此instance1修改colors这个引用类型值,也会反映到instance2中
借用构造函数
此方法为了解决原型中包含引用类型值所带来的问题
这种方法的思想就是在子类构造函数的内部调用父类构造函数,能够借助apply()和call()方法来改变对象的执行上下文
function SuperType() { this.colors = ['red', 'blue', 'green'] } function SubType() { // 继承SuperType SuperType.call(this) } var instance1 = new SubType() var instance2 = new SubType() instance1.colors.push('black') console.log(instance1.colors) // ["red", "blue", "green", "black"] console.log(instance2.colors) // ["red", "blue", "green"]
在新建SubType实例是调用了SuperType构造函数,这样以来,就会在新SubType对象上执行SuperType函数中定义的全部对象初始化代码
结果,SubType的每一个实例就会具备本身的colors属性的副本了
传递参数
借助构造函数还有一个优点就是能够传递参数
function SuperType(name) { this.name = name } function SubType() { // 继承SuperType SuperType.call(this, 'Jiang') this.job = 'student' } var instance = new SubType() console.log(instance.name) // Jiang console.log(instance.job) // student
问题
若是仅仅借助构造函数,方法都在构造函数中定义,所以函数没法达到复用
组合继承(原型链+构造函数)
组合继承是将原型链继承和构造函数结合起来,从而发挥两者之长的一种模式
思路就是使用原型链实现对原型属性和方法的继承,而经过借用构造函数来实现对实例属性的继承
这样,既经过在原型上定义方法实现了函数复用,又可以保证每一个实例都有它本身的属性
function SuperType(name) { this.name = name this.colors = ['red', 'blue', 'green'] } SuperType.prototype.sayName = function() { console.log(this.name) } function SubType(name, job) { // 继承属性 SuperType.call(this, name) this.job = job } // 继承方法 SubType.prototype = new SuperType() SubType.prototype.constructor = SuperType SubType.prototype.sayJob = function() { console.log(this.job) } var instance1 = new SubType('Jiang', 'student') instance1.colors.push('black') console.log(instance1.colors) //["red", "blue", "green", "black"] instance1.sayName() // 'Jiang' instance1.sayJob() // 'student' var instance2 = new SubType('J', 'doctor') console.log(instance2.colors) // //["red", "blue", "green"] instance2.sayName() // 'J' instance2.sayJob() // 'doctor'
这种模式避免了原型链和构造函数继承的缺陷,融合了他们的优势,是最经常使用的一种继承模式
原型式继承
借助原型能够基于已有的对象建立新对象,同时还没必要所以建立自定义类型
function object(o) { function F() {} F.prototype = o return new F() }
在object函数内部,先建立一个临时性的构造函数,而后将传入的对象做为这个构造函数的原型,最后返回这个临时类型的一个新实例
本质上来讲,object对传入其中的对象执行了一次浅复制
var person = { name: 'Jiang', friends: ['Shelby', 'Court'] } var anotherPerson = object(person) console.log(anotherPerson.friends) // ['Shelby', 'Court']
这种模式要去你必须有一个对象做为另外一个对象的基础
在这个例子中,person做为另外一个对象的基础,把person传入object中,该函数就会返回一个新的对象
这个新对象将person做为原型,因此它的原型中就包含一个基本类型和一个引用类型
因此意味着若是还有另一个对象关联了person,anotherPerson修改数组friends的时候,也会体如今这个对象中
Object.create()方法
ES5经过Object.create()方法规范了原型式继承,能够接受两个参数,一个是用做新对象原型的对象和一个可选的为新对象定义额外属性的对象,行为相同,基本用法和上面的object同样,除了object不能接受第二个参数之外
var person = { name: 'Jiang', friends: ['Shelby', 'Court'] } var anotherPerson = Object.create(person) console.log(anotherPerson.friends)
寄生式继承
寄生式继承的思路与寄生构造函数和工厂模式相似,即建立一个仅用于封装继承过程的函数
function createAnother(o) { var clone = Object.create(o) // 建立一个新对象 clone.sayHi = function() { // 添加方法 console.log('hi') } return clone // 返回这个对象 } var person = { name: 'Jiang' } var anotherPeson = createAnother(person) anotherPeson.sayHi()
基于person返回了一个新对象anotherPeson,新对象不只拥有了person的属性和方法,还有本身的sayHi方法
在主要考虑对象而不是自定义类型和构造函数的状况下,这是一个有用的模式
寄生组合式继承
在前面说的组合模式(原型链+构造函数)中,继承的时候须要调用两次父类构造函数
父类
function SuperType(name) { this.name = name this.colors = ['red', 'blue', 'green'] }
第一次在子类构造函数中
function SubType(name, job) { // 继承属性 SuperType.call(this, name) this.job = job }
第二次将子类的原型指向父类的实例
// 继承方法 SubType.prototype = new SuperType()
当使用var instance = new SubType()
的时候,会产生两组name和color属性,一组在SubType实例上,一组在SubType原型上,只不过实例上的屏蔽了原型上的
使用寄生式组合模式,能够规避这个问题
这种模式经过借用构造函数来继承属性,经过原型链的混成形式来继承方法
基本思路:没必要为了指定子类型的原型而调用父类的构造函数,咱们须要的无非就是父类原型的一个副本
本质上就是使用寄生式继承来继承父类的原型,在将结果指定给子类型的原型
function inheritPrototype(subType, superType) { var prototype = Object.create(superType.prototype); prototype.constructor = subType; subType.prototype = prototype; }
该函数实现了寄生组合继承的最简单形式
这个函数接受两个参数,一个子类,一个父类
第一步建立父类原型的副本,第二步将建立的副本添加constructor属性,第三部将子类的原型指向这个副本
function SuperType(name) { this.name = name this.colors = ['red', 'blue', 'green'] } SuperType.prototype.sayName = function() { console.log(this.name) } function SubType(name, job) { // 继承属性 SuperType.call(this, name) this.job = job } // 继承 inheritPrototype(SubType, SuperType) var instance = new SubType('Jiang', 'student') instance.sayName()
> 补充:直接使用Object.create来实现,其实就是将上面封装的函数拆开,这样演示能够更容易理解
function SuperType(name) { this.name = name this.colors = ['red', 'blue', 'green'] } SuperType.prototype.sayName = function() { console.log(this.name) } function SubType(name, job) { // 继承属性 SuperType.call(this, name) this.job = job } // 继承 SubType.prototype = Object.create(SuperType.prototype) // 修复constructor SubType.prototype.constructor = SubType var instance = new SubType('Jiang', 'student') instance.sayName()
ES6新增了一个方法,Object.setPrototypeOf
,能够直接建立关联,并且不用手动添加constructor属性
//code from http://caibaojian.com/6-javascript-prototype.html // 继承 Object.setPrototypeOf(SubType.prototype, SuperType.prototype) console.log(SubType.prototype.constructor === SubType) // true
使用jQuery能够给元素很方便的添加class和删除class等操做,如今原生的JavaScript也能够实现这个方法了。使用classList能够方便的添加class、删除class、查询class等。
语法:
let elementClass = element.classList;
elementClasses 是一个 DOMTokenList 表示 element 的类属性 。若是类属性未设置或为空,那么 elementClasses.length 返回 0。element.classList 自己是只读的,虽然你可使用 add() 和 remove() 方法修改它。
方法:
add( String [, String] )
添加指定的类值。若是这些类已经存在于元素的属性中,那么它们将被忽略。
remove( String [,String] )
删除指定的类值。
item ( Number )
按集合中的索引返回类值。
toggle ( String [, force] )
当只有一个参数时:切换 class value; 即若是类存在,则删除它并返回false,若是不存在,则添加它并返回true。
当存在第二个参数时:若是第二个参数的计算结果为true,则添加指定的类值,若是计算结果为false,则删除它
contains( String )
检查元素的类属性中是否存在指定的类值。
示例:
// div是具备class =“foo bar”的<div>元素的对象引用 div.classList.remove("foo"); div.classList.add("anotherclass"); // 若是visible被设置则删除它,不然添加它 div.classList.toggle("visible"); // 添加/删除 visible,取决于测试条件,i小于10 div.classList.toggle("visible", i < 10); alert(div.classList.contains("foo")); //添加或删除多个类 div.classList.add("foo","bar"); div.classList.remove("foo", "bar");
兼容性
不兼容Android2.3和iOS4.2的,在移动端上想使用也是有点头疼啊。IE系列的更别说IE9和IE8了。因此目前来看,仍是没法在实际中放心的使用,只能用于某些特定的项目等。不过咱们能够经过一些shim来实现,或者最下方给出的原生javascript实现。
跨浏览器javascript shim
https://github.com/eligrey/classList.js/blob/master/classList.js
if ("document" in self) { // Full polyfill for browsers with no classList support if (!("classList" in document.createElement("_"))) { (function (view) { "use strict"; if (!('Element' in view)) return; var classListProp = "classList" , protoProp = "prototype" , elemCtrProto = view.Element[protoProp] , objCtr = Object , strTrim = String[protoProp].trim || function () { return this.replace(/^\s+|\s+$/g, ""); } , arrIndexOf = Array[protoProp].indexOf || function (item) { var i = 0 , len = this.length ; for (; i < len; i++) { if (i in this && this[i] === item) { return i; } } return -1; } // Vendors: please allow content code to instantiate DOMExceptions , DOMEx = function (type, message) { this.name = type; this.code = DOMException[type]; this.message = message; } , checkTokenAndGetIndex = function (classList, token) { if (token === "") { throw new DOMEx( "SYNTAX_ERR" , "An invalid or illegal string was specified" ); } if (/\s/.test(token)) { throw new DOMEx( "INVALID_CHARACTER_ERR" ,