标识符
在程序的执行过程中,数据往往会不断发生变化,为了能够跟踪数据的变化,最简单的方法就是将数据赋给一个符号容器,这个容器就叫标识符。我们可以将标识符看成数据本身的替代品。
标识符分类
符合标识符定义标准的有:变量、函数名、函数参数、属性名和 this 。从广义上来说,函数名和函数参数都可以归类到变量里,因为这三种标识符的查询机制都属于" 静态作用域机制 “(1);this 的查询机制属于” 动态作用域机制 “(2),但 ES6 标准将 this 也归类到了” 静态作用域机制 “里;属性名和以上四种标识符查询机制则完全不同,属于” 原型查询机制 "(3)。
*(1) 定义位置决定值的内容;
*(2) 执行方式决定值的内容;
*(3) 如果在对象上没有找到需要的属性名,就查找整条原型链;
原型查询机制
作用域和 this 的绑定规则我们都已经讲过了,这部分内容主要用来讲解标识符中,有关于属性的查询机制,也就是" 原型查询机制 "。
对象
首先,原型查询机制是在查找属性值时才会运行的一种机制,在 JavaScript 中众多的数据类型中,只有对象才有属性,其属性值可以是数据,也可以是函数。
但对象是一个范围很大的概念,有很多人说 JavaScript 中万物皆是对象,但这里不应该包括基本值。虽然 typeof null 时会返回字符串 “object”,但 null 绝非一个对象,它只是一个基本值。其他基本类型,比如 String、Boolean、Number 、Symbol 和 Undefined 类型值,本身也并不是对象。但 String、Boolean 和 Number 这三个基本类型比较特殊,引擎在执行的时候,会自动为其创建对应的包装对象。
综合以上内容,我们常见的对象有:Object、Array、Data、RegExp、Error、Function、Boolean、Number、String 等类型。
如果用一种通俗的语言解释什么是对象,可以这样去理解,对象就是可以进行描述( 数据 )和拥有行为( 函数 )的最小单元,而基本值之所以不需要属性和行为,是因为它们本身的意义很明确,而且不可更改,自然也就不需要属性和行为。
原型与原型查询
在 JavaScript 中,我们创建的所有对象都会有一个特殊的 [[Prototype]] 内置属性(1)。[[Prototype]] 属性默认指向另一个对象,这个对象通常被称为原型。当你试图查找对象的属性时,第一步是检查实例对象(2)本身是否有这个属性,如果有的话就使用它;如果没有,就会继续查找实例对象的原型对象(2)。
*(1) 对象的 [[Prototype]] 属性属于内置属性,不能直接访问,虽然一些浏览器提供了 __proto__
属性接口,但这个属性并没有写在 ES 语言规范中,所以不建议使用。如果想要取得实例对象的原型对象,可以使用 Object.getPrototypeOf() 方法。
*(2) 为了方便区分,可以把你创建的对象称为实例对象,而实例对象的 [[Prototype]] 属性指向的对象称为原型对象。
例子:
var humen = {
name: "Tom"
}
var person = Object.create(humen);
console.log(person.name);
//输出:Tom
*person 本身并没有name 属性,这个属性存在于 person 的原型对象中。
默认原型对象
上面例子中,我们使用 Object.create() 方法,明确指定了实例对象 person 的原型对象。如果我们没有明确的标明实例对象的原型,在代码编译阶段,引擎也会为其指定一个默认的原型对象( Object.prototype 对象 )。例如 toString() 、valueOf() 和 hasOwnProperty() 等方法,都是绑定在 Object.prototype 对象上的方法,所有实例对象都可以拿来直接使用。
例子:
var obj = {
name: "Amy",
age: 33
}
console.log(obj.toString());
//输出:[object Object]
*obj 本身并没有 toString 属性,这个属性存在于默认的 Object.prototype 对象中。
可以使用 Object.create(null) 创建一个没有默认原型的实例对象。
例子:
var obj = Object.create(null);
console.log(obj.toString());
//输出:报错
实例对象与原型对象的相对性
我们一般会使用 hasOwnProperty() 方法来判断属性是绑定在实例对象中还是原型对象中,如果属性是直接绑定在实例对象中的,则返回 true ;否则,返回 false 。
例子:
var humen = {
name: "Tom"
}
var person = Object.create(humen);
person.age = 20;
console.log(person.hasOwnProperty("name")); //输出:false
console.log(person.hasOwnProperty("age")); //输出:true
一个对象,它是实例对象还是原型对象,不是绝对的:它可能是其他对象的原型对象,而它本身也可以有自己的原型对象,因此它既扮演着实例对象,又扮演着原型对象。
例子:
var humen = {
name: "Tom"
}
var person = Object.create(humen);
person.age = 20;
//相对于 person 而言,humen 属于原型对象
// age 绑定在实例对象 person 中,name 绑定在原型对象 humen 中
console.log(person.hasOwnProperty("age")); //输出:true
console.log(person.hasOwnProperty("name")); //输出:false
//相对于 humen 的默认原型对象 Object.prototype 而言,humen 属于实例对象
// name 绑定在实例对象 humen 中,toString 绑定在原型对象 Object.prototype 中
console.log(humen.hasOwnProperty("name")); //输出:true
console.log(humen.hasOwnProperty("toString")); //输出:false
//Object.getPrototypeOf() 方法用于取得实例对象的原型对象
console.log(Object.getPrototypeOf(person) === humen); //输出:true
console.log(Object.getPrototypeOf(person).hasOwnProperty("name")); //输出:true
// Object.prototype 没有原型对象,因为它已经是原型链的顶端
// toString 绑定在实例对象 Object.prototype 中
console.log(Object.prototype.hasOwnProperty("toString")); //输出:true
*本质上,对象只是对象,并没有" 原型 “和” 实例 "之分,因此在使用 hasOwnProperty() 方法时,只要属性是直接绑定在当前对象中的,则返回 true,如果是查询原型对象而来的,则返回 false。
函数的原型
在 JavaScript 中,所有函数默认都会拥有一个名为 prototype 的属性,它会指向一个对象,这个对象被称为函数的原型对象。
上面提到过,对象的原型对象 [[Prototype]] 不能直接访问,但函数是一个比较特殊的对象,它是可以直接访问原型对象的。
这样的设计,主要原因是函数在 JavaScript 中具有两种截然不同的身份:第一种,作为普通函数去调用,会直接执行函数体中的代码;第二种,通过 new 关键字去调用,负责创建新对象并绑定 this 值。函数作为第二种身份使用时,新对象的原型对象会关联到函数的原型对象上,这一部分内容,后面我们会详细讲解。
例子:
function fn() {};
console.log(fn.prototype);
*函数的原型对象也是对象,因此在编译阶段,引擎会默认指定它的原型对象为 Object.prototype 对象。
函数的原型对象默认有一个 constructor 属性( 参考上面的截图 ),这个属性引用的是该对象关联的函数。constructor 属性值是可修改的。
例子:
function fn() {};
function fn2() {};
console.log(fn.prototype.constructor === fn); //输出:true
fn.prototype.constructor = fn2;
console.log(fn.prototype.constructor === fn); //输出:false
原型链
如果在实例对象上没有找到需要的属性或者方法,引擎就会继续在实例对象的原型对象上进行查找,同理,如果在后者中也没有找到需要的属性或者方法,引擎就会继续查找它的原型对象,以此类推。这一系列对象的链接被称为" 原型链 "。
例子:
var a = Object.create(null);
a.name = "Tom";
var b = Object.create(a);
b.age = 20;
var c = Object.create(b);
console.log(c.name); //输出:Tom
console.log(c.age); //输出:20
运行时流程图
结合以上内容,JavaScript 的运行时流程图如下:
这张图会根据内容的增加不断进行补充。
深入挖掘系列手记
浏览器理论(未更新)
如有错误,欢迎指正,本人不胜感激。