0%

学习笔记 2020 10 17

学习笔记 2020-10-17

JavaScript 高级程序设计(第4版) 阅读记录

对象、类与面向对象编程

ECMA-262 将对象定义为一组属性的无序集合,使用一些内部特性来描述属性的特征,这些特性由两个中括号括起来。

  1. 数据属性

    • [[Configurable]]

      表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特性,是否可以把它改为访问器属性。默认值为 true

    • [[Enumerable]]

      表示属性是否可以通过 for-in 循环返回。默认值为 true

    • [[Writeble]]

      表示属性的值是否可以被修改。默认值为 true

    • [[Value]]

      包含属性实际的值。默认值为 undefined

    修改属性的默认特征,使用 Object.defineProperty 方法。这个方法接收三个参数,要添加属性的对象、属性的名称和一个描述符对象。描述符对象的属性可以包括上述四个属性名:configurableenumerable 、· writablevalue 。调用时,如果不指定属性值,则都默认为 false

  2. 访问器属性

    访问器属性包含一个 getter 和一个 setter 函数。有 4 个特性描述它们的行为。

    • [[Configurable]]

      表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特性,是否可以把它改为数据属性。默认值为 true

    • [[Enumerable]]

      表示属性是否可以通过 for-in 循环返回。默认值为 true

    • [[Get]]

      获取函数,读取属性时调用,默认值为 undefined

    • [[Set]]

      设置函数,写入属性时调用,默认值为 undefined

可以同时定义多个属性,只需要使用 Object.defineProperties 方法,接收两个参数,添加或修改属性的对象和另一个描述符对象。

读取属性的特征

使用 Object.getOwnPropertyDescriptor() 方法可以取得指定属性的属性描述符。接收两个参数,属性所在的对象和要取得其描述符的属性名。

let book = {};
Object.defineProperties(book, {
  year_: {
    value: 2017
  },
  edition: {
    value: 1
  },
  year: {
    get: function () {
      return this.year_;
    },
    set: function (newValue) {
      if (newValue > 2017) {
        this.year_ = newValue;
        this.edition += newValue - 2017;
      }
    }
  }
});
let descriptor = Object.getOwnPropertyDescriptor(book, 'year_');
console.log(descriptor.value); // 2017
console.log(descriptor.configurable); // false
console.log(typeof descriptor.get); // "undefined"
let descriptor = Object.getOwnPropertyDescriptor(book, 'year');
console.log(descriptor.value); // undefined
console.log(descriptor.enumerable); // false
console.log(typeof descriptor.get); // "function"

ES8 新增了 Object.getOwnpropertyDescriptors() 静态方法。会在每个自有属性上调用 Object.getOwnpeopertyDescriptor() 并在一个新对象中返回它们。

let book = {};
Object.defineProperties(book, {
  year_: {
    value: 2017
  },
  edition: {
    value: 1
  },
  year: {
    get: function () {
      return this.year_;
    },
    set: function (newValue) {
      if (newValue > 2017) {
        this.year_ = newValue;
        this.edition += newValue - 2017;
      }
    }
  }
});
console.log(Object.getOwnPropertyDescriptors(book));
// {
// edition: {
// configurable: false,
// enumerable: false,
// value: 1,
// writable: false
// },
// year: {
// configurable: false,
// enumerable: false,
// get: f(),
// set: f(newValue),
// },
// year_: {
// configurable: false,
// enumerable: false,
// value: 2017,
// writable: false
// }
// }

合并对象

ES6 专门为合并对象提供了 Object.assign() 方法。接收一个目标对象和一个或多个源对象作为参数,将每个源对象中可枚举( Object.propertyIsEnumerable() 返回 true ) 和自有 ( Object.hasOwnProperty() 返回 true ) 属性复制到目标对象。以字符串和符号为键的属性会被复制。对每个符合条件的属性,会使用源对象上的 [[Get]] 取得属性的值,然后使用目标对象上的 [[Set]] 设置属性的值。

let dest, src, result;
/**
 * 简单复制
 */
dest = {};
src = { id: 'src' };
result = Object.assign(dest, src);
// Object.assign 修改目标对象
// 也会返回修改后的目标对象
console.log(dest === result); // true
console.log(dest !== src); // true
console.log(result); // { id: src }
console.log(dest); // { id: src }
dest.id = '123';
console.log(result); // {id: "123"}
/**
 * 多个源对象
 */
dest = {};
result = Object.assign(dest, { a: 'foo' }, { b: 'bar' });
console.log(result); // { a: foo, b: bar }
/**
 * 获取函数与设置函数
 */
dest = {
  set a(val) {
    console.log(`Invoked dest setter with param ${val}`);
  }
};
src = {
  get a() {
    console.log('Invoked src getter');
    return 'foo';
  }
};
Object.assign(dest, src);
// 调用 src 的获取方法
// 调用 dest 的设置方法并传入参数"foo"
// 因为这里的设置函数不执行赋值操作
// 所以实际上并没有把值转移过来
console.log(dest); // { set a(val) {...} }

复制过程执行的是浅复制。多个源对象若存在相同的属性,最后一个会覆盖前面的值。

对象标识及相等判定

ES6 新增了 Object.is() ,接收两个参数。

console.log(Object.is(true, 1)); // false
console.log(Object.is({}, {})); // false
console.log(Object.is('2', 2)); // false
// 正确的 0、-0、+0 相等/不等判定
console.log(Object.is(+0, -0)); // false
console.log(Object.is(+0, 0)); // true
console.log(Object.is(-0, 0)); // false
// 正确的 NaN 相等判定
console.log(Object.is(NaN, NaN)); // true
// 要检查超过两个值,递归地利用相等性传递即可:
function recursivelyCheckEqual(x, ...rest) {
  return Object.is(x, rest[0]) && (rest.length < 2 || recursivelyCheckEqual(...rest));
}

现代 JavaScript 教程

任务

不好的风格

下面的代码风格有什么问题?

function pow(x,n)
{
  let result=1;
  for(let i=0;i<n;i++) {result*=x;}
  return result;
}

let x=prompt("x?",''), n=prompt("n?",'')
if (n<=0)
{
  alert(`Power ${n} is not supported, please enter an integer number greater than zero`);
}
else
{
  alert(pow(x,n))
}

改为:

function pow(x, n) {
  let result = 1;

  for(let i = 0; i < n; i++) {
    result *= x;
  }

    return result;
}

let x = prompt("x?",''),
    n=prompt("n?",'');

if (n <= 0) {
  alert(`Power ${n} is not supported, please enter an integer number greater than zero`);
} else {
  alert(pow(x,n))
}

知识点记录

  1. vertical-align 用于行内块元素和内联元素对齐。

  2. table 单元格默认垂直居中。

  3. 垂直居中的一种思路

    <div class="container">
      <span>
        这里是多行文本这里是多行文本这里是多行文本这里是多行文本这里是多行文本这里是多行文本这里是多行文本这里是多行文本这里是多行文本这里是多行文本
      </span>
    </div>
    .container {
      display: table;
      border: 1px solid #000;
      width: 100px;
      height: 500px;
    }
    .container span {
      display: table-cell;
      vertical-align: middle;
    }
  4. 行内块元素排列时有 4px 的空格解决方案

    • 父级容器 font-size: 0 ,子级容器中若存在文本,需要重设字体大小,不规范。
    • 行内块元素设置 margin-left: -4px 或是其余数值,但文件压缩后会出现问题。因为空格来源于标签的换行或是空格符,压缩后这些东西不存在,左移的样式就会多余。