语句和对象
语句和对象
语句
普通语句
语句块
语句块可以把多行语句视为同一行语句。需要注意,语句块会产生作用域。
{
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 语句
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 | |
Obejct | 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.protoype 为原型创建一个新对象;
// 以新对象为 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
:跟原来的函数相关联。