文檔對象模型 (DOM) 是HTML和XML文檔的編程接口。它提供了對文檔的結構化的表述,并定義了一種方式可以使從程序中對該結構進行訪問,從而改變文檔的結構,樣式和內容。文檔對象模型 (DOM) 是對HTML文件的另一種展示,通俗地說,一個HTML 文件,我們可以用編輯器以代碼的形式展示它,也可以用瀏覽器以頁面的形式展示它,同一份文件通過不同的展示方式,就有了不一樣的表現(xiàn)形式。而DOM 將文檔解析為一個由節(jié)點和對象(包含屬性和方法的對象)組成的結構集合。簡言之,它會將web頁面和腳本或程序語言連接起來,我們可以使用腳本或者程序語言通過DOM 來改變或者控制web頁面。
我們可以通過JavaScript 來調用document和window元素的API來操作文檔或者獲取文檔的信息。
Node 是一個接口,有許多接口都從Node 繼承方法和屬性:Document, Element, CharacterData (which Text, Comment, and CDATASection inherit), ProcessingInstruction, DocumentFragment, DocumentType, Notation, Entity, EntityReference。Node 有一個nodeType的屬性表示Node 的類型,是一個整數(shù),不同的值代表不同的節(jié)點類型。具體如下表所示:
節(jié)點類型常量
已棄用的節(jié)點類型常量
假設我們要判斷一個Node 是不是一個元素,通過查表可知元素的nodeType屬性值為1,代碼可以這么寫:
復制代碼if(X.nodeType===1){
console.log('X 是一個元素');
}
在Node 類型中,比較常用的就是element,text,comment,document,document_fragment這幾種類型。
Element提供了對元素標簽名,子節(jié)點和特性的訪問,我們常用HTML元素比如div,span,a等標簽就是element中的一種。Element有下面幾條特性:(1)nodeType為1(2)nodeName為元素標簽名,tagName也是返回標簽名(3)nodeValue為null(4)parentNode可能是Document或Element(5)子節(jié)點可能是Element,Text,Comment,Processing_Instruction,CDATASection或EntityReference
Text表示文本節(jié)點,它包含的是純文本內容,不能包含html代碼,但可以包含轉義后的html代碼。Text有下面的特性:(1)nodeType為3(2)nodeName為#text(3)nodeValue為文本內容(4)parentNode是一個Element(5)沒有子節(jié)點
Comment表示HTML文檔中的注釋,它有下面的幾種特征:(1)nodeType為8(2)nodeName為#comment(3)nodeValue為注釋的內容(4)parentNode可能是Document或Element(5)沒有子節(jié)點
Document表示文檔,在瀏覽器中,document對象是HTMLDocument的一個實例,表示整個頁面,它同時也是window對象的一個屬性。Document有下面的特性:(1)nodeType為9(2)nodeName為#document(3)nodeValue為null(4)parentNode為null(5)子節(jié)點可能是一個DocumentType或Element
DocumentFragment是所有節(jié)點中唯一一個沒有對應標記的類型,它表示一種輕量級的文檔,可能當作一個臨時的倉庫用來保存可能會添加到文檔中的節(jié)點。DocumentFragment有下面的特性:(1)nodeType為11(2)nodeName為#document-fragment(3)nodeValue為null(4)parentNode為null
用如其名,這類API是用來創(chuàng)建節(jié)點的
createElement通過傳入指定的一個標簽名來創(chuàng)建一個元素,如果傳入的標簽名是一個未知的,則會創(chuàng)建一個自定義的標簽,注意:IE8以下瀏覽器不支持自定義標簽。
語法
復制代碼 let element=document.createElement(tagName);
使用createElement要注意:通過createElement創(chuàng)建的元素并不屬于HTML文檔,它只是創(chuàng)建出來,并未添加到HTML文檔中,要調用appendChild或insertBefore等方法將其添加到HTML文檔樹中。
例子:
復制代碼 let elem=document.createElement("div");
elem.id='test';
elem.style='color: red';
elem.innerHTML='我是新創(chuàng)建的節(jié)點';
document.body.appendChild(elem);
運行結果為:
createTextNode用來創(chuàng)建一個文本節(jié)點
語法
復制代碼 var text=document.createTextNode(data);
createTextNode接收一個參數(shù),這個參數(shù)就是文本節(jié)點中的文本,和createElement一樣,創(chuàng)建后的文本節(jié)點也只是獨立的一個節(jié)點,同樣需要appendChild將其添加到HTML文檔樹中
例子:
復制代碼 var node=document.createTextNode("我是文本節(jié)點");
document.body.appendChild(node);
運行結果為:
cloneNode返回調用該方法的節(jié)點的一個副本
語法
復制代碼 var dupNode=node.cloneNode(deep);
node 將要被克隆的節(jié)點dupNode 克隆生成的副本節(jié)點deep(可選)是否采用深度克隆,如果為true,則該節(jié)點的所有后代節(jié)點也都會被克隆,如果為false,則只克隆該節(jié)點本身.
這里有幾點要注意:(1)和createElement一樣,cloneNode創(chuàng)建的節(jié)點只是游離有HTML文檔外的節(jié)點,要調用appendChild方法才能添加到文檔樹中(2)如果復制的元素有id,則其副本同樣會包含該id,由于id具有唯一性,所以在復制節(jié)點后必須要修改其id(3)調用接收的deep參數(shù)最好傳入,如果不傳入該參數(shù),不同瀏覽器對其默認值的處理可能不同
注意如果被復制的節(jié)點綁定了事件,則副本也會跟著綁定該事件嗎?這里要分情況討論:(1)如果是通過addEventListener或者比如onclick進行綁定事件,則副本節(jié)點不會綁定該事件(2)如果是內聯(lián)方式綁定比如:<div onclick="showParent()"></div>,這樣的話,副本節(jié)點同樣會觸發(fā)事件。
例子:
復制代碼<body>
<div id="parent">
我是父元素的文本
<br/>
<span>
我是子元素
</span>
</div>
<button id="btnCopy">復制</button>
</body>
<script>
var parent=document.getElementById("parent");
document.getElementById("btnCopy").onclick=function(){
var parent2=parent.cloneNode(true);
parent2.id="parent2";
document.body.appendChild(parent2);
}
</script>
運行結果為:
DocumentFragments 是DOM節(jié)點。它們不是主DOM樹的一部分。通常的用例是創(chuàng)建文檔片段,將元素附加到文檔片段,然后將文檔片段附加到DOM樹。在DOM樹中,文檔片段被其所有的子元素所代替。因為文檔片段存在于內存中,并不在DOM樹中,所以將子元素插入到文檔片段時不會引起頁面回流(reflow)(對元素位置和幾何上的計算)。因此,使用文檔片段document fragments 通常會起到優(yōu)化性能的作用。
語法
復制代碼 let fragment=document.createDocumentFragment();
例子:
復制代碼<body>
<ul id="ul"></ul>
</body>
<script>
(function()
{
var start=Date.now();
var str='', li;
var ul=document.getElementById('ul');
var fragment=document.createDocumentFragment();
for(var i=0; i<1000; i++)
{
li=document.createElement('li');
li.textContent='第'+(i+1)+'個子節(jié)點';
fragment.appendChild(li);
}
ul.appendChild(fragment);
})();
</script>
運行結果為:
節(jié)點創(chuàng)建型API主要包括createElement,createTextNode,cloneNode和createDocumentFragment四個方法,需要注意下面幾點:(1)它們創(chuàng)建的節(jié)點只是一個孤立的節(jié)點,要通過appendChild添加到文檔中(2)cloneNode要注意如果被復制的節(jié)點是否包含子節(jié)點以及事件綁定等問題(3)使用createDocumentFragment來解決添加大量節(jié)點時的性能問題
前面我們提到節(jié)點創(chuàng)建型API,它們只是創(chuàng)建節(jié)點,并沒有真正修改到頁面內容,而是要調用·appendChild·來將其添加到文檔樹中。我在這里將這類會修改到頁面內容歸為一類。修改頁面內容的api主要包括:appendChild,insertBefore,removeChild,replaceChild。
appendChild我們在前面已經(jīng)用到多次,就是將指定的節(jié)點添加到調用該方法的節(jié)點的子元素的末尾。
語法
復制代碼 parent.appendChild(child);
child節(jié)點將會作為parent節(jié)點的最后一個子節(jié)點。appendChild這個方法很簡單,但是還有有一點需要注意:如果被添加的節(jié)點是一個頁面中存在的節(jié)點,則執(zhí)行后這個節(jié)點將會添加到指定位置,其原本所在的位置將移除該節(jié)點,也就是說不會同時存在兩個該節(jié)點在頁面上,相當于把這個節(jié)點移動到另一個地方。如果child綁定了事件,被移動時,它依然綁定著該事件。
例子:
復制代碼<body>
<div id="child">
要被添加的節(jié)點
</div>
<br/>
<br/>
<br/>
<div id="parent">
要移動的位置
</div>
<input id="btnMove" type="button" value="移動節(jié)點" />
</body>
<script>
document.getElementById("btnMove").onclick=function(){
var child=document.getElementById("child");
document.getElementById("parent").appendChild(child);
}
</script>
運行結果:
insertBefore用來添加一個節(jié)點到一個參照節(jié)點之前
語法
復制代碼 parentNode.insertBefore(newNode,refNode);
parentNode表示新節(jié)點被添加后的父節(jié)點newNode表示要添加的節(jié)點refNode表示參照節(jié)點,新節(jié)點會添加到這個節(jié)點之前
例子:
復制代碼<body>
<div id="parent">
父節(jié)點
<div id="child">
子元素
</div>
</div>
<input type="button" id="insertNode" value="插入節(jié)點" />
</body>
<script>
var parent=document.getElementById("parent");
var child=document.getElementById("child");
document.getElementById("insertNode").onclick=function(){
var newNode=document.createElement("div");
newNode.textContent="新節(jié)點"
parent.insertBefore(newNode,child);
}
</script>
運行結果:
關于第二個參數(shù)參照節(jié)點還有幾個注意的地方:(1)refNode是必傳的,如果不傳該參數(shù)會報錯(2)如果refNode是undefined或null,則insertBefore會將節(jié)點添加到子元素的末尾
刪除指定的子節(jié)點并返回
語法
復制代碼 var deletedChild=parent.removeChild(node);
deletedChild指向被刪除節(jié)點的引用,它等于node,被刪除的節(jié)點仍然存在于內存中,可以對其進行下一步操作。注意:如果被刪除的節(jié)點不是其子節(jié)點,則程序將會報錯。我們可以通過下面的方式來確保可以刪除:
復制代碼if(node.parentNode){
node.parentNode.removeChild(node);
}
運行結果:
通過節(jié)點自己獲取節(jié)點的父節(jié)點,然后將自身刪除
replaceChild用于使用一個節(jié)點替換另一個節(jié)點
語法
復制代碼 parent.replaceChild(newChild,oldChild);
newChild是替換的節(jié)點,可以是新的節(jié)點,也可以是頁面上的節(jié)點,如果是頁面上的節(jié)點,則其將被轉移到新的位置oldChild是被替換的節(jié)點
例子:
復制代碼<body>
<div id="parent">
父節(jié)點
<div id="child">
子元素
</div>
</div>
<input type="button" id="insertNode" value="替換節(jié)點" />
</body>
<script>
var parent=document.getElementById("parent");
var child=document.getElementById("child");
document.getElementById("insertNode").onclick=function(){
var newNode=document.createElement("div");
newNode.textContent="新節(jié)點"
parent.replaceChild(newNode,child)
}
運行結果:
頁面修改型API主要是這四個接口,要注意幾個特點:(1)不管是新增還是替換節(jié)點,如果新增或替換的節(jié)點是原本存在頁面上的,則其原來位置的節(jié)點將被移除,也就是說同一個節(jié)點不能存在于頁面的多個位置(2)節(jié)點本身綁定的事件會不會消失,會一直保留著。
這個接口很簡單,根據(jù)元素id返回元素,返回值是Element類型,如果不存在該元素,則返回null
語法
復制代碼 var element=document.getElementById(id);
使用這個接口有幾點要注意:(1)元素的Id是大小寫敏感的,一定要寫對元素的id(2)HTML文檔中可能存在多個id相同的元素,則返回第一個元素(3)只從文檔中進行搜索元素,如果創(chuàng)建了一個元素并指定id,但并沒有添加到文檔中,則這個元素是不會被查找到的
例子:
復制代碼<body>
<p id="para1">Some text here</p>
<button onclick="changeColor('blue');">blue</button>
<button onclick="changeColor('red');">red</button>
</body>
<script>
function changeColor(newColor) {
var elem=document.getElementById("para1");
elem.style.color=newColor;
}
</script>
運行結果:
返回一個包括所有給定標簽名稱的元素的HTML集合HTMLCollection。 整個文件結構都會被搜索,包括根節(jié)點。返回的 HTML集合是動態(tài)的, 意味著它可以自動更新自己來保持和 DOM 樹的同步而不用再次調用document.getElementsByTagName()
語法
復制代碼 var elements=document.getElementsByTagName(name);
(1)如果要對HTMLCollection集合進行循環(huán)操作,最好將其長度緩存起來,因為每次循環(huán)都會去計算長度,暫時緩存起來可以提高效率(2)如果沒有存在指定的標簽,該接口返回的不是null,而是一個空的HTMLCollection(3)name是一個代表元素的名稱的字符串。特殊字符 "*" 代表了所有元素。
例子:
復制代碼<body>
<div>div1</div>
<div>div2</div>
<input type="button" value="顯示數(shù)量" id="btnShowCount"/>
<input type="button" value="新增div" id="btnAddDiv"/>
</body>
<script>
var divList=document.getElementsByTagName("div");
document.getElementById("btnAddDiv").onclick=function(){
var div=document.createElement("div");
div.textContent="div" + (divList.length+1);
document.body.appendChild(div);
}
document.getElementById("btnShowCount").onclick=function(){
alert(divList.length);
}
</script>
這段代碼中有兩個按鈕,一個按鈕是顯示HTMLCollection元素的個數(shù),另一個按鈕可以新增一個div標簽到文檔中。前面提到HTMLCollcetion元素是即時的表示該集合是隨時變化的,也就是是文檔中有幾個div,它會隨時進行變化,當我們新增一個div后,再訪問HTMLCollection時,就會包含這個新增的div。
運行結果:
getElementsByName主要是通過指定的name屬性來獲取元素,它返回一個即時的NodeList對象
語法
復制代碼 var elements=document.getElementsByName(name)
使用這個接口主要要注意幾點:(1)返回對象是一個即時的NodeList,它是隨時變化的(2)在HTML元素中,并不是所有元素都有name屬性,比如div是沒有name屬性的,但是如果強制設置div的name屬性,它也是可以被查找到的(3)在IE中,如果id設置成某個值,然后傳入getElementsByName的參數(shù)值和id值一樣,則這個元素是會被找到的,所以最好不好設置同樣的值給id和name
例子:
復制代碼<script type="text/javascript">
function getElements()
{
var x=document.getElementsByName("myInput");
alert(x.length);
}
</script>
<body>
<input name="myInput" type="text" size="20" /><br />
<input name="myInput" type="text" size="20" /><br />
<input name="myInput" type="text" size="20" /><br />
<br />
<input type="button" onclick="getElements()" value="How many elements named 'myInput'?" />
</body>
運行結果:
這個API是根據(jù)元素的class返回一個即時的HTMLCollection
語法
復制代碼 var elements=document.getElementsByClassName(names); // or:
var elements=rootElement.getElementsByClassName(names);
這個接口有下面幾點要注意:(1)返回結果是一個即時的HTMLCollection,會隨時根據(jù)文檔結構變化(2)IE9以下瀏覽器不支持(3)如果要獲取2個以上classname,可傳入多個classname,每個用空格相隔,例如
復制代碼 var elements=document.getElementsByClassName("test1 test2");
例子:
復制代碼 var elements=document.getElementsByClassName('test');
復制代碼 var elements=document.getElementsByClassName('red test');
復制代碼 var elements=document.getElementById('main').getElementsByClassName('test');
復制代碼 var testElements=document.getElementsByClassName('test');
var testDivs=Array.prototype.filter.call(testElements, function(testElement){
return testElement.nodeName==='DIV';;
});
這兩個API很相似,通過css選擇器來查找元素,注意選擇器要符合CSS選擇器的規(guī)則
document.querySelector返回第一個匹配的元素,如果沒有匹配的元素,則返回null
語法
復制代碼 var element=document.querySelector(selectors);
注意,由于返回的是第一個匹配的元素,這個api使用的深度優(yōu)先搜索來獲取元素。
例子:
復制代碼<body>
<div>
<div>
<span class="test">第三級的span</span>
</div>
</div>
<div class="test">
同級的第二個div
</div>
<input type="button" id="btnGet" value="獲取test元素" />
</body>
<script>
document.getElementById("btnGet").addEventListener("click",function(){
var element=document.querySelector(".test");
alert(element.textContent);
})
</script>
兩個class都包含“test”的元素,一個在文檔樹的前面,但是它在第三級,另一個在文檔樹的后面,但它在第一級,通過querySelector獲取元素時,它通過深度優(yōu)先搜索,拿到文檔樹前面的第三級的元素。運行結果:
語法
復制代碼 var elementList=document.querySelectorAll(selectors);
例子:
復制代碼 var matches=document.querySelectorAll("div.note, div.alert");
返回一個文檔中所有的class為"note"或者"alert"的div元素
復制代碼<body>
<div class="test">
class為test
</div>
<div id="test">
id為test
</div>
<input id="btnShow" type="button" value="顯示內容" />
</body>
<script>
document.getElementById("btnShow").addEventListener("click",function(){
var elements=document.querySelectorAll("#test,.test");
for(var i=0,length=elements.length;i<length;i++){
alert(elements[i].textContent);
}
})
</script>
這段代碼通過querySelectorAll,使用id選擇器和class選擇器選擇了兩個元素,并依次輸出其內容。要注意兩點:(1)querySelectorAll也是通過深度優(yōu)先搜索,搜索的元素順序和選擇器的順序無關(2)返回的是一個非即時的NodeList,也就是說結果不會隨著文檔樹的變化而變化兼容性問題:querySelector和querySelectorAll在ie8以下的瀏覽器不支持。
運行結果:
在html文檔中的每個節(jié)點之間的關系都可以看成是家譜關系,包含父子關系,兄弟關系等等
每個節(jié)點都有一個parentNode屬性,它表示元素的父節(jié)點。Element的父節(jié)點可能是Element,Document或DocumentFragment
返回元素的父元素節(jié)點,與parentNode的區(qū)別在于,其父節(jié)點必須是一個Element,如果不是,則返回null
返回一個即時的NodeList,表示元素的子節(jié)點列表,子節(jié)點可能會包含文本節(jié)點,注釋節(jié)點等
一個即時的HTMLCollection,子節(jié)點都是Element,IE9以下瀏覽器不支持children屬性為只讀屬性,對象類型為HTMLCollection,你可以使用elementNodeReference.children[1].nodeName來獲取某個子元素的標簽名稱
只讀屬性返回樹中節(jié)點的第一個子節(jié)點,如果節(jié)點是無子節(jié)點,則返回 null
返回當前節(jié)點的最后一個子節(jié)點。如果父節(jié)點為一個元素節(jié)點,則子節(jié)點通常為一個元素節(jié)點,或一個文本節(jié)點,或一個注釋節(jié)點。如果沒有子節(jié)點,則返回null
返回一個布爾值,表明當前節(jié)點是否包含有子節(jié)點.
返回當前節(jié)點的前一個兄弟節(jié)點,沒有則返回nullGecko內核的瀏覽器會在源代碼中標簽內部有空白符的地方插入一個文本結點到文檔中.因此,使用諸如Node.firstChild和Node.previousSibling之類的方法可能會引用到一個空白符文本節(jié)點, 而不是使用者所預期得到的節(jié)點
previousElementSibling返回當前元素在其父元素的子元素節(jié)點中的前一個元素節(jié)點,如果該元素已經(jīng)是第一個元素節(jié)點,則返回null,該屬性是只讀的。注意IE9以下瀏覽器不支持
Node.nextSibling是一個只讀屬性,返回其父節(jié)點的childNodes列表中緊跟在其后面的節(jié)點,如果指定的節(jié)點為最后一個節(jié)點,則返回nullGecko內核的瀏覽器會在源代碼中標簽內部有空白符的地方插入一個文本結點到文檔中.因此,使用諸如Node.firstChild和Node.previousSibling之類的方法可能會引用到一個空白符文本節(jié)點, 而不是使用者所預期得到的節(jié)點
nextElementSibling返回當前元素在其父元素的子元素節(jié)點中的后一個元素節(jié)點,如果該元素已經(jīng)是最后一個元素節(jié)點,則返回null,該屬性是只讀的。注意IE9以下瀏覽器不支持
設置指定元素上的一個屬性值。如果屬性已經(jīng)存在,則更新該值; 否則將添加一個新的屬性用指定的名稱和值
語法
復制代碼 element.setAttribute(name, value);
其中name是特性名,value是特性值。如果元素不包含該特性,則會創(chuàng)建該特性并賦值。
例子:
復制代碼<body>
<div id="div1">ABC</div>
</body>
<script>
let div1=document.getElementById("div1");
div1.setAttribute("align", "center");
</script>
運行結果:
如果元素本身包含指定的特性名為屬性,則可以世界訪問屬性進行賦值,比如下面兩條代碼是等價的:
復制代碼 element.setAttribute("id","test");
element.id="test";
getAttribute()返回元素上一個指定的屬性值。如果指定的屬性不存在,則返回null或""(空字符串)
語法
復制代碼 let attribute=element.getAttribute(attributeName);
attribute是一個包含attributeName屬性值的字符串。attributeName是你想要獲取的屬性值的屬性名稱
例子:
復制代碼<body>
<div id="div1">ABC</div>
</body>
<script>
let div1=document.getElementById("div1");
let align=div1.getAttribute("align");
alert(align);
</script>
運行結果:
removeAttribute()從指定的元素中刪除一個屬性
語法
復制代碼 element.removeAttribute(attrName)
attrName是一個字符串,將要從元素中刪除的屬性名
例子:
復制代碼<body>
<div id="div1" style="color:red" width="200px">ABC
</div>
</body>
<script>
let div=document.getElementById("div1")
div.removeAttribute("style");
</script>
在運行之前div有個style="color:red"的屬性,在運行之后這個屬性就被刪除了
運行結果:
Window.getComputedStyle()方法給出應用活動樣式表后的元素的所有CSS屬性的值,并解析這些值可能包含的任何基本計算假設某個元素并未設置高度而是通過其內容將其高度撐開,這時候要獲取它的高度就要用到getComputedStyle
語法
復制代碼 var style=window.getComputedStyle(element[, pseudoElt]);
element是要獲取的元素,pseudoElt指定一個偽元素進行匹配。返回的style是一個CSSStyleDeclaration對象。通過style可以訪問到元素計算后的樣式
getBoundingClientRect用來返回元素的大小以及相對于瀏覽器可視窗口的位置
語法
復制代碼 var clientRect=element.getBoundingClientRect();
clientRect是一個DOMRect對象,包含left,top,right,bottom,它是相對于可視窗口的距離,滾動位置發(fā)生改變時,它們的值是會發(fā)生變化的。除了IE9以下瀏覽器,還包含元素的height和width等數(shù)據(jù)
例子:
復制代碼 elem.style.color='red';
elem.style.setProperty('font-size', '16px');
elem.style.removeProperty('color');
例子:
復制代碼 var style=document.createElement('style');
style.innerHTML='body{color:red} #top:hover{background-color: red;color: white;}';
document.head.appendChild(style););
JavaScript中的API太多了,將這些API記住并熟練使用對JavaScript的學習是有很大的幫助
作者:yyzclyang
鏈接:https://juejin.cn/post/6844903604445249543
數(shù)字化轉型潮流席卷各大行業(yè)的今天,越來越多的企業(yè)開始重視 BI(商業(yè)智能)技術的部署和應用,期望從不斷增長的數(shù)據(jù)資源中獲得更多業(yè)務價值,從而提升利潤、控制風險、降低成本。BI 能整合、組織和分析數(shù)據(jù),將數(shù)據(jù)轉化為有價值的信息,為企業(yè)管理和決策提供支持,成為企業(yè)迎接變革和商業(yè)創(chuàng)新的決勝因素。
由于 BI 技術的重要性,企業(yè)更希望在現(xiàn)有的業(yè)務平臺和系統(tǒng)中按需集成BI能力,從而在各類場景中充分發(fā)揮數(shù)據(jù)分析帶來的優(yōu)勢,滿足企業(yè)日益多樣化的數(shù)據(jù)分析訴求,使 BI 能力與企業(yè)業(yè)務深度融合。然而,市面上常見的 BI 工具大都是獨立、打包的整體方案,很難與前端的業(yè)務系統(tǒng)集成在一起,在實踐中常常無法滿足需求。在這樣的背景下,嵌入式 BI 應運而生。
所謂嵌入式 BI,就是在企業(yè)現(xiàn)有業(yè)務系統(tǒng)中按需集成各種類型的數(shù)據(jù)分析能力。這種集成工作一般需要考慮兩個要點:一方面,它本質上是現(xiàn)有業(yè)務系統(tǒng)的一次升級過程,需要關注升級內容與原系統(tǒng)的兼容性、穩(wěn)定性、安全性等指標;另一方面,業(yè)務側一般希望深度集成專業(yè)的數(shù)據(jù)分析組件,而不是任意掛載一個簡單的模塊應付了事。這兩個要點為開發(fā)團隊提出了更高的要求和挑戰(zhàn),需要團隊認真對待。
嵌入式數(shù)據(jù)分析模塊架構探索
對于很多中小企業(yè)而言,軟件開發(fā)團隊并不具備獨立開發(fā) In-House 嵌入 BI 方案的能力,需要尋求外部第三方供應商的支持。行業(yè)中也出現(xiàn)了很多專業(yè)的外部供應商,他們探索出了一些經(jīng)過市場驗證的嵌入式 BI 最佳實踐。我們以業(yè)內老牌知名企業(yè)葡萄城開發(fā)的 Wyn 商業(yè)智能嵌入式架構為例,探討數(shù)據(jù)分析模塊應該如何嵌入現(xiàn)有業(yè)務系統(tǒng):
如上所示,對于業(yè)務人員來說,應用功能層的數(shù)據(jù)分析儀表板、圖表、設計器、門戶等模塊,就是嵌入式 BI 方案展現(xiàn)出來的最終效果。其中,模塊的內容和形態(tài)一般是根據(jù)業(yè)務需求決定的,例如為某個銷售看板集成一些銷售數(shù)據(jù)動態(tài)圖表,實時反映各地區(qū)的當前銷售狀況。由于業(yè)務需求往往比較多樣化,嵌入模塊的內容和形態(tài)也非常多變,這就要求前端技術層具備更強的適應能力。
前端技術層在過去普遍采用 URL iFrame 架構來實現(xiàn)模塊嵌入,如今更復雜的需求更多用 DIV 方式打造解決方案。此外,以 Wyn 商業(yè)智能為例,其 BI 模塊還可以同泛微、用友 U8+、企業(yè)微信和釘釘?shù)瘸S玫钠髽I(yè)信息系統(tǒng)集成,增強它們的數(shù)據(jù)分析能力。
API 層對于嵌入式 BI 方案是非常重要的。例如,API 允許根據(jù)用戶類型打開和關閉工具欄,只允許根據(jù)使用規(guī)則顯示指定的數(shù)據(jù)源,并支持創(chuàng)建具有不同過濾器和選項的儀表板。專業(yè)的嵌入式BI可以通過調用 API,在應用軟件內對儀表板/報表進行權限管理、分類管理、重命名、刪除等深度集成操作,而應用軟件和 BI 軟件之間的接口對最終用戶是完全透明的。當然,對于較為簡單的業(yè)務需求而言,嵌入式 BI 架構中取消 API 層,或者只有簡單的 API 層也是可以接受的。
主流實現(xiàn)方案對比
如上所述,對于開發(fā)團隊而言,嵌入式 BI 方案的技術選型關鍵在于 DIV 和IFrame 架構的選擇,以及是否加入強大的 API 層。
IFrame 架構在早期的嵌入式 BI 市場非常流行,因其原理簡單、實現(xiàn)方便、開發(fā)周期較短,使企業(yè)能夠快速實現(xiàn)初期的嵌入式 BI 需求。但這種方式雖然簡單,局限性也是很大的。例如,使用 IFrame 就很難在系統(tǒng)中深度集成數(shù)據(jù)分析模塊。IFrame 更像曾經(jīng)的 Flash 元素,是一種相對獨立的模塊。它與頁面的其他元素很難融合和互動,即便可以實現(xiàn)也需要付出大量努力和代價。
相比之下,基于 JavaScript 的 DIV 層級的無縫嵌入方案,可以利用原生的JavaScript 將整個儀表板等以 DIV 的方式集成到項目中。嵌入的圖表元素具有高度開放的接口,能夠與其他元素進行數(shù)據(jù)交互。甚至 BI 軟件整體都可以通過DIV 架構直接嵌入到現(xiàn)有系統(tǒng)中,確保了無縫和直觀的用戶體驗。即便當前的業(yè)務需求僅僅停留在簡單的圖表展示層面,考慮到未來的升級和擴展?jié)摿Γ_發(fā)團隊還是最好選擇 DIV 架構來設計 BI 嵌入方案。
另一方面,API 層能夠大大簡化業(yè)務人員對嵌入式 BI 模塊的操作,往往是開發(fā)團隊需要重點實現(xiàn)的功能目標。Wyn 商業(yè)智能的嵌入式 BI 方案就提供了GraphQL API,幾乎所有界面操作均可通過 API 調用簡單完成。下圖就是一個簡單的 API 調用示例:
GraphQL API 不需要為不同的對象操作提供不同的 URL。不同對象的不同操作均通過一個統(tǒng)一的 URL(http://localhost:51980/api/graphql)調用,只是各類操作提交的數(shù)據(jù)不同。可以看到,GraphQL API 的操作非常易于上手,可以大大方便開發(fā)團隊和業(yè)務團隊,滿足各類復雜的業(yè)務需求。下面來看 Wyn 商業(yè)智能提供的一個數(shù)據(jù)查詢 API 的操作示例,從中可以體會 API 的低門檻與便利性:
當我們需要調用某個數(shù)據(jù)集,可以通過該 API 以 POST 或 GET 兩種方式完成操作。
(示例 URL 為http://10.32.5.7:51980/api/v1/datasource/b7d93485-66f2-4356-ada5-0a163579232d/query?queryType=sql&query=select *from Categories&token=27487CA0BE14CF599444E8553E5E07F78D5D1AB8646302A2715E4802FCB95F08&format=json;調用數(shù)據(jù)集的 URL 格式為 POST/GET api/v1/dataset/{document id}/query。)
POST 方式,有效負載格式:
{
"QueryType": "NONE|WAX",
"Query": "some text like a WAX statement or empty"
"DatasetParameters":{
"Parameter1": "ParameterValue1"(單值),
"Parameter2": "ParameterValue1,ParameterValue2" (多值使用逗號分隔)
},
"Format":Arrow | Json,
"Options":{
"Parameter1":"Value1"
}
}
GET 方式,查詢參數(shù)
?format=Arrow | Json
&$parameter1=value1
&$parameter2=value2_1
&$parameter2=value2_2
&option1=value1
&option2=value2
option1, option2 ...為選項參數(shù),前綴$表示數(shù)據(jù)集參數(shù),多個值通過多次重復一個參數(shù)來表示。Option 選項參數(shù)具體信息如下:
只需幾行簡單的代碼即可完成數(shù)據(jù)集的調用操作,這對嵌入式 BI 場景而言無疑是非常有價值的。API 層與 DIV 嵌入結合,可以為嵌入式 BI 場景需求提供令人滿意的解決方案。
總體而言,雖然 iFrame 架構在入門門檻、開發(fā)成本和周期方面具有一定優(yōu)勢,但隨著企業(yè)數(shù)據(jù)分析需求愈加復雜,DIV 架構很快就能表現(xiàn)出更強的擴展能力和適應性。團隊可以在初期選擇 iFrame 實現(xiàn),并在需求提升后遷移到 DIV 方案。與此同時,開發(fā)團隊往往在一開始就要考慮 API 層的實現(xiàn),為業(yè)務團隊帶來更多便利,并為后期的開發(fā)工作打好基礎。
嵌入式 BI 選型:如何挑選最適合自己的方案?
企業(yè)開發(fā)團隊在選擇嵌入式 BI 方案時,除了關注方案的開發(fā)周期、開發(fā)難度外,一般還要考慮定價模型、云端支持、業(yè)務系統(tǒng)集成支持等要素:
考慮到以上要素,實踐中中小企業(yè)和開發(fā)團隊更適合選擇成熟的第三方嵌入式 BI 方案來滿足自身需求。以 Wyn 商業(yè)智能提供的嵌入 BI 方案為例,其不僅提供了完善的功能、基于 GraphQL 的便捷 API 集成,還支持強大的授權管理、增強安全特性,兼容多種云端部署模式,且能夠方便地集成到用友、企業(yè)微信等系統(tǒng)中。該方案既可以嵌入部署也可以獨立使用,具備良好的伸縮能力,幫助企業(yè)持續(xù)深度挖掘數(shù)據(jù)價值。想要進一步了解關于嵌入式 BI 方案的相關內容,可以點擊以下鏈接獲取更多信息。
者:前端Q
轉發(fā)鏈接:https://mp.weixin.qq.com/s/ewFfXptccFs5KvjUINLGbQ
小試牛刀,實現(xiàn)了六款簡單常見HTML5 Canvas特效濾鏡,并且封裝成一個純JavaScript可調用的API文件gloomyfishfilter.js。支持的特效濾鏡分別為:
1.反色
2.灰色調
3.模糊
4.浮雕
5.雕刻
6.合理
2.灰色調:獲取一個預期點RGB值r,g,b則新的RGB值
newr=(r * 0.272)+(g * 0.534)+(b * 0.131);
newg=(r * 0.349)+(g * 0.686)+(b * 0.168);
newb=(r * 0.393)+(g * 0.769)+(b * 0.189);
3.模糊:基于一個5 * 5的卷積核
4.浮雕與雕刻:
根據(jù)當前預期的前一個預期RGB值與它的后一個重新的RGB值之差再加上128
5.總體:模擬了物體在鏡子中與之對應的效果。
var canvas=document.getElementById("target");
canvas.width=source.clientWidth;
canvas.height=source.clientHeight;
**if**(!canvas.getContext) {
console.log("Canvas not supported. Please install a HTML5compatible browser.");
**return**;
}
// get 2D context of canvas and draw image
tempContext=canvas.getContext("2d");
var source=document.getElementById("source");
tempContext.drawImage(source, 0, 0, canvas.width,canvas.height);
var canvas=document.getElementById("target");
var len=canvas.width * canvas.height * 4;
var canvasData=tempContext.getImageData(0, 0, canvas.width, canvas.height);
var binaryData=canvasData.data;
function bindButtonEvent(element, type, handler)
{
if(element.addEventListener){
element.addEventListener(type, handler,**false**);
}else{
element.attachEvent('on'+type, handler);// for IE6,7,8
}
}
<scriptsrc=*"gloomyfishfilter.js"*></script> //導入API文件
gfilter.colorInvertProcess(binaryData, len); //調用 API
<meta http-equiv="X-UA-Compatible"*content=*"chrome=IE8">
效果演示:
CSS部分:
#svgContainer {
width:800px;
height:600px;
background-color:#EEEEEE;
}
#sourceDiv { float: left; border: 2px solid blue}
#targetDiv { float: right;border: 2px solid red}
filter1.html中HTML源代碼:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="chrome=IE8">
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
<title>Canvas Filter Demo</title>
<link href="default.css" rel="stylesheet" />
<script src="gloomyfishfilter.js"></scrip>
</head>
<body>
<h1>HTML Canvas Image Process - By Gloomy Fish</h1>
<div id="svgContainer">
<div id="sourceDiv">
<img id="source" src="../test.png" />
</div>
<div id="targetDiv">
<canvas id="target"></canvas>
</div>
</div>
<div id="btn-group">
<button type="button" id="invert-button">反色</button>
<button type="button" id="adjust-button">灰色調</button>
<button type="button" id="blur-button">模糊</button>
<button type="button" id="relief-button">浮雕</button>
<button type="button" id="diaoke-button">雕刻</button>
<button type="button" id="mirror-button">鏡像</button>
</div>
</body>
</html>
filter1.html中JavaScript源代碼:
var tempContext=null; // global variable 2d context
window.onload=function() {
var source=document.getElementById("source");
var canvas=document.getElementById("target");
canvas.width=source.clientWidth;
canvas.height=source.clientHeight;
if (!canvas.getContext) {
console.log("Canvas not supported. Please install a HTML5 compatible browser.");
return;
}
// get 2D context of canvas and draw image
tempContext=canvas.getContext("2d");
tempContext.drawImage(source, 0, 0, canvas.width, canvas.height);
// initialization actions
var inButton=document.getElementById("invert-button");
var adButton=document.getElementById("adjust-button");
var blurButton=document.getElementById("blur-button");
var reButton=document.getElementById("relief-button");
var dkButton=document.getElementById("diaoke-button");
var mirrorButton=document.getElementById("mirror-button");
// bind mouse click event
bindButtonEvent(inButton, "click", invertColor);
bindButtonEvent(adButton, "click", adjustColor);
bindButtonEvent(blurButton, "click", blurImage);
bindButtonEvent(reButton, "click", fudiaoImage);
bindButtonEvent(dkButton, "click", kediaoImage);
bindButtonEvent(mirrorButton, "click", mirrorImage);
}
function bindButtonEvent(element, type, handler)
{
if(element.addEventListener) {
element.addEventListener(type, handler, false);
} else {
element.attachEvent('on'+type, handler); // for IE6,7,8
}
}
function invertColor() {
var canvas=document.getElementById("target");
var len=canvas.width * canvas.height * 4;
var canvasData=tempContext.getImageData(0, 0, canvas.width, canvas.height);
var binaryData=canvasData.data;
// Processing all the pixels
gfilter.colorInvertProcess(binaryData, len);
// Copying back canvas data to canvas
tempContext.putImageData(canvasData, 0, 0);
}
function adjustColor() {
var canvas=document.getElementById("target");
var len=canvas.width * canvas.height * 4;
var canvasData=tempContext.getImageData(0, 0, canvas.width, canvas.height);
var binaryData=canvasData.data;
// Processing all the pixels
gfilter.colorAdjustProcess(binaryData, len);
// Copying back canvas data to canvas
tempContext.putImageData(canvasData, 0, 0);
}
function blurImage()
{
var canvas=document.getElementById("target");
var len=canvas.width * canvas.height * 4;
var canvasData=tempContext.getImageData(0, 0, canvas.width, canvas.height);
// Processing all the pixels
gfilter.blurProcess(tempContext, canvasData);
// Copying back canvas data to canvas
tempContext.putImageData(canvasData, 0, 0);
}
function fudiaoImage()
{
var canvas=document.getElementById("target");
var len=canvas.width * canvas.height * 4;
var canvasData=tempContext.getImageData(0, 0, canvas.width, canvas.height);
// Processing all the pixels
gfilter.reliefProcess(tempContext, canvasData);
// Copying back canvas data to canvas
tempContext.putImageData(canvasData, 0, 0);
}
function kediaoImage()
{
var canvas=document.getElementById("target");
var len=canvas.width * canvas.height * 4;
var canvasData=tempContext.getImageData(0, 0, canvas.width, canvas.height);
// Processing all the pixels
gfilter.diaokeProcess(tempContext, canvasData);
// Copying back canvas data to canvas
tempContext.putImageData(canvasData, 0, 0);
}
function mirrorImage()
{
var canvas=document.getElementById("target");
var len=canvas.width * canvas.height * 4;
var canvasData=tempContext.getImageData(0, 0, canvas.width, canvas.height);
// Processing all the pixels
gfilter.mirrorProcess(tempContext, canvasData);
// Copying back canvas data to canvas
tempContext.putImageData(canvasData, 0, 0);
}
濾鏡源代碼(gloomyfishfilter.js):
var gfilter={
type: "canvas",
name: "filters",
author: "zhigang",
getInfo: function () {
return this.author + ' ' + this.type + ' ' + this.name;
},
/**
* invert color value of pixel, new pixel=RGB(255-r, 255-g, 255 - b)
*
* @param binaryData - canvas's imagedata.data
* @param l - length of data (width * height of image data)
*/
colorInvertProcess: function(binaryData, l) {
for (var i=0; i < l; i +=4) {
var r=binaryData[i];
var g=binaryData[i + 1];
var b=binaryData[i + 2];
binaryData[i]=255-r;
binaryData[i + 1]=255-g;
binaryData[i + 2]=255-b;
}
},
/**
* adjust color values and make it more darker and gray...
*
* @param binaryData
* @param l
*/
colorAdjustProcess: function(binaryData, l) {
for (var i=0; i < l; i +=4) {
var r=binaryData[i];
var g=binaryData[i + 1];
var b=binaryData[i + 2];
binaryData[i]=(r * 0.272) + (g * 0.534) + (b * 0.131);
binaryData[i + 1]=(r * 0.349) + (g * 0.686) + (b * 0.168);
binaryData[i + 2]=(r * 0.393) + (g * 0.769) + (b * 0.189);
}
},
/**
* deep clone image data of canvas
*
* @param context
* @param src
* @returns
*/
copyImageData: function(context, src)
{
var dst=context.createImageData(src.width, src.height);
dst.data.set(src.data);
return dst;
},
/**
* convolution - keneral size 5*5 - blur effect filter(模糊效果)
*
* @param context
* @param canvasData
*/
blurProcess: function(context, canvasData) {
console.log("Canvas Filter - blur process");
var tempCanvasData=this.copyImageData(context, canvasData);
var sumred=0.0, sumgreen=0.0, sumblue=0.0;
for ( var x=0; x < tempCanvasData.width; x++) {
for ( var y=0; y < tempCanvasData.height; y++) {
// Index of the pixel in the array
var idx=(x + y * tempCanvasData.width) * 4;
for(var subCol=-2; subCol<=2; subCol++) {
var colOff=subCol + x;
if(colOff <0 || colOff >=tempCanvasData.width) {
colOff=0;
}
for(var subRow=-2; subRow<=2; subRow++) {
var rowOff=subRow + y;
if(rowOff < 0 || rowOff >=tempCanvasData.height) {
rowOff=0;
}
var idx2=(colOff + rowOff * tempCanvasData.width) * 4;
var r=tempCanvasData.data[idx2 + 0];
var g=tempCanvasData.data[idx2 + 1];
var b=tempCanvasData.data[idx2 + 2];
sumred +=r;
sumgreen +=g;
sumblue +=b;
}
}
// calculate new RGB value
var nr=(sumred / 25.0);
var ng=(sumgreen / 25.0);
var nb=(sumblue / 25.0);
// clear previous for next pixel point
sumred=0.0;
sumgreen=0.0;
sumblue=0.0;
// assign new pixel value
canvasData.data[idx + 0]=nr; // Red channel
canvasData.data[idx + 1]=ng; // Green channel
canvasData.data[idx + 2]=nb; // Blue channel
canvasData.data[idx + 3]=255; // Alpha channel
}
}
},
/**
* after pixel value - before pixel value + 128
* 浮雕效果
*/
reliefProcess: function(context, canvasData) {
console.log("Canvas Filter - relief process");
var tempCanvasData=this.copyImageData(context, canvasData);
for ( var x=1; x < tempCanvasData.width-1; x++)
{
for ( var y=1; y < tempCanvasData.height-1; y++)
{
// Index of the pixel in the array
var idx=(x + y * tempCanvasData.width) * 4;
var bidx=((x-1) + y * tempCanvasData.width) * 4;
var aidx=((x+1) + y * tempCanvasData.width) * 4;
// calculate new RGB value
var nr=tempCanvasData.data[aidx + 0] - tempCanvasData.data[bidx + 0] + 128;
var ng=tempCanvasData.data[aidx + 1] - tempCanvasData.data[bidx + 1] + 128;
var nb=tempCanvasData.data[aidx + 2] - tempCanvasData.data[bidx + 2] + 128;
nr=(nr < 0) ? 0 : ((nr >255) ? 255 : nr);
ng=(ng < 0) ? 0 : ((ng >255) ? 255 : ng);
nb=(nb < 0) ? 0 : ((nb >255) ? 255 : nb);
// assign new pixel value
canvasData.data[idx + 0]=nr; // Red channel
canvasData.data[idx + 1]=ng; // Green channel
canvasData.data[idx + 2]=nb; // Blue channel
canvasData.data[idx + 3]=255; // Alpha channel
}
}
},
/**
* before pixel value - after pixel value + 128
* 雕刻效果
*
* @param canvasData
*/
diaokeProcess: function(context, canvasData) {
console.log("Canvas Filter - process");
var tempCanvasData=this.copyImageData(context, canvasData);
for ( var x=1; x < tempCanvasData.width-1; x++)
{
for ( var y=1; y < tempCanvasData.height-1; y++)
{
// Index of the pixel in the array
var idx=(x + y * tempCanvasData.width) * 4;
var bidx=((x-1) + y * tempCanvasData.width) * 4;
var aidx=((x+1) + y * tempCanvasData.width) * 4;
// calculate new RGB value
var nr=tempCanvasData.data[bidx + 0] - tempCanvasData.data[aidx + 0] + 128;
var ng=tempCanvasData.data[bidx + 1] - tempCanvasData.data[aidx + 1] + 128;
var nb=tempCanvasData.data[bidx + 2] - tempCanvasData.data[aidx + 2] + 128;
nr=(nr < 0) ? 0 : ((nr >255) ? 255 : nr);
ng=(ng < 0) ? 0 : ((ng >255) ? 255 : ng);
nb=(nb < 0) ? 0 : ((nb >255) ? 255 : nb);
// assign new pixel value
canvasData.data[idx + 0]=nr; // Red channel
canvasData.data[idx + 1]=ng; // Green channel
canvasData.data[idx + 2]=nb; // Blue channel
canvasData.data[idx + 3]=255; // Alpha channel
}
}
},
/**
* mirror reflect
*
* @param context
* @param canvasData
*/
mirrorProcess : function(context, canvasData) {
console.log("Canvas Filter - process");
var tempCanvasData=this.copyImageData(context, canvasData);
for ( var x=0; x < tempCanvasData.width; x++) // column
{
for ( var y=0; y < tempCanvasData.height; y++) // row
{
// Index of the pixel in the array
var idx=(x + y * tempCanvasData.width) * 4;
var midx=(((tempCanvasData.width -1) - x) + y * tempCanvasData.width) * 4;
// assign new pixel value
canvasData.data[midx + 0]=tempCanvasData.data[idx + 0]; // Red channel
canvasData.data[midx + 1]=tempCanvasData.data[idx + 1]; ; // Green channel
canvasData.data[midx + 2]=tempCanvasData.data[idx + 2]; ; // Blue channel
canvasData.data[midx + 3]=255; // Alpha channel
}
}
},
};
感謝閱讀,如果你覺得我今天分享的內容,不錯,請點一個贊,謝謝!!
*請認真填寫需求信息,我們會在24小時內與您取得聯(lián)系。