属性与特性


在进入浏览器作为客户端之后,属性(Attribute)与特性(Property)这两个名词就不断交相出现,到目前还没正式解释它们的意义。

其实在正式进入浏览器作为客户端前,对于 JavaScript 对象本身带有的名称,这边的文件都用特性这个名词。例如:

let obj = {
    x : 10,
    y : 20
};

以上文件都称,对象obj拥有特性xy,特性x的值为 10,特性y的值为 20。

HTML 本身可以拥有属性。例如:

<input name="user" value="guest">

文件中会称,<input>标签拥有属性namevalue,属性值各为userguest

浏览器会解析 HTML,为每个标签创建对应的 DOM 对象,完成解析后,对于 HTML 的所有属性(无论标签上是否有编写),DOM 对象上会创建对应的特性,通常属性名称是什么,特性名称也会是什么,如果标签上有设定某个属性,则属性值为何,特性值也就为何,如果标签上没有设置属性,则 DOM 对象上的特性会有默认值。

例如方才的 HTML 片段,<input>对应的 DOM 元素上,name特性与value特性值分别是'user''guest'。可以如下分别获取(假设是页面中第一个<input>标签):

let input = document.getElementsByTagName('user')[0];
let name = input.name;
let value = input.value;

或者使用 ES6 的解构语法:

let input = document.getElementsByTagName('user')[0];
let {name, value} = input;

像这时,DOM 元素上的namevalue特性,要称之为namevalue属性也是没错,因为此时特性对应于属性。对于 HTML 中没有设定的标签属性,DOM 上也会有对应的特性,不过都是默认值,例如,上面的<input>标签并没有设置type属性,但 DOM 对象上对应的特性,其值为'text'

不过,HTML 的属性名称未必与 DOM 对象的特性名称相对应。

例如class就是一个例子,因为class在 JavaScript 中是关键字,在 DOM 上要获取 HTML 的class属性对应名称必须使用className<label>for属性也是,因为for是 JavaScript 中的关键字,而必须使用htmlFor特性来获取。例如:

<img id="logo" src="images/caterpillar.jpg" 
     class="logo" title="Caterpillar's Logo"/>

若要以 JavaScript 获取 HTML 的class属性值,则必须:

let className = document.getElementById('logo').className;

透过 JavaScript 特性访问方式获取 HTML 属性的对应值,也未必是 HTML 属性中真正设定的值。例如,透过 JavaScript 获取<img>src,结果是绝对 URL,即使属性中设定的是相对 URL。

浏览器在解析完 HTML 后,对于HTML中有设置的属性,其实会在 DOM 对象上创建attributes特性。你可以如下显示attributes的元素值,attributes的类型是NamedNodeMap,为类数组对象,其中每个元素的类型是Attr,属性也被视为节点,因此想获取属性名称与属性值,是透过nodeNamenodeValue

let attributes = document.getElementById('logo').attributes;
Array.from(attributes).forEach(attr => {
    let {nodeName, nodeValue} = attr;
    console.log(`${nodeName}:${nodeValue}`);
});

以对象结构来表示的话:

{
    attributes : {
        '0' : {nodeName : 'id', nodeValue : 'logo', ...},
        '1' : {nodeName : 'src', nodeValue : 'images/src', ...},
        '2' : {nodeName : 'class', nodeValue : 'logo', ...},
        '3' : {nodeName : 'title', nodeValue : 'Caterpillar\’s logo', ...},
        length : 4
        ...
    },
    id : 'logo',
    src : 'https://openhome.cc/images/caterpillar.jpg',
    className : 'logo',
    title : 'Caterpillar\'s logo',
    …
}

Attr实例上的特性值,是 HTML 上真正设定的属性与值。在文件解析完毕后,DOM 对象上的特性与attributesAttr实例之特性是对应的。

注意,上面是以对象结构来示意,并不是指真正的类型就是上面所表示的。attributes的类型会是NamedNodeMap,而每个索引元素的类型会是Attr,如果手边有个 JavaScript Debugger 之类的工具,可以很方便地观察这些东西。

可以使用DOM对象的getAttribute来获取attributes中的属性,使用setAttribute设定attributes中的属性(同时亦会改变 DOM 对应的特性),使用removeAttribute来移除attributes属性。

移除属性是指移除attributes上对应的特性值,而非移除 DOM 对象上对应的特性(属性)值,DOM 对象上对应的特性(属性)值在使用removeAttribute后,只是回到默认值,而不是直接将特性移除,没有任何操作可以将DOM上对应属性的特性移除。

如果 HTML 上没有设置该属性,则使用getAttribute指定该属性会获取null,但并不表示 DOM 上没有对应属性的特性,而是该特性值会是默认值。使用setAttribute可以在attributes中设定属性,相对应的 DOM 特性值也会改变。

例如,以下的程序,只会将attributes中对应src属性的Attr实例移除,不会移除 DOM 上src特性(属性),DOM 上src只是回到''的默认值,也就是空字符串。

let img = document.getElementById('logo');
img.removeAttribute('src');
// img.src 的值是 '',不是 undefined
// img.attributes['src'] 是 undefined

如果直接改变 DOM 上的特性,attributes中对应的属性会有对应的变化,反之亦然。然而有异常,例如input元素:

<input id="user" value="guest">

使用以下的程序:

document.getElementById('user').value = 'Justin';
let user1 = document.getElementById('user').value;                 // 值是 'Justin'
let user2 = document.getElementById('user').getAttribute('value'); // 值是 'guest'

要改变attributes上的属性,可以使用setAttribute。例如:

document.getElementById('user').setAttribute('value', 'Justin');
let user1 = document.getElementById('user').value;                 // 值 'Justin'
let user2 = document.getElementById('user').getAttribute('value'); // 值 'Justin'




展开阅读全文