跳至主要內容

语句和对象

Mr.LRH大约 10 分钟

语句和对象

语句

普通语句

语句块

语句块可以把多行语句视为同一行语句。需要注意,语句块会产生作用域。

{
  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
// undefined

let 语句和 const 语句

letconst 的作用范围是iffor等结构型语句。在重复声明时会抛错。

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 根据指定的原型创建新对象,原型可以是 null
  • Object.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中,能够通过语言本身的构造器创建的对象称作原生对象。

基本类型基础功能和数据结构错误类型二进制操作带类型的数组
BooleanArrayErrorArrayBufferFloat32Array
StringDateEvalErrorSharedArrayBufferFloat64Array
NumberRegExpRangeErrorDataViewInt8Array
SymbolPromiseReferenceErrorInt16Array
ObejctProxySyntaxErrorInt32Array
MapTypeErrorUInt8Array
WeakMapURIErrorUInt16Array
SetUInt32Array
WeakSetUInt8ClampedArray
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.protoype 为原型创建一个新对象;
// 以新对象为 this,执行函数的[[call]];
// 如果[[call]]的返回值是对象,那么,返回这个对象,否则返回第一步创建的新对象。

如果构造器返回了一个新的对象,new 创建的新对象就变成了一个构造函数之外完全无法访问的对象,这一定程度上可以实现“私有”。

function cls(){
  this.a = 100;
  return {
    getValue:() => this.a
  }
}
var o = new cls;
o.getValue(); // 100
// a 在外面永远无法访问到

特殊行为的对象

常见的下标运算(使用中括号或者点来做属性访问)或者设置原型跟普通对象不同:

  • ArrayArraylength 属性根据最大的下标自动发生变化。
  • Object.prototype:作为所有正常对象的默认原型,不能再给它设置原型了。
  • String:为了支持下标运算,String 的正整数属性访问会去字符串里查找。
  • Arguments:arguments 的非负整数型下标属性跟对应的变量联动。
  • 模块的 namespace 对象:特殊的地方非常多,跟一般对象完全不一样,尽量只用于 import
  • 类型数组和数组缓冲区:跟内存块相关联,下标运算比较特殊。
  • bind 后的 function:跟原来的函数相关联。
上次编辑于:
贡献者: lingronghai