JavaScript - 變數、值、型別

liusGG發表於2018-10-10

一、type of

返回值共有7種:undefined, object, boolean, number, string, symbol, function

typeof undefined === 'undefined';
typeof true === 'boolean';
typeof 123 === 'number';
typeof 'username' === 'string';
typeof {team : '75team'} === 'object'
typeof symbol() === 'symbol';
typeof null === 'object';
typeof function() {} === 'function';
複製程式碼

二、變數宣告方法

var - 宣告一個變數,可以將其初始化為一個值
const - 宣告一個塊級作用域變數,可以將其初始化一個值
let - 宣告一個只讀的常量

2.1 使用var的注意事項:

  1. var不支援塊級作用域
  2. var存在變數提升

舉例:

console.log(a === undefined);
var a = 10;

function foo(){
  console.log([a, i]);
  var a = 20;
  for(var i = 0; i < a; i++){
    //do sth.
  }
  console.log([a, i]);
}
foo();
複製程式碼
//---console:
true
[undefined, undefined]
[20, 20]
複製程式碼

由於變數宣告提升,這段程式碼相當於

var a;
console.log(a === undefined);
a = 10;

function foo(){
  var a, i;
  console.log([a, i]);
  a = 20;
  for(i = 0; i < a; i++){
    //do sth.
  }
  console.log([a, i]);
}
foo();
複製程式碼

2.2 使用let的注意事項

  1. 塊級作用域
  2. 同一個作用域下只能宣告一次
  3. 暫存死區(temporal dead zone)
  4. 迴圈中的let作用域
  5. 瀏覽器相容性

塊級作用域示例:

{
  let x = 10;

  console.log('x is ' + x);
}

console.log(typeof x);

let i = 20;

for(let i = 0; i < 10; i++){
  console.log(i);
}
console.log(i);

var y = 3;
var y = 4;

console.log(y);

// let z = 3;
// var z = 4;
// console.log(z);
複製程式碼
//---console:

"x is 10"
"undefined"
0
1
2
3
4
5
6
7
8
9
20
4
複製程式碼

暫存死區示例:

let x = 10;

function foo(){
  console.log(x);
  let x = 20;  //如果這一句改成 var 會怎樣?
  return x * x;
}

console.log(foo());
複製程式碼
//---console:
"ReferenceError: x is not defined"
複製程式碼

迴圈中的let作用域示例:

var buttons = document.querySelectorAll('button');

for(var i = 0; i < buttons.length; i++){
  buttons[i].onclick = evt => console.log('點選了第 ' + i + '個按鈕');
}
複製程式碼
//---console:
"點選了第 5個按鈕"
"點選了第 5個按鈕"
"點選了第 5個按鈕"
"點選了第 5個按鈕"
複製程式碼

這裡是因為click這個方法是非同步的,只有在點選之後,js才會去執行這段程式碼,這時for迴圈中的 i 已經迴圈完,所以會產生點選哪個按鈕都返回 5 。解決這類問題常利用‘閉包’,或者使用let

var buttons = document.querySelectorAll('button');

for(var i = 0; i < buttons.length; i++){
  (function (i) {
       buttons[i].onclick = evt => console.log('點選了第 ' + i + '個按鈕');
  })(i)
}
複製程式碼

2.2 使用const的注意事項

  1. const宣告的變數不可以再次被改變
  2. 其他使用與let一樣

示例:

const BUFFER_SIZE = 1024;

let buffer = new ArrayBuffer(BUFFER_SIZE);

console.log(buffer);

let data = new Uint16Array(buffer);
let data2 = new Uint8Array(buffer);

data[0] = 0xff06;

console.log(data2[0], data2[1]);
複製程式碼
//---console:
[object ArrayBuffer]
6
255
複製程式碼

注意:給變數賦值為物件時,物件引用不能變,但是物件裡的屬性值可以改變。

// A的地址沒有改變,這樣也是可以的。
const A = {a: 1};
A.a = 2;
console.log(A); 
// A: {a: 2}

// 如何不讓A被操作呢?
const A = Object.freeze({a: 1});
A.a = 2;
console.log(A); 
// A: {a: 1}
複製程式碼

三、嚴格模式-"use strict";

(function(){
  //'use strict';
  
  var x = y = 0;
  
  //do sth.
})();

console.log([typeof x, typeof y]);
複製程式碼
//---console:
["undefined", "number"]
複製程式碼

但是在ES6與‘use strict’某些時候會有衝突

(function f( j = 777 ) {
    'use strict';
    // do sth.
})


ERROR: Uncaught SyntaxError: Illegal 'use strict' directive in function with non-simple parameter list
複製程式碼

四、抽象相等

4.1嚴格相等於非嚴格相等

javascript

console.log([null !== undefined, null == undefined]); //true, true

console.log(['1' == 1, [] == 0, '' == 0, 0 == false, 1 == true]);

console.log([NaN != NaN]);
複製程式碼

Console

[true, true]
[true, true, true, true, true]
[true]
複製程式碼

五、Boolean 型別

  • true 和 false
  • 0、''、null、undefined 被隱式轉換為 false,其他轉為 true
  • 建議採用嚴格比較,可以通過 !! 將非 boolean 值轉為 boolean
  • 布林操作符 && 和 || 並不會轉換型別
  • && 和 || 的短路特性
  • 比較操作符總是返回 boolean 型別

5.1 Boolean型別的應用

javascript

var result = 1 && 2;

console.log([result, !!result]);

var list = [1,,2,,,4,5,0,9];

list = list.filter(item => item);

console.log(list);

function showTip(tip){
  tip = tip || 'This is a tip';
  console.log(tip);
  
  var label = document.createElement('label');
  label.className = 'tip';
  label.innerHTML = tip;
  
  document.body.appendChild(label);
}

document.querySelector('button').onclick 
  = showTip.bind(null, '');
複製程式碼

console

"This is a tip"
"This is a tip"
"This is a tip"
...
複製程式碼

示例2: JavaScript

/**
 * options -> type:x、y、xy or function
 */
function applyAnimate(el, duration, options, easing){
  var startTime = Date.now();
  if(typeof el === 'string'){
    el = document.querySelector(el);
  }
  duration = duration || 1000;
  options = options || {
    property: 'x',
    distance: 100
  };
  easing = easing || function(p){return p;};

  requestAnimationFrame(function update(){
    var now = Date.now();
    var p = (now - startTime)/duration;
    var ep = easing(p); 

    if(typeof options !== 'function'){
      var attr = options.property, distance = options.distance;
      var translate = [];
      if(attr.indexOf('x') >= 0){
        translate.push('translateX(' + distance * ep + 'px)');
      }
      if(attr.indexOf('y') >= 0){
        translate.push('translateY(' + distance * ep + 'px)');
      }
      el.style.transform = translate.join(' ');
    }else{
      options(el, ep, p);
    }
    
    if(p <= 1){
      requestAnimationFrame(update);
    }
  });
}

document.querySelector('.ball').onclick = function(){

  applyAnimate('.ball');

  /*applyAnimate('.ball', 1000, function(el, ep, p){
  const distance = 100, pi2 = 2 * Math.PI;
  el.style.transform = 'translate(' + distance * Math.cos(pi2 * ep) 
    + 'px,' + distance * Math.sin(pi2 * ep)  + 'px)';
});*/
}
複製程式碼

六、Number

  • 整數 -2^53 ~ 2^53
  • 小數精度 Number.EPSILON
  • Infinity、Number.MAX_VALUE、Number.MIN_VALUE
  • 浮點數有精度問題
  • 2進位制,8進位制,十六進位制
  • +0和-0

6.1 Number型別的應用

javascript

//-----------

console.log(0.2 + 0.4); //WTF!! IEEE754

console.log(((0.2 + 0.4) * 100).toPrecision(2));

console.log(((0.2 + 0.4) * 100).toFixed(2));

//----------

const ball = document.getElementById('ball');

var distance = 100, duration = 2000;

ball.onclick = function(){
  var startTime = Date.now();
  requestAnimationFrame(function update(){
    var p = (Date.now() - startTime) / duration;
  
    ball.style.transform = 'translateX(' + distance * p + 'px)'
    console.log(p,distance);
    if(p <= 1){ //不應當用相等比較浮點數!!
      requestAnimationFrame(update);
    }
  });
}
複製程式碼

console

0.6000000000000001
"60"
"60.00"
複製程式碼

示例2: JavaScript

console.log([Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER]);

var bigInteger = Number.MAX_SAFE_INTEGER + 1;

console.log([bigInteger, bigInteger + 1, bigInteger + 2]); //WTF!!

console.log(1234567890000000000000000000); //科學計數法

console.log(Math.pow(2, 53) === bigInteger);

console.log([Number.isSafeInteger(bigInteger), 
             Number.isSafeInteger(bigInteger - 1)]);

//-----------

console.log([Number.MAX_VALUE, Number.MIN_VALUE]);

console.log([Number.MAX_VALUE + 1, Number.MAX_VALUE * 2]);

console.log([Number.EPSILON]);

console.log(0.99 - 1e-17 === 0.99);

console.log(0.99 - Number.EPSILON === 0.99);
複製程式碼

console

[9007199254740991, -9007199254740991]
[9007199254740992, 9007199254740992, 9007199254740994]
1.23456789e+27
true
[false, true]
[1.7976931348623157e+308, 5e-324]
[1.7976931348623157e+308, Infinity]
[2.220446049250313e-16]
true
false
複製程式碼

七、String

  • 引號規範
  • 轉義字元
  • 字串與字元
  • 字串與型別轉換
  • 常用字串操作
  • 模板字串

7.1 引號規範與轉義

javascript

var text = 'This is a text.'; //規範建議預設使用雙引號
var html = '<p class="sth">This is a <em>paragraph</em></p>';

console.log(text);
console.log(html);

var text2 = '我所做的餡餅\n是全天下\n最好吃的';

console.log(text2);

var text3 = 'if(a){\n\tconsole.log(b);\n}';

console.log(text3);

var text4 = '\u5947\u821e\u56e2';

console.log(text4);
複製程式碼

Console

"This is a text."
"<p class=\"sth\">This is a <em>paragraph</em></p>"
"我所做的餡餅
是全天下
最好吃的"
"if(a){
  console.log(b);
}"
"奇舞團"
複製程式碼

7.2 處理字元

JavaScript

var str = 'my string';

console.log(str.charAt(5));

var charArray = Array.from(str); //str.split('');

console.log(charArray);

console.log(str.charCodeAt(5), str.charCodeAt(6));

console.log(String.fromCharCode(114, 105));

console.log(String.fromCharCode(...charArray.map(c=>c.charCodeAt(0))));
複製程式碼

Console

"r"
["m", "y", " ", "s", "t", "r", "i", "n", "g"]
114
105
"ri"
"my string"
複製程式碼

7.3 字串型別轉換

7.3.1 隱式型別轉換

console.log([1+2, '1'+2, '1'-2]); 
// console: [3, "12", -1]
// 註釋:減法不會發生隱式型別轉換,可以用減零的方式將字串轉化成數字,如:console.log('1'- 0 + 2);
複製程式碼
console.log(parseInt('100abc', 2), Number('0b100')); 
// console: 4 4 
// 轉整數
複製程式碼
console.log(parseFloat('12.3e10xx')); 
// console: 123000000000
// 轉浮點數, parseFloat會盡可能的將字串中的值轉化成浮點數,然後將轉化不了的字串忽略,這裡是將“xx”省略。
複製程式碼
// 封裝物件的 toString 方法
var foo = {
  toString(){
   return 'foo'; 
  }
};

console.log(foo + ' bar');
// console: "foo bar"
複製程式碼

7.5 常用字串操作

javascript

const a = 'hello';
const b = 'WORLD';
const c = '!';

console.log(a + ' ' + b + c); //字串連線
// 註釋:字串可以用“+”相加,但在某些瀏覽器內效能不好,可採用陣列的join方法拼接字串。
// console.log([a, b, c].join(''));

console.log(a.toUpperCase() + ' ' + b.toLowerCase() + c); //轉大小寫

console.log(a.split('').reverse().join('')); //逆序字串

console.log([a.slice(2,3), a.substr(2,3)]); //擷取子串
// 註釋: slice和substr的引數含義不一樣

const d = a + ' ' + b + c;

console.log(d.indexOf(b)); //字串查詢

console.log(d.replace(a, a.toUpperCase()));
複製程式碼

Console

"hello WORLD!"
"HELLO world!"
"olleh"
["l", "llo"]
6
"HELLO WORLD!"
複製程式碼

7.6 模板字串(ES6)

const tpl1 = `我所做的餡餅
  是全天下
  最好吃的`;

console.log([tpl1, typeof tpl1]);

{
  let who = '月影', what = '月餅';
  
  const tpl2 = `${who}所做的${what}
    是全天下
    最好吃的`;

  console.log(tpl2);
}
複製程式碼
["我所做的餡餅
  是全天下
  最好吃的", "string"]
"月影所做的月餅
    是全天下
    最好吃的"
複製程式碼

7.7 模板字串高階用法

JavaScript


let output = (a, b) => `${a} + ${b} is ${a + b}`;

console.log(output(3, 5));

let formatedList = (data) => `
  <ul>
     ${data.map(item=>`
       <li><span>姓名:${item.name}</span><span>年齡:${item.age}</span></li>
     `).join('')}
  </ul>
`;

let data = [
  {name: 'Akira', age: 35},
  {name: 'Jane',  age: 26},
  {name: 'Jhon',  age: 54}
];

console.log(formatedList(data));
複製程式碼

Console

"3 + 5 is 8"
"
  <ul>
     
       <li><span>姓名:Akira</span><span>年齡:35</span></li>
     
       <li><span>姓名:Jane</span><span>年齡:26</span></li>
     
       <li><span>姓名:Jhon</span><span>年齡:54</span></li>
     
  </ul>
"
複製程式碼

八、Object

  • 物件的屬性
  • 值和引用
  • 型別與構造器
  • 內建型別
  • 物件的高階屬性
  • class

8.1 物件的屬性

  • 屬性名規則:可以是有效字串或者任意可轉換為有效字串的型別​​​​​​​
  • 屬性的訪問和遍歷​​​​​​​

8.2 物件建立

//建立物件
{
  let myObj = new Object();

  myObj.name = 'akira';
  myObj.birthday = '12-29';

  console.log(myObj);
}

//然而上面這麼寫的是菜鳥,普通青年這麼寫
{
  let myObj = {
    name: "akira",
    birthday: "12-29"
  };
  
  console.log(myObj);
}

//有些二逼青年:
{
  let myObj = Object.create({name: "akira", birthday: "21-29"});
  
  console.log(myObj);
}
複製程式碼

8.3 屬性訪問

javascript

// 物件屬性是有效字串,屬性訪問可以通過.和[]
{
  let myObj = {
    name: "akira",
    birthday: "12-29"
  };

  console.log([myObj.name, myObj['birthday']]);
}

{
  // []屬性訪問的好處是可以計算
  const conf = {
    adapter: 'sqlite',
    db: {
      sqlite: {
        //...
      },
      mysql: {
        //...
      }
    }
  }
  let dbSetting = conf.db[conf.adapter];
}

{
  // 在 ES6 中字面量的 key 也支援屬性計算
  // 比如可以給物件定義一個變數key
  let process = {env: {}};

  const ENV = process.env.JSBIN_ENV || 'development';
  const conf = {
    [ENV]: true
  };

  console.log([conf.development, conf.production]);


  //ES5中只能這樣實現:
  var ENV = process.env.JSBIN_ENV || 'development';
  var conf = {};
  conf[ENV] = true; 
  console.log([conf.development, conf.production]);
}
複製程式碼

Console

["akira", "12-29"]
[true, undefined]
複製程式碼

8.4 屬性訪問

javscript

let point = {
  x : 100,
  y : 100,
  getLength : function(){
    let {x, y} = this;
    return Math.sqrt(x * x + y * y);
  }
}

console.log(point.getLength());

//用 for...in 遍歷
for(let key in point){
  console.log([key, point[key]]);
}

//用 Object.keys 遍歷
Object.keys(point).forEach((key) => console.log([key, point[key]]));
複製程式碼

Console

141.4213562373095
["x", 100]
["y", 100]
["getLength", function(){
  let {x, y} = this;
  return Math.sqrt(x * x + y * y);
}]
["x", 100]
["y", 100]
["getLength", function(){
  let {x, y} = this;
  return Math.sqrt(x * x + y * y);
}]
複製程式碼

8.5 值和引用

  • 值型別與引用型別
  • 基本型別對應的包裝型別
  • 物件的拷貝
let x = 20, y = 30;

function foo(a, b){
  a++;
  b++;
  console.log([a, b]);
}

foo(x, y);

console.log([x, y]);
// 注意:Number是值型別,當把x,y作為引數傳遞給function的時候,只是傳遞了x,y的副本,所以執行完foo(x,y), x,y的值並沒有變化。

const obj = {x: 20, y: 30};

function foo2(obj){
  obj.x++;
  obj.y++;
  console.log(obj);
}

foo2(obj);

console.log(obj);
// 注意:Object是引用型別,它是將obj裡的引用自加,所以函式執行完,obj裡的值會發生變化。
複製程式碼

Console

[21, 31]
[20, 30]
[object Object] {
  x: 21,
  y: 31
}
[object Object] {
  x: 21,
  y: 31
}
複製程式碼

九、基本型別的包裝類

var str = "Hello World";
var strObj = new String(str);
// 註釋:如果寫new是包裝型別,如果不寫new是值型別

console.log([str, strObj]); 
// console: ["Hello World", "Hello World"]

console.log([typeof str, typeof strObj]);
// console: ["string", "object"]

console.log([str instanceof String, strObj instanceof String]);
// console: [false, true]

var n = new Number(10);
console.log([typeof n, typeof ++n]);
// console: ["object", "number"]

console.log(Object.prototype.toString.call(10));
// console: [object Number]

複製程式碼

十、物件的拷貝

let conf = {
  adapter: 'sqlite',
  db: {
    sqlite: {
      name: 'xxx.sqlite'
    },
    mysql: {
      name: 'xxx',
      username: 'work',
      passwd: '******'
    }
  }
};

//直接引用
let conf2 = conf;
conf2.adapter = 'mysql';

console.log(conf.adapter);
// console: "mysql"

//ES5 淺拷貝
conf.adapter = 'sqlite';
let copied = Object.assign({}, conf);
copied.adapter = 'mysql';

console.log(conf.adapter);
// console: "sqlite"
console.log(copied.adapter);
// console: "mysql"
copied.db.sqlite.name = 'yyy.sqlite';
console.log(conf.db.sqlite.name);
// console: "yyy.sqlite"

//深拷貝
function deepCopy(des, src){
  for(var key in src){
    let prop = src[key];
    if(typeof prop === 'object'){
      des[key] = des[key] || {};
      deepCopy(des[key], prop);
    }else{
      des[key] = src[key];
    }
  }
  return des;
}

let deepCopied = deepCopy({}, conf);
deepCopied.db.sqlite.name = 'zzz.sqlite';

console.log([deepCopied.db.sqlite.name, conf.db.sqlite.name]);
// console: ["zzz.sqlite", "yyy.sqlite"]
複製程式碼

十一、物件的型別和構造器

  • new 和 constructor
  • prototype
  • instanceOf
  • ES6 class
//假設 JS 沒有 "new" 操作符,我們該如何實現建立物件?
function defineClass(initializer, proto){  
  return function f(...args){
    let obj = Object.create(proto);
    //f.prototype = proto; //just let instanceof make sense    
    obj.constructor = initializer;
    obj.constructor(...args);
    return obj;
  }
}

var Point = defineClass(function(x, y){
  this.x = x;
  this.y = y;
}, {
  getLength: function(){
    let {x, y} = this;
    return Math.sqrt(x * x + y * y);
  }
});

var p = Point(3, 4);
console.log([p.getLength(), p instanceof Point, p instanceof Object]);

//所以 'new' 其實是一種語法糖
複製程式碼

十二、原型鏈

// __proto__ 暴力構建原型鏈
var a = {x : 1}, 
    b = {y : 2}, 
    c = {z : 3};
    
b.__proto__ = a;
c.__proto__ = b;

console.log(c);

/**
 * console:
 * [object Object] {
 *   x: 1,
 *   y: 2,
 *   z: 3
 * }
 */
複製程式碼
//使用 Object.create 構建原型鏈
var a = {x : 1};
var b = Object.create(a); // b繼承物件a
b.y = 2;
var c = Object.create(b); // c繼承物件b
c.z = 3;

console.log(c);
/**
 * console:
 * [object Object] {
 *   x: 1,
 *   y: 2,
 *   z: 3
 * }
 */
複製程式碼
//使用構造器方式
function A() {
  this.x = 1;
}

function B() {
  this.y = 2;
}
B.prototype = new A();

function C() {
  this.z = 3;
}
C.prototype = new B();

var c = new C();
console.log(c);
/**
 * console:
 * [object Object] {
 *   x: 1,
 *   y: 2,
 *   z: 3
 * }
*/
複製程式碼

img

12.1 原型鏈有什麼弊端?

設計原型鏈是為了程式碼複用,但是原型鏈有額外構造器呼叫的問題。

12.2 問題:額外的構造器呼叫?

javascript

//原型繼承
/**
 * abstract point
 */
function Point(components){
  console.log('Point constructor called');
  this.components = components;
}

Point.prototype = {
  getDimension: function(){
    return this.components.length;
  },
  getLength: function(){
    var sum = 0, components = this.components;
    for(var i = 0; i < components.length; i++){
      sum += Math.pow(components[i], 2);
    }
    return Math.sqrt(sum);
  }
};

function Point2D(x, y){
  Point.call(this, [x, y]);
}

Point2D.prototype = new Point();

Point2D.prototype.getXY = function(){
  var components = this.components;
  return {
    x: components[0],
    y: components[1]
  };
}

複製程式碼

Console

"Point constructor called"
"Point constructor called"
["(3,4)", 5, true]
// 注意:這裡呼叫了兩次構造器
複製程式碼

解決方法 1:通用化父類構造器

//原型繼承
/**
 * abstract point
 */
function Point(dimension){
  console.log('Point constructor called');
  this.dimension = dimension;
}

Point.prototype = {
  setComponents: function(){
    components = [].slice.call(arguments);
    if(this.dimension !== components.length){
      throw new Error('Dimension not match!');
    }
    this.components = components;
  },
  getDimension: function(){
    return this.dimension;
  },
  getLength: function(){
    var sum = 0, components = this.components;
    for(var i = 0; i < components.length; i++){
      sum += Math.pow(components[i], 2);
    }
    return Math.sqrt(sum);
  }
};

function Point2D(x, y){
  this.setComponents(x, y);
}

Point2D.prototype = new Point(2);

Point2D.prototype.getXY = function(){
  var components = this.components;
  return {
    x: components[0],
    y: components[1]
  };
}

Point2D.prototype.toString = function(){
  return '(' + this.components + ')';
}

var p = new Point2D(3, 4);

console.log([p+'', p.getLength(), p instanceof Point]);
複製程式碼

Console

"Point constructor called"
["(3,4)", 5, true]
複製程式碼

註釋:但這並不總是好的,尤其是在多重繼承的時候

解決方法 2:延遲父類構造器呼叫

//原型繼承
/**
 * abstract point
 */
function Point(components){
  console.log('Point constructor called');
  this.components = components;
}

Point.prototype = {
  getDimension: function(){
    return this.components.length;
  },
  getLength: function(){
    var sum = 0, components = this.components;
    for(var i = 0; i < components.length; i++){
      sum += Math.pow(components[i], 2);
    }
    return Math.sqrt(sum);
  }
};

function Point2D(x, y){
  Point.call(this, [x, y]);
}

Point2D.prototype = Object.create(Point.prototype);
//function PointT(){};
//PointT.prototype = Point.prototype;
//Point2D.prototype = new PointT();

Point2D.prototype.getXY = function(){
  var components = this.components;
  return {
    x: components[0],
    y: components[1]
  };
}

Point2D.prototype.toString = function(){
  return '(' + this.components + ')';
}

var p = new Point2D(3, 4);

console.log([p+'', p.getLength(), p instanceof Point]);
複製程式碼

Console

"Point constructor called"
["(3,4)", 5, true]
複製程式碼

十三、javascript內建型別

  • Object
  • Function
  • Array
  • Date
  • Regex
  • Error
  • Math
  • ArrayBuffer
  • Promise
  • DataView
  • Map
  • Set
  • TypedArray // Uint16Array(buffer) Uint8Array(buffer)
  • Proxy(ES6)// 處理非同步

十四、為什麼不建議修改 Object.prototype 和 Array.prototype

14.1 在陣列上新增方法的問題

Array.prototype.remove = function(item) {
  var idx = this.indexOf(item);
  if(idx >= 0){
    return this.splice(idx, 1)[0];
  }
  return null;
}

var arr = [1, 2, 3];
arr.remove(2);  //perfect??
console.log(arr);

for(var i in arr){
  if(!Number.isNaN(i-0)){
    console.log(i + ':' + arr[i]);
  }else{
    console.log(i + '是什麼鬼?');
  }
}

/**
 * [1, 3]
 * "0:1"
 * "1:3"
 * "remove是什麼鬼?"
 */
 
 // 註釋:利用for in遍歷陣列或物件的好處是可以把值為undefined的過濾掉,它會把陣列上可列舉的屬性都遍歷出來。
複製程式碼

解決這一問題的方法

  • 使用 ES5 新特性 defineProperty
  • 規定禁止使用 for...in 運算元組,只用 for 或 Array.prototype.forEach
  • 規定禁止往陣列上隨便新增方法

示例:

Object.defineProperty(Array.prototype, 'remove', {
  value : function(item) {
    var idx = this.indexOf(item);
    if(idx >= 0) {
      return this.splice(idx, 1); 
    }
    return null;  
  },
  // enumerable: true 
  // 當且僅當該屬性的enumerable為true時,該屬性才能夠出現在物件的列舉屬性中。預設為 false。
});

var arr = [1, 2, 3];
arr.remove(2);
console.log(arr);

for(var i in arr) {
  if(!Number.isNaN(i-0)) {
    console.log(i + ':' + arr[i]);
  }else{
    console.log(i + '是什麼鬼?');
  }
}

/** 
 * console:
 * [1, 3]
 * "0:1"
 * "1:3"
 */
複製程式碼

14.2 getter / setter

function Point2D(x, y){
  this.x = x;
  this.y = y;
}

Object.defineProperty(Point2D.prototype, 'length', {
  get: function() {
    let {x, y} = this;
    return Math.sqrt(x * x + y * y);
  },
  set: function(len) {
    let arc = Math.atan2(this.y, this.x);
    this.x = len * Math.cos(arc);
    this.y = len * Math.sin(arc);
  }
});

Object.defineProperty(Point2D.prototype, 'arc', {
  get: function(){
    let {x, y} = this;
    return 180 * Math.atan2(y, x) / Math.PI;
  },
  set: function(arc){
    arc = Math.PI * arc / 180;
    let len = this.length;
    this.x = len * Math.cos(arc);
    this.y = len * Math.sin(arc);
  }
});

let p = new Point2D(1, 1);

console.log([p.length, p.arc]);
// [1.4142135623730951, 45]

p.length *= 2;

console.log([p.x, p.y]);
// [2.0000000000000004, 2]

p.arc = 90;
console.log([p.x, p.y]);
// [1.7319121124709868e-16, 2.8284271247461903]
複製程式碼

14.3 雙向繫結

const view = {
  nameEl: document.getElementById('name'),
  ageEl: document.getElementById('age'),
  submitBtn: document.getElementById('confirm')
}

view.submitBtn.addEventListener('click', function(evt){
  console.log('你要提交的資料是:' + [user.name, user.age]);
  evt.preventDefault();
});

function User(name, age){
  this.name = name;
  this.age = age;
}

User.prototype.bind = function(view){
  view.nameEl.addEventListener('change', evt => {
     this.name = evt.target.value;
  });
  view.ageEl.addEventListener('change', evt => {
     this.age = evt.target.value;
  });
}

Object.defineProperty(User.prototype, 'name', {
  set: function(name){
    view.nameEl.value = name;
  },
  get: function(){
    return view.nameEl.value;
  }
});

Object.defineProperty(User.prototype, 'age', {
  set: function(name){
    var ageOptions = Array.from(view.ageEl.options)
              .map(item => item.innerHTML);
    if(ageOptions.indexOf(name) === '-1'){
      throw new Error('無效的年齡格式');
    }
    view.ageEl.value = name;
  },
  get: function(){
    return view.ageEl.value;
  }
});

var user = new User('akira', '80後');
user.bind(view);
複製程式碼

演示 demo

歡迎關注

相關文章