JavaScript - 变量、值、类型

1、type of

返回值共有7种:undefined, object, boolean, number, string, symbol, functionjavascript

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';

2、变量声明方法

var - 声明一个变量,能够将其初始化为一个值
const - 声明一个块级做用域变量,能够将其初始化一个值
let - 声明一个只读的常量html

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]

因为变量声明提高,这段代码至关于java

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. 浏览器兼容性

块级做用域示例:mysql

{
  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

暂存死区示例:sql

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

注意:给变量赋值为对象时,对象引用不能变,可是对象里的属性值能够改变。bash

// 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}

3、严格模式-"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、抽象相等

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]

5、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)';
});*/
}

6、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

7、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>
"

8、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
}

9、基本类型的包装类

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]

10、对象的拷贝

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"]

11、对象的类型和构造器

  • 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' 实际上是一种语法糖

12、原型链

// __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]

十3、javascript内置类型

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

十4、为何不建议修改 Object.prototype 和 Array.prototype

14.1 在数组上添加方法会被for...in枚举出来

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

欢迎关注

相关文章
相关标签/搜索