语句和对象
语句和对象
语句
普通语句
语句块
语句块可以把多行语句视为同一行语句。需要注意,语句块会产生作用域。
{
  var x, y;
  x = 10;
  y = 20;
}
{
  let x = 1;
}
console.log(x); // 报错:let声明,仅仅对语句块作用域生效空语句
一个独立的分号。
;表达式语句
if 语句
if(a < 10) {
  //...
} else if(a < 20) {
  //...
} else if(a < 30) {
  //...
} else {
  //...
}switch 语句
switch(num) {
  case 1:
    print 1;
    break;
  case 2:
    print 2;
    break;
  case 3:
    print 3;
    break;
}循环语句
for 循环
for (i = 0; i < 100; i++) { console.log(i); } for (var i = 0; i < 100; i++) { console.log(i); } for (let i = 0; i < 100; i++) { console.log(i); } var j = 0; for (const i = 0; j < 100; j++) { console.log(i); }for in 循环
枚举对象的属性,体现了属性的 enumerable 特征。
let o = { a: 10, b: 20}; Object.defineProperty(o, "c", {enumerable: false, value: 30}); // 如果属性 c 的 enumerable为 true 时,则在 for in循环也可进行枚举 for (let p in o) { console.log(p); }for of 循环与 for await of 循环
其背后的机制是 iterator 机制。
// 添加 iterator let o = { [Symbol.iterator]:() => ({ _value: 0, next(){ if(this._value == 10) { return { done: true }; } else { return { value: this._value++, done: false }; } } }) } for(let e of o) { console.log(e); } // 在实际操作中,一般不需要定义iterator,可以使用generator function。 function* foo(){ yield 0; yield 1; yield 2; yield 3; } for (let e of foo()) { console.log(e); } // 一个异步生成器函数,异步生成器函数每隔一秒生成一个数字 // 使用 for await of 来访问这个异步生成器函数的结果 function sleep(duration) { return new Promise(function(resolve, reject) { setTimeout(resolve,duration); }) } async function* foo(){ i = 0; while(true) { await sleep(1000); yield i++; } } for await(let e of foo()) { console.log(e); }while 循环与 do while 循环
let a = 100 while(a--) { console.log("*"); } // do while循环无论如何至少会执行一次 let b = 101; do { console.log(b); } while(b < 100)
return 语句
用于函数中,它终止函数的执行,并且指定函数的返回值。
function squre(x){
  return x * x;
}break 语句和 continue 语句
- break 语句:用于跳出循环语句或者switch语句
 - continue 语句:用于结束本次循环并继续循环
 
注意:break 语句和 continue 语句都有带标签的用法。
// 带标签的break和continue可以控制自己被外层的哪个语句结构消费
// 可以跳出复杂的语句结构。
outer:for(let i = 0; i < 100; i++)
  inner:for(let j = 0; j < 100; j++)
    if( i == 50 && j == 50)
      break outer;
outer:for(let i = 0; i < 100; i++)
  inner:for(let j = 0; j < 100; j++)
    if( i >= 50 && j == 50)
      continue outer;with 语句
把对象的属性在它内部的作用域内变成变量。但它把 JS 的变量引用关系变得不可分析
let o = {a:1, b:2}
with(o){
  console.log(a, b);
}try 语句和 throw 语句
try 语句和 throw 语句用于处理异常。
try 语句用于捕获异常,用 throw 抛出的异常,可以在 try 语句的结构中被处理掉:
try部分:用于标识捕获异常的代码段catch部分:用于捕获异常后做一些处理finally部分:用于执行后做一些必须执行的清理工作
catch 结构会创建一个局部的作用域,并且把一个变量写入其中。注意,在这个作用域,不能再声明变量 e 了,否则会出错。
debugger 语句
通知调试器在此断点。在没有调试器挂载时,它不产生任何效果。
声明型语句
var 语句
var 声明永远作用于脚本、模块和函数体级别,在预处理阶段,不关心赋值的部分,只管在当前作用域声明这个变量。
var a = 1;
function foo() {
  console.log(a);
  var a = 2;
}
foo(); // undefined
var a = 1;
function foo() {
  console.log(a);
  if (false) {
    var a = 2;
  }
}
foo(); // undefined
var a = 1;
function foo() {
  var o= { a: 3 }
  with (o) {
    var a = 2;
  }
  console.log(o.a);
  console.log(a);
}
foo();
// 2
// undefinedlet 语句和 const 语句
let 和 const 的作用范围是if、for等结构型语句。在重复声明时会抛错。
const a = 2;
if (true) {
  const a = 1;
  console.log(a);
}
console.log(a);class 声明
class 声明作用于块级作用域,预处理阶段则会屏蔽外部变量。class 内部,可以使用 constructor 关键字来定义构造函数。还能定义 getter/setter 和方法。
class 默认内部的函数定义都是 strict 模式的。
class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
  // Getter
  get area() {
    return this.calcArea();
  }
  // Method
  calcArea() {
    return this.height * this.width;
  }
}函数声明
- 普通函数声明
 - async 函数声明
 - generator 函数声明
 - async generator 函数声明
 
function foo(){ }
// 带 * 的函数是 generator。
// 可以理解为返回一个序列的函数,它的底层是 iterator 机制。
function* foo(){
  yield 1;
  yield 2;
  yield 3;
}
// async 函数是可以暂停执行,等待异步操作的函数。
// 它的底层是 Promise 机制。
async function foo(){
  await sleep(3000);
}
async function* foo(){
  await sleep(3000);
  yield 1;
}
function foo(a = 1, ...other) {
  console.log(a, other)
}对象
对象的特征
- 对象具有唯一标识性:即使完全相同的两个对象,也并非同一个对象。
 - 对象具有状态:对象具有状态,同一对象可能处于不同状态之下。
 - 对象具有行为:即对象的状态,可能因为它的行为产生变迁。
 
在JavaScript中,对象唯一标识性都是用内存地址来体现的, 对象具有唯一标识的内存地址。对象的状态和行为其实都被抽象为了属性。
在JavaScript中,对象具有高度的动态性,这是因为JavaScript赋予了使用者在运行时为对象添改状态和行为的能力。
对象的两类属性
- 数据属性 
- value:就是属性的值。
 - writable:决定属性能否被赋值。
 - enumerable:决定for in能否枚举该属性。
 - configurable:决定该属性能否被删除或者改变特征值。
 
 - 访问器(getter/setter)属性 
- getter:函数 或 undefined,在取属性值时被调用。
 - setter:函数 或 undefined,在设置属性值时被调用。
 - enumerable:决定 for in 能否枚举该属性。
 - configurable:决定该属性能否被删除或者改变特征值。
 
 
var o = { a: 1 };
o.b = 2;
//a和b皆为数据属性
// Object.defineProperty()
// 直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象
Object.defineProperty(o, "b", {value: 2, writable: false, enumerable: false, configurable: true});
// Object.getOwnPropertyDescripter
// 返回指定对象上一个自有属性对应的属性描述符
Object.getOwnPropertyDescriptor(o, "a")
// {value: 1, writable: true, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptor(o, "b")
// {value: 2, writable: true, enumerable: true, configurable: true}
var o = {
  // get语法将对象属性绑定到查询该属性时将被调用的函数。
  get a() {
    return 1;
  }
};
console.log(o.a); // 1原型
ES6 以来,JavaScript 提供了一系列内置函数,可以直接访问操作原型:
Object.create根据指定的原型创建新对象,原型可以是 nullObject.getPrototypeOf获得一个对象的原型Object.setPrototypeOf设置一个对象的原型
var cat = {
  say(){
    console.log("meow~");
  },
  jump(){
    console.log("jump");
  }
}
var tiger = Object.create(cat,  {
  say:{
    writable:true,
    configurable:true,
    enumerable:true,
    value:function(){
      console.log("roar!");
    }
  }
})
var anotherCat = Object.create(cat);
anotherCat.say();
var anotherTiger = Object.create(tiger);
anotherTiger.say();类
class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    console.log(this.name + ' makes a noise.');
  }
}
class Dog extends Animal {
  constructor(name) {
    super(name); // call the super class constructor and pass in the name parameter
  }
  speak() {
    console.log(this.name + ' barks.');
  }
}
let d = new Dog('Mitzie');
d.speak(); // Mitzie barks.对象分类
- 宿主对象(host Objects):由JavaScript宿主环境提供的对象,它们的行为完全由宿主环境决定。
 - 内置对象(Built-in Objects):由JavaScript语言提供的对象。 
- 固有对象(Intrinsic Objects):由标准规定,随着JavaScript运行时创建而自动创建的对象实例。
 - 原生对象(Native Objects):可以由用户通过Array、RegExp等内置构造器或者特殊语法创建的对象。
 - 普通对象(Ordinary Objects):由 
{}语法、Object构造器或者class关键字定义类创建的对象,它能够被原型继承。 
 
宿主对象
在浏览器环境中,全局对象是 window 。window 上的属性,一部分来自JavaScript语言,一部分来自浏览器环境。
JavaScript标准中规定了全局对象属性,w3c的各种标准中规定了Window对象的其它属性。
内置对象·固有对象
固有对象是由标准规定,随着JavaScript运行时创建而自动创建的对象实例。固有对象在任何JS代码执行前就已经被创建出来了,它们通常扮演者类似基础库的角色。
ECMA标准为我们提供了一份固有对象表,里面含有150+个固有对象。
内置对象·原生对象
JavaScript中,能够通过语言本身的构造器创建的对象称作原生对象。
| 基本类型 | 基础功能和数据结构 | 错误类型 | 二进制操作 | 带类型的数组 | 
|---|---|---|---|---|
| Boolean | Array | Error | ArrayBuffer | Float32Array | 
| String | Date | EvalError | SharedArrayBuffer | Float64Array | 
| Number | RegExp | RangeError | DataView | Int8Array | 
| Symbol | Promise | ReferenceError | Int16Array | |
| Object | Proxy | SyntaxError | Int32Array | |
| Map | TypeError | UInt8Array | ||
| WeakMap | URIError | UInt16Array | ||
| Set | UInt32Array | |||
| WeakSet | UInt8ClampedArray | |||
| Function | 
通过这些构造器,可以用new运算创建新的对象,所以我们把这些对象称作原生对象。
构造器创建的对象多数使用了私有字段,这些字段使得原型继承方法无法正常工作。例如:
- Error: [[ErrorData]]
 - Boolean: [[BooleanData]]
 - Number: [[NumberData]]
 - Date: [[DateValue]]
 - RegExp: [[RegExpMatcher]]
 - Symbol: [[SymbolData]]
 - Map: [[MapData]]
 
用对象来模拟函数与构造器:函数对象与构造器对象
- 函数对象的定义是:具有 
[[call]]私有字段的对象 - 构造器对象的定义是:具有私有字段 
[[construct]]的对象 
任何宿主只要提供了“具有[[call]]私有字段的对象”,就可以被 JavaScript 函数调用语法支持。
- 任何对象只需要实现
[[call]],它就是一个函数对象,可以去作为函数被调用。 - 任何对象只需要实现
[[construct]],它就是一个构造器对象,可以作为构造器被调用。 
用 function 关键字创建的函数必定同时是函数和构造器。它们表现出来的行为效果却并不相同。
对于宿主和内置对象来说,它们实现 [[call]](作为函数被调用)和 [[construct]](作为构造器被调用)不总是一致的。
// 内置对象 `Date` 在作为构造器调用时产生新的对象,作为函数时,则产生字符串。
console.log(new Date); // 1
console.log(Date())
// 浏览器宿主环境中,提供的Image构造器,则根本不允许被作为函数调用。
console.log(new Image);
console.log(Image());//抛出错误箭头函数(=>)语法创建的函数仅仅是函数,它们无法被当作构造器使用。
使用 function 语法或者 Function 构造器创建的对象来说,[[call]] 和 [[construct]] 行为总是相似的,它们执行同一段代码。
function f(){
  return 1;
}
var v = f(); // 把 f 作为函数调用
var o = new f(); // 把 f 作为构造器调用
// [[construct]]执行过程如下:
// 以 Object.prototype 为原型创建一个新对象;
// 以新对象为 this,执行函数的[[call]];
// 如果[[call]]的返回值是对象,那么,返回这个对象,否则返回第一步创建的新对象。如果构造器返回了一个新的对象,new 创建的新对象就变成了一个构造函数之外完全无法访问的对象,这一定程度上可以实现“私有”。
function cls(){
  this.a = 100;
  return {
    getValue:() => this.a
  }
}
var o = new cls;
o.getValue(); // 100
// a 在外面永远无法访问到特殊行为的对象
常见的下标运算(使用中括号或者点来做属性访问)或者设置原型跟普通对象不同:
Array:Array的length属性根据最大的下标自动发生变化。Object.prototype:作为所有正常对象的默认原型,不能再给它设置原型了。String:为了支持下标运算,String的正整数属性访问会去字符串里查找。Arguments:arguments 的非负整数型下标属性跟对应的变量联动。- 模块的 
namespace对象:特殊的地方非常多,跟一般对象完全不一样,尽量只用于import。 - 类型数组和数组缓冲区:跟内存块相关联,下标运算比较特殊。
 bind后的function:跟原来的函数相关联。
