本文深入解析JavaScript中11个核心概念,涵盖原始值与引用值的存储差异、undefined与null的区别、伪数组的转换方法、原型链的工作原理以及对象创建的内部机制。通过具体代码示例,阐明构造函数返回值对对象创建的影响、instanceof运算符的原理以及Function与Object的特殊关系,帮助开发者理解JavaScript底层逻辑。
原始值与引用值的存储机制
1. 原始值与引用值的存储机制
在JavaScript中,原始值(Primitive Values)通常存储在栈内存(Stack)中,而引用值(Reference Values)则存储在堆内存(Heap)中。以下代码展示了这两种值的定义方式:
代码如下:
function Person(id,name,age){
this.id = id;
this.name = name;
this.age = age;
}
var num = 10;
var bol = true;
var str = "abc";
var obj = new Object();
var arr = ['a','b','c'];
var person = new Person(100,"笨蛋的座右铭",25);
undefined 与 null 的区别
2. undefined 与 null 的区别
undefined 表示变量已声明但未赋值,它是 Undefined 类型的唯一值。
null 表示对象引用为空,它是 Null 类型的唯一值。
通过 typeof 运算符可以观察到以下特性: typeof(undefined) === 'undefined'; typeof(null) === 'object'; undefined == null; // true undefined !== null; // false null instanceof Object; // false undefined instanceof Object; // false
尽管存在 Undefined 和 Null 类型,但它们是不可见的,开发者只能使用它们的值,无法直接访问类型本身。
尝试使用 instanceof 检查这些类型会返回 false: alert(undefined instanceof Undefined); // false alert(null instanceof Null); // false
伪数组的转换与处理
3. 伪数组(Array-like Objects)
伪数组具有以下特征: 1) 拥有 length 属性;
2) 可以通过索引顺序访问数据,类似于数组;
3) 不具备数组特有的操作方法,如 push、pop、slice 等。
可以通过 Array.prototype.slice 将伪数组转换为真正的数组:
var fakeArray = {0: 'a', 1: 'b', length: 2}; // 标准伪数组
var realArray = Array.prototype.slice.call(fakeArray);
JavaScript 中常见的伪数组包括 arguments、node.childNodes 以及 document.getElementsByTagName() 的返回值。
需要注意的是,在 IE 浏览器中,node.childNodes 无法通过 slice 方法进行转换。
jQuery 对象本身也是一个伪数组:
alert($('.class1').length); // 获取长度 alert($('.class1')[0].tagName); // 访问第一个元素的标签名
简单类型字面量的类型检测
4. 简单类型字面量的类型检测
var a = 1; var b = true; var c = "ccc";
字面量在 typeof 检测下显示出具体的类型。
使用 typeof 可以正确识别类型: alert(typeof a); // 'number' alert(typeof b); // 'boolean' alert(typeof c); // 'string'
然而,使用 instanceof 检测时,字面量并不属于对应的包装对象类型。
检测结果如下: alert(a instanceof Number); // false alert(a instanceof Object); // false alert(b instanceof Boolean); // false alert(b instanceof Object); // false alert(c instanceof String); // false alert(c instanceof Object); // false
原型链与对象属性访问
5. 函数的 prototype 属性与对象实例的内部 [[Prototype]]
每个函数(构造函数)都拥有一个 prototype 属性。每个对象实例都有一个内部的 [[Prototype]] 属性(在 Mozilla 浏览器中可通过 __proto__ 访问),该属性指向构造函数的 prototype 属性。prototype 本身也可以拥有自己的 prototype,从而形成原型链。Object 是原型链的顶端,因此所有原型链最终都会指向 Object.prototype。当访问对象实例的属性或方法时,JavaScript 引擎会先从对象自身开始搜索,如果未找到,则沿着原型链向上搜索,直到 Object.prototype 的 [[Prototype]] 为 null 为止。
构造函数的返回值机制
6. 构造函数的返回值秘密
代码如下:
var s = new function(){return "sss"};
alert(s); // '[object Object]'
s = new function(){return new String("sss")};
alert(s); // 'sss'关于这段代码的解释:
当 new 表达式后的构造函数返回一个引用类型(如数组、对象、函数等)时,new 创建的匿名对象将被覆盖,返回该引用对象。如果构造函数返回原始类型(或未显式 return,此时隐含返回 undefined),则返回 new 创建的匿名对象。
对象的创建过程详解
7. 对象的创建过程
代码如下:
function Person(name){
this.name = name;
}
Person.prototype = {
getName: function(){return this.name}
};
var p = new Person('zhangsan');解密 p 的创建过程:
1. 创建一个内置对象 obj 并进行初始化;
2. 将 p 的内部 [[Prototype]] 指向 Person.prototype;
3. 将 p 作为 this,使用 arguments 参数调用 Person 的内部 [[Call]] 方法,即执行 Person 函数体,并返回返回值。如果没有 return 语句,则返回 undefined;
4. 如果上一步返回的是对象类型,则将该值赋给 p;否则,返回步骤 1 中创建的 obj。
自有属性与继承属性的区别
8. 对象的自有属性与继承属性
代码如下:
function Person(name){
this.name = name;
}
Person.prototype = {
type: 'human',
getName: function(){return this.name}
};
var p = new Person('zhangsan');
alert(p.hasOwnProperty('type')); // false
p.type = 'ren';
alert(p.hasOwnProperty('type')); // true运行结果明确表明:对象无法修改原型中的同名属性,而是会在自身创建一个同名属性并赋值。
函数对象的创建过程
9. 函数对象的创建过程
1. 创建一个内置对象 fn;
2. 将 fn 的内部 [[Prototype]] 设置为 Function.prototype;
3. 设置内部的 [[Call]] 属性,这是一个内部方法,用于处理函数调用的逻辑(可简单理解为指向函数体);
4. 设置 fn.length 为 funArgs.length,如果函数没有参数,则将 fn.length 设置为 0;
5. 使 fn.prototype 的 constructor 指向 fn 自身;
6. 返回 fn。
instanceof 运算符原理
10. instanceof 运算符的原理
判断 a 是否为 B 的实例,即检查 B 的 prototype 属性指向的对象是否存在于 a 的原型链上。
Function 与 Object 的特殊关系
11. 关于 Function 和 Object 的相互关系
alert(Function instanceof Function); // true alert(Function instanceof Object); // true alert(Object instanceof Function); // true alert(Object instanceof Object); // true
这一现象反映了 JavaScript 中 Function 和 Object 的特殊地位,Function 是构造函数的构造函数,而 Object 是所有对象的基类。