JavaScript經常使用,繼承,原生JavaScript實現classList

原文連接:http://caibaojian.com/8-javascript-attention.htmljavascript

基於 Class 的組件最佳實踐(Class Based Components)

基於 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() }) }

8. Math.min()比Math.max()大

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嘛?
有兩種方法:

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) } }
  • 你也可使用箭頭函數(=>)來防止做用域被修改。備註:能夠參考Fundebug的另外一篇博客 JavaScript初學者必看「箭頭函數」
    asyncGreet () {
      this.someThingAsync() .then(() => { this.greet() }) }

8. Math.min()比Math.max()大

Math.min() < Math.max() // false

由於Math.min() 返回 Infinity, 而 Math.max()返回 -Infinity。

 

 

 

 

 

 

 

 

 

 

JavaScript六種繼承方式詳解

 

繼承是面向對象編程中又一很是重要的概念,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" , "String contains an invalid character" ); } return arrIndexOf.call(classList, token); } , ClassList = function (elem) { var trimmedClasses = strTrim.call(elem.getAttribute("class") || "") , classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [] , i = 0 , len = classes.length ; for (; i < len; i++) { this.push(classes[i]); } this._updateClassName = function () { elem.setAttribute("class", this.toString()); }; } , classListProto = ClassList[protoProp] = [] , classListGetter = function () { return new ClassList(this); } ; // Most DOMException implementations don't allow calling DOMException's toString() // on non-DOMExceptions. Error's toString() is sufficient here. DOMEx[protoProp] = Error[protoProp]; classListProto.item = function (i) { return this[i] || null; }; classListProto.contains = function (token) { token += ""; return checkTokenAndGetIndex(this, token) !== -1; }; classListProto.add = function () { var tokens = arguments , i = 0 , l = tokens.length , token , updated = false ; do { token = tokens[i] + ""; if (checkTokenAndGetIndex(this, token) === -1) { this.push(token); updated = true; } } while (++i < l); if (updated) { this._updateClassName(); } }; classListProto.remove = function () { var tokens = arguments , i = 0 , l = tokens.length , token , updated = false , index ; do { token = tokens[i] + ""; index = checkTokenAndGetIndex(this, token); while (index !== -1) { this.splice(index, 1); updated = true; index = checkTokenAndGetIndex(this, token); } } while (++i < l); if (updated) { this._updateClassName(); } }; classListProto.toggle = function (token, force) { token += ""; var result = this.contains(token) , method = result ? force !== true && "remove" : force !== false && "add" ; if (method) { this[method](token); } if (force === true || force === false) { return force; } else { return !result; } }; classListProto.toString = function () { return this.join(""); }; if (objCtr.defineProperty) { var classListPropDesc = { get: classListGetter , enumerable: true , configurable: true }; try { objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); } catch (ex) { // IE 8 doesn't support enumerable:true if (ex.number === -0x7FF5EC54) { classListPropDesc.enumerable = false; objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); } } } else if (objCtr[protoProp].__defineGetter__) { elemCtrProto.__defineGetter__(classListProp, classListGetter); } }(self)); } else { // There is full or partial native classList support, so just check if we need // to normalize the add/remove and toggle APIs. (function () { "use strict"; var testElement = document.createElement("_"); testElement.classList.add("c1", "c2"); // Polyfill for IE 10/11 and Firefox <26, where classList.add and // classList.remove exist but support only one argument at a time. if (!testElement.classList.contains("c2")) { var createMethod = function(method) { var original = DOMTokenList.prototype[method]; DOMTokenList.prototype[method] = function(token) { var i, len = arguments.length; for (i = 0; i < len; i++) { token = arguments[i]; original.call(this, token); } }; }; createMethod('add'); createMethod('remove'); } testElement.classList.toggle("c3", false); // Polyfill for IE 10 and Firefox <24, where classList.toggle does not // support the second argument. if (testElement.classList.contains("c3")) { var _toggle = DOMTokenList.prototype.toggle; DOMTokenList.prototype.toggle = function(token, force) { if (1 in arguments && !this.contains(token) === !force) { return force; } else { return _toggle.call(this, token); } }; } testElement = null; }()); } }

原生JavaScript

 

 

相似於jQuery庫的使用方式,使用className經過正則來添加或者刪除class。

addClass、removeClass、toggleClass、hasClass

 

 

function hasClass(obj, cls) { return obj.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)')); } function addClass(obj, cls) { if (!this.hasClass(obj, cls)) obj.className += " " + cls; } function removeClass(obj, cls) { if (hasClass(obj, cls)) { var reg = new RegExp('(\\s|^)' + cls + '(\\s|$)'); obj.className = obj.className.replace(reg, ' '); } } function toggleClass(obj,cls){ if(hasClass(obj,cls)){ removeClass(obj, cls); }else{ addClass(obj, cls); } }
相關文章
相關標籤/搜索