zhangxinxu/quiz

DOM基础测试49期

Opened this issue · 9 comments

已知页面上有一个<select>下拉框元素,假设该元素的DOM对象是select,请问:

  1. 请获得当前下拉框元素所在的<form>元素;
  2. 请问,当前下拉框选中元素的索引值(选中项的序号)是多少?
  3. 请问,我想知道第3个<options>元素的value属性值,代码该如何书写?
  4. 设置select.value = 'xxx'并不能让<select>下拉框元素触发change事件,实现使用value属性赋值的时候,下拉框元素change事件可以被触发。

本题考点想要满分不容易。大家提交回答的时候,注意缩进距离,起始位置从左边缘开始;另外,github自带代码高亮,所以请使用下面示意的格式。

```js
// 你的JS代码写在这里
 ```

其他
本期小测没有直播,也没有打分,但是会反馈要点。

const select = document.querySelector('select')
// 1
console.log(select.form)
// 2
console.log(select.selectedIndex)
// 3
console.log(select[2].value)
// 4
select.onchange = function(v) {
  console.log(v)
}
select = new Proxy(select, {
  get (target, props) {
    return target[props]
  },
  set (target, props, value) {
    target.value = value
    if (props === "value") {
      var event = document.createEvent('Event');
      event.initEvent('change', true, false);
      target.dispatchEvent(event);  
    }
    return value
  }
});
select.value = 'xxx'
// 1
select.form;

// 2
select.selectedIndex;

// 3
select[2].value;

// 4
const props = Object.getOwnPropertyDescriptor(HTMLSelectElement.prototype, 'value');
Object.defineProperty(HTMLSelectElement.prototype, 'value', {
    ...props,
    set(v) {
        props.set.call(this, v);
        this.dispatchEvent(new InputEvent('change'));
    }
})

demo

// 第1题
select.form
// 第2题
select.selectedIndex
// 第3题
select[2].value
// 第4题
Object.defineProperty(select, 'value', {
  set (val) {
    let options = Array.from(select.options)
    let curSelect = select.selectedIndex
    for (let i = 0; i < options.length; i++) {
      if (options[i].value === val) {
        if (i !== curSelect) {// 判断是否与当前选中值有所不同
          select.selectedIndex = i
          select.dispatchEvent(new Event('change'))
        }
        break
      }
    }
  },
  get () {
    return select.selectedOptions[0].value // 避免直接访问select.value
  }
})
    {
      let select = document.querySelector('select')
      console.log(select.form)
      console.log(select.selectedIndex)
      console.log(select[2].value)
    
      select = new Proxy(select, {
        get (target, key) {
          return target[key]
        },
        set (target, key, value) {
          target.value = value
          target.dispatchEvent(new InputEvent('change'))
          return value
        }
      })
    }
<form id="form1" action="">
    <div>
        <select name="hi" id="select1">
            <option value="apple">apple</option>
            <option value="bar">bar</option>
            <option value="car" selected>car</option>
            <option value="dar">dar</option>
        </select>
    </div>
</form>
<fieldset form="form1">
    <div>
        <select name="hi2" id="select2">
            <option value="apple2">apple2</option>
            <option value="bar2">bar2</option>
            <option value="car2">car2</option>
        </select>
    </div>
</fieldset>
(function () {
    const objSel1 = document.getElementById('select1');
    const objSel2 = document.getElementById('select2');

    // 第一题
    // 找到控件对应的 form 元素
    const getFormByControl = function (objControl) {
        // 能找到 form 对象
        if (objControl.form) {
            return objControl.form;
        }

        // control 元素不在form 元素内部
        const objForm = null;
        let objParent = objControl.parentElement;
        while (objParent && objParent.tagName.toLowerCase() !== 'body') {
            const strParentTagName = objParent.tagName.toLowerCase();
            if (strParentTagName === 'fieldset') {
                return objParent.form;
            }
            objParent = objParent.parentElement;
        }

        return objForm;
    };
    console.log(getFormByControl(objSel1));
    console.log(getFormByControl(objSel2));

    // 第二题
    console.log(objSel1.selectedIndex);
    console.log(objSel2.selectedIndex);

    // 第三题
    console.dir(objSel1[2].value);
    console.dir(objSel2[2].value);


    // 第四题
    objSel1.addEventListener('change', function () {
        console.log('change', this.value);
    });
    Object.defineProperty(objSel1, 'value', {
        get: function () {
            return this[this.selectedIndex] ? this[this.selectedIndex].value : '';
        },
        set: function (val) {
            // 选中找到的索引值
            const getSelectedIndex = function (objSelect, val) {
                for (let i = 0, len = objSelect.length; i < len; i++) {
                    if (objSelect[i].value === val) {
                        return i;
                    }
                }
                // 没找到返回-1;
                return -1;
            }
            this.selectedIndex = getSelectedIndex(this, val);

            // 触发 onchange
            this.dispatchEvent(new Event('change'));
        }
    });
    objSel1.value = 'bar';
})();
// 1
select.form;

// 2
select.selectedIndex;

// 3
select.options[2].value
  // const selector = document.getElementById('#selector')
  // 1 获取 selector 的 form 父元素
  selector.form
  // 2 获取 selector 选中的 selectedIndex
  selector.selectedIndex
  // 3 获取 selector 第 3 个 options 元素的 value 属性值
  selector.options[2].value
  // 4 设置 value ,触发 onchange 事件
  Object.defineProperty(selector, 'value', {
    get: function () {
      // selectedIndex 为正说明选择有结果,否则就无结果
      return this.selectedIndex >= 0 ? this.options[this.selectedIndex].value : ''
    },
    set: function (value) {
      let options = Array.from(selector.options)
      let optionsValue = options.map(option => option.value)
      // 找到目标 index, 更改 index 为目标 index
      this.selectedIndex = optionsValue.indexOf(value)
      // 触发事件
      let event = new InputEvent('change')
      this.dispatchEvent(event)
    },
  })
  selector.onchange = (e) => {
    console.log(e.target.value)
  }
  selector.value = 'b'
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>DOM49</title>
</head>
<body>
  <form action="GET">
    <select name="selector" id="selector">
      <option value="a">a</option>
      <option value="b">b</option>
      <option value="c">c</option>
      <option value="d">d</option>
      <option value="e">e</option>
    </select>
  </form>
</body>
</html>
HMHJ commented
var select=document.getElementById('sel')
// 1
console.log(select.form)
//2
console.log(select.selectedIndex)
//3
console.log(select.options[2].value)
//4
Object.defineProperty(select, 'value', {
    set (val) {
        for (let i = 0; i < select.options.length; i++) {
            if (select.options[i].value === val && i !== select.selectedIndex) {
                select.selectedIndex = i
                select.dispatchEvent(new Event('change'))
                break
            }
        }
    },
    get () {
        return select.selectedIndex ? select.selectedIndex.value : '';
    }
})
var selectElm = document.querySelector('select');

第 1 题

selectElm.form

第 2 题

selectElm.selectedIndex
// 或者
selectElm.options.selectedIndex

第 3 题

selectElm.options[2].value

第 4 题

测试要点:

  1. 不能影响 select 原有功能、特性;
  2. 如 option 不设置 value 属性,仍可通过 option 的 text 来赋值;
  3. 赋一个无效值(option 里不存在的值),则 select 选中为空;
  4. 表单的原生重置按钮可还原 select 的值。
Object.defineProperty(HTMLSelectElement.prototype, 'value', {
  set(newVal) {
    const curOptIndex = [...this.options].findIndex(opt =>
      opt.value == newVal  // 使用弱类型判断,以涵盖 1 == '1' 类似情况
    );

    if (curOptIndex !== this.selectedIndex) {  // 非上次选中项
      this.selectedIndex = curOptIndex;
      this.dispatchEvent(new Event('change'));  // 派发事件
    }
  }
});
// 兼容 IE9+
Object.defineProperty(HTMLSelectElement.prototype, 'value', {
  set: function(newVal) {
    var curOptIndex = -1;

    [].slice.call(this.options).some(function(opt, idx) {
      return opt.value == newVal && ~(curOptIndex = idx)
    });

    if (curOptIndex !== this.selectedIndex) {
      var evt;
      if (typeof Event === 'function') {
        evt = new Event('change')
      } else {
        evt = document.createEvent('Event');
        evt.initEvent('change', true, true);
      }

      this.selectedIndex = curOptIndex;
      this.dispatchEvent(evt);
    }
  }
});