• xxx


    JavaScript:浅谈操作 BOM 和 DOM

    浏览器说明

    由于JavaScript的出现就是为了能在浏览器中运行,所以,浏览器自然是JavaScript开发者必须要关注的。

    目前主流的浏览器分这么几种:

    • IE 6~11:国内用得最多的IE浏览器,历来对W3C标准支持差。从IE10开始支持ES6标准;
    • Chrome:Google出品的基于Webkit内核浏览器,内置了非常强悍的JavaScript引擎——V8。由于Chrome一经安装就时刻保持自升级,所以不用管它的版本,最新版早就支持ES6了;
    • Safari:Apple的Mac系统自带的基于Webkit内核的浏览器,从OS X 10.7 Lion自带的6.1版本开始支持ES6,目前最新的OS X 10.11 El Capitan自带的Safari版本是9.x,早已支持ES6;
    • Firefox:Mozilla自己研制的Gecko内核和JavaScript引擎OdinMonkey。早期的Firefox按版本发布,后来终于聪明地学习Chrome的做法进行自升级,时刻保持最新;
    • 移动设备上目前iOS和Android两大阵营分别主要使用Apple的Safari和Google的Chrome,由于两者都是Webkit核心,结果HTML5首先在手机上全面普及(桌面绝对是Microsoft拖了后腿),对JavaScript的标准支持也很好,最新版本均支持ES6。

    其他浏览器如Opera等由于市场份额太小就被自动忽略了。

    另外还要注意识别各种国产浏览器,如某某安全浏览器,某某旋风浏览器,它们只是做了一个壳,其核心调用的是IE,也有号称同时支持IE和Webkit的“双核”浏览器。

    不同的浏览器对JavaScript支持的差异主要是,有些API的接口不一样,比如AJAX,File接口。对于ES6标准,不同的浏览器对各个特性支持也不一样。

    在编写JavaScript的时候,就要充分考虑到浏览器的差异,尽量让同一份JavaScript代码能运行在不同的浏览器中。

    浏览器对象

    JavaScript可以获取浏览器提供的很多对象,并进行操作。

    window

    window对象不但充当全局作用域,而且表示浏览器窗口。

    window对象有innerWidth和innerHeight属性,可以获取浏览器窗口的内部宽度和高度。内部宽高是指除去菜单栏、工具栏、边框等占位元素后,用于显示网页的净宽高。

    对应的,还有一个outerWidth和outerHeight属性,可以获取浏览器窗口的整个宽高。

    // 可以调整浏览器窗口大小试试:
    console.log('window inner size: ' + window.innerWidth + ' x ' + window.innerHeight);
    

    navigator 对象表示浏览器的信息,最常用的属性包括:

    • navigator.appName:浏览器名称;
    • navigator.appVersion:浏览器版本;
    • navigator.language:浏览器设置的语言;
    • navigator.platform:操作系统类型;
    • navigator.userAgent:浏览器设定的User-Agent字符串
    console.log('appName = ' + navigator.appName);
    console.log('appVersion = ' + navigator.appVersion);
    console.log('language = ' + navigator.language);
    console.log('platform = ' + navigator.platform);
    console.log('userAgent = ' + navigator.userAgent);
    

    请注意,navigator的信息可以很容易地被用户修改,所以JavaScript读取的值不一定是正确的。

    screen对象表示屏幕的信息,常用的属性有:

    • screen.width:屏幕宽度,以像素为单位;
    • screen.height:屏幕高度,以像素为单位;
    • screen.colorDepth:返回颜色位数,如8、16、24。
    console.log('Screen size = ' + screen.width + ' x ' + screen.height);
    

    location对象表示当前页面的URL信息。例如,一个完整的URL:

    可以用location.href获取。要获得URL各个部分的值,可以这么写:

    location.protocol; // 'http'
    location.host; // 'www.example.com'
    location.port; // '8080'
    location.pathname; // '/path/index.html'
    location.search; // '?a=1&b=2'
    location.hash; // 'TOP'
    

    如果要重新加载当前页面,调用location.reload()方法非常方便。

    document对象表示当前页面。由于HTML在浏览器中以DOM形式表示为树形结构,document对象就是整个DOM树的根节点。

    document的title属性是从HTML文档中的xxx读取的,但是可以动态改变:

    document.title = '努力学习JavaScript!';
    
    • 用document对象提供的getElementById()和getElementsByTagName()可以按ID获得一个DOM节点和按Tag名称获得一组DOM节点!
    • JavaScript可以通过document.cookie读取到当前页面的Cookie:

    history

    history对象保存了浏览器的历史记录,JavaScript可以调用history对象的back()或forward (),相当于用户点击了浏览器的“后退”或“前进”按钮。

    这个对象属于历史遗留对象,对于现代Web页面来说,由于大量使用AJAX和页面交互,简单粗暴地调用history.back()可能会让用户感到非常愤怒。

    新手开始设计Web页面时喜欢在登录页登录成功时调用history.back(),试图回到登录前的页面。这是一种错误的方法。

    任何情况,你都不应该使用history这个对象了。

    DOM

    由于HTML文档被浏览器解析后就是一棵DOM树,要改变HTML的结构,就需要通过JavaScript来操作DOM。

    始终记住DOM是一个树形结构。操作一个DOM节点实际上就是这么几个操作:

    • 更新:更新该DOM节点的内容,相当于更新了该DOM节点表示的HTML的内容;
    • 遍历:遍历该DOM节点下的子节点,以便进行进一步操作;
    • 添加:在该DOM节点下新增一个子节点,相当于动态增加了一个HTML节点;
    • 删除:将该节点从HTML中删除,相当于删掉了该DOM节点的内容以及它包含的所有子节点。

    在操作一个DOM节点前,我们需要通过各种方式先拿到这个DOM节点。最常用的方法是document.getElementById()document.getElementsByTagName()

    由于ID在HTML文档中是唯一的,所以document.getElementById()可以直接定位唯一的一个DOM节点。document.getElementsByTagName()和document.getElementsByClassName()总是返回一组DOM节点。要精确地选择DOM,可以先定位父节点,再从父节点开始选择,以缩小范围。

    // 返回ID为'test'的节点:
    var test = document.getElementById('test');
    
    // 先定位ID为'test-table'的节点,再返回其内部所有tr节点:
    var trs = document.getElementById('test-table').getElementsByTagName('tr');
    
    // 先定位ID为'test-div'的节点,再返回其内部所有class包含red的节点:
    var reds = document.getElementById('test-div').getElementsByClassName('red');
    
    // 获取节点test下的所有直属子节点:
    var cs = test.children;
    
    // 获取节点test下第一个、最后一个子节点:
    var first = test.firstElementChild;
    var last = test.lastElementChild;
    

    更新DOM

    拿到一个DOM节点后,我们可以对它进行更新。

    可以直接修改节点的文本,方法有两种:

    一种是修改innerHTML属性,这个方式非常强大,不但可以修改一个DOM节点的文本内容,还可以直接通过HTML片段修改DOM节点内部的子树:

    // 设置文本为abc:
    p.innerHTML = 'ABC'; 
    

    第二种是修改innerText或textContent属性,这样可以自动对字符串进行HTML编码,保证无法设置任何HTML标签;

    // 设置文本:
    p.innerText = '<script>alert("Hi")</script>';
    // HTML被自动编码,无法设置一个<script>节点:
    // <p id="p-id">&lt;script&gt;alert("Hi")&lt;/script&gt;</p>
    

    插入DOM

    当我们获得了某个DOM节点,想在这个DOM节点内插入新的DOM,应该如何做?

    如果这个DOM节点是空的,例如,

    ,那么,直接使用innerHTML = child就可以修改DOM节点的内容,相当于“插入”了新的DOM节点。

    如果这个DOM节点不是空的,那就不能这么做,因为innerHTML会直接替换掉原来的所有子节点。

    有两个办法可以插入新的节点。一个是使用appendChild,把一个子节点添加到父节点的最后一个子节点。例如:

    <!-- HTML结构 -->
    <p id="js">JavaScript</p>
    <div id="list">
        <p id="java">Java</p>
        <p id="python">Python</p>
        <p id="scheme">Scheme</p>
    </div>
    

    JavaScript

    添加到
    的最后一项:

    var
        js = document.getElementById('js'),
        list = document.getElementById('list');
    list.appendChild(js);
    

    更多的时候我们会从零创建一个新的节点,然后插入到指定位置:

    var
        list = document.getElementById('list'),
        haskell = document.createElement('p');
    haskell.id = 'haskell';
    haskell.innerText = 'Haskell';
    list.appendChild(haskell);
    

    动态创建一个节点然后添加到DOM树中,可以实现很多功能。举个例子,下面的代码动态创建了一个