本文内容
本文主要是记录 Javascript 中关于变量与对象的内容
变量声明
声明后没有赋值的,值为
undefined重复声明不会改变该变量,例如:
var a = 7; console.log(a); // 7 var a; console.log(a); // 7常用的非覆盖操作:
var a = a || 7; // 如果 a 已经有值就直接使用,否则赋值 7
变量和引用
和
Java相似,变量可能指向基本类型和引用类型函数传参是值传递,如果传递是参数是引用类型,相当于把引用类型的引用地址传递给了参数(可以这么理解),对象并没有进行复制,所以函数内部对传递的对象型参数进行修改是对外部有效的,例如:
var a = {x:1}; function test(arg){ arg.x++; } test(a); console.log(a.x); // 输出 2变量和属性可以认为是完全一样的,例如:
var a = 3 var b = {x:3}; // 可以认为 a 和 b.x 的地位是一样的,只不过两者的名称不同web 环境中,全局对象是
window,node 环境中,全局对象是global,web 环境中,我们在函数外部声明的变量都属于全局对象,可以将 JS 理解成一棵树,全局对象就是树根,我们定义的变量都是挂载树根下的,然后变量可能也是个对象,它也有属性,这样一层一层的,就构造成了一颗全局对象树。局部变量是调用函数时隐式生成的对象的属性,我们调用一个函数,就隐式生成了一个 Call 对象。
默认参数的惯用写法:
function getDistance(pos) { pos = pos || { x: 0, y: 0, z: 0 }; // 如果没有收到参数 pos 的话,则使用默认值 return Math.sqrt(pos.x * pos.x + pos.y * pos.y + pos.z * pos.z); }
构造函数和 new 表达式 (ES 5)
构造函数是用于生成对象的函数,例如:
// 构造函数(类的定义)
function MyClass(x, y) {
this.x = x;
this.y = y;
}
var obj = new MyClass(3, 2);
console.log(obj.x, obj.y);
// 输出 3 2
从形式看,构造函数有以下特质:
- 构造函数本身和普通的函数声明形式相同
- 构造函数通过 new 表达式来调用
- 调用构造函数的 new 表达式的值是(被新生成的)对象的引用
- 通过 new 表达式调用的构造函数内的 this 引用了(被新生成的)对象
new 表达式的操作
- 生成一个不具有特别的操作对象(类型是函数名称)。
- 构造函数的 this 指向这个新生成的空对象
- 将这个新生成对象的引用返回(函数作为构造函数进行调用的时候,最后会隐式
return this) - 如果构造函数内最后有 return 语句,那么有以下情况:
- return 返回的是基本类型,或者只有 return 没有其它值,那么行为跟上述一致,最终构造函数返回了构造出的新对象。(构造函数会无视掉基本类型和空的
return) - return 返回的是引用类型,那么构造函数返回的是这个引用,而不是构造的新对象
- return 返回的是基本类型,或者只有 return 没有其它值,那么行为跟上述一致,最终构造函数返回了构造出的新对象。(构造函数会无视掉基本类型和空的
this 引用规则
最外层代码中,this 引用的是全局对象(非模块化的情况下,在 node 中无法还原 this === global,在浏览器环境中有 this === window)
在函数内,this 引用根据 函数调用方式 具有不同的指向:
| 函数的调用方式 | this 引用的引用对象 |
|---|---|
| 构造函数调用 | 构造的新对象 |
| 方法调用 | 接收方对象,例如 obj.method()指向 obj |
| apply 或是 call 调用 | 由 apply 或 call 的参数指定的对象 |
| 其他方式的调用 | 全局对象 |
接收方对象的示例:
var obj = {
x: 100,
doit: function () {
console.log('method is called.' + this.x);
var innerObj = {
x: 20,
inner: function () {
console.log('inner method is called.' + this.x);
}
}
innerObj.inner();
}
}
obj.doit();
// 输出:
// method is called.100
// inner method is called.20
进一步说明接收方的概念(以下示例是跑在 node 环境中,所以全局对象是 global):
global.x = 3; // 给全局对象添加属性 x = 3
var obj = {
x: 100,
doit: function () {
console.log('method is called.' + this.x);
}
}
obj.doit(); // 接收方是 obj,所以输出的是 obj.x = 100
var tmp = obj.doit;
tmp(); // 没有接收方,this 指向全局对象 global,所以输出的是 global.x = 3;
apply 与 call
通过 apply 与 call 调用的函数的 this 引用可以指向任意对象。也就是说,它们可以显式地指定接收对象,例如:
function f() {
console.log(this.x);
}
var obj = { x: 4 };
f.apply(obj); // 指定此次调用中,f指向的接收对象是 obj,因此输出 obj.x = 4
f.call(obj); // 指定此次调用中,f指向的接收对象是 obj,因此输出 obj.x = 4
var obj = {
x: 3,
doit: function () {
console.log('method is called.' + this.x);
}
};
var obj2 = { x: 5 };
obj.doit.apply(obj2); // 指定此次调用中,f指向的接收对象是 obj2,因此输出 obj2.x = 5
两种方法的第一个参数都是接收方对象,两种方式的区别仅仅在于后续传递参数的方式,例如:
function f(a, b) {
console.log('this.x = ' + this.x + ', a = ' + a + ', b = ' + b);
}
f.apply({ x: 4 }, [1, 2]); // apply 后续参数需要使用数组传递 [1,2]
f.call({ x: 4 }, 1, 2); // call 后续参数按照原来参数的顺序进行传递 1,2
原型链
记住以下 2 条即可:
JavaScript 对象有一个指向一个原型对象的引用(称之为
__proto__)。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型对象,以及该对象的原型对象的原型对象,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾(一直找到 Object 到 null)。例如:var obj1 = { x: 1, name: function () { return 'obj1'; } }; var obj2 = { y: 2, name: function () { return 'obj2'; } }; var obj3 = { z: 3, name: function () { return 'obj3'; }, toString: function () { for (var x in this) { if (x !== 'name' && x !== 'toString') { console.log('Object: ' + this.name() + ', ' + x + ': ' + this[x]); } } } }; obj1.__proto__ = obj2; obj2.__proto__ = obj3; // 经过以上操作,现在 obj1 的原型对象是 obj2,obj2 的原型对象是 obj3 // 因此 obj1 拥有 obj2 和 obj3 的属性 y 和 z,obj2 拥有 obj3 的属性 z obj1.toString(); obj2.toString(); obj3.toString(); // 输出: // Object: obj1, x: 1 // Object: obj1, y: 2 // Object: obj1, z: 3 // Object: obj2, y: 2 // Object: obj2, z: 3 // Object: obj3, z: 3注意:
__proto__不是一个标准属性,但是许多 JS 环境都实现了该属性。从 ES 6 开始,我们可以通过
Object.getPrototypeOf(obj)和Object.setPrototypeOf(obj)来访问一个对象的原型对象,这个等同于非标准实现的对象的__proto__属性。为了避免混淆,本文就一直使用
__proto__来代表一个对象的原型对象。千万注意
__proto__和下文要讲的prototype属性是两个完全不同的属性
被构造函数创建的实例对象的
___proto__指向这个构造函数的prototype属性,例如:function a() { } /** * a 对象(函数也是个对象)的原型现在是这个 { x: 3 } 对象 * 因此 a.x = 3 */ a.__proto__ = { x: 3 }; a.prototype.x = 5; // 将 a 的 prototype 属性对象,添加一个属性 x = 5 /** * 由构造函数(a 对象)生成的对象,它的原型对象等于构造函数对象的 prototype 属性 * 也就是说 b.__proto__ === a.prototype 此时是成立的 * 如果我调用 a.prototype = otherObj,直接修改指针指向的话 * 那么 b.__proto__ === a.prototype 这个等式就不成立了 */ var b = new a(); console.log(a.x); // a.x = a.__proto__.x = 3 /** * b.x = b.__proto__.x = a.prototype.x = 5 * 记住(b.__proto__ === a.prototype) */ console.log(b.x);也可以这么说:只有构造函数对象的
prototype属性才是有意义的,普通对象的prototype属性没有其它的含义(暂时理解是这样),而所有对象(包括构造函数对象)的___proto__属性都指向了这个对象本身的原型对象。
Object.create 方法
MDN 文档:Object.create()
Javascript 官方一共有 3 种对象生成方法:
- 对象字面量
- new 表达式
- Object.create
API:Object.create(proto[, propertiesObject])
作用:创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
例如:
var Proto = { x: 2, y: 3 };
var obj = Object.create(Proto); // 相当于 var obj = {}; obj.__proto__ = Proto
console.log(obj.x); // 输出 2
这个方法的第二个参数是一个关联数组,key 是新对象的属性名,值是属性描述符(描述这个属性的值、元属性的值),例如:
var Proto = { x: 2, y: 3 };
var obj = Object.create(Proto,
{
a: { value: 5, writable: true, enumerable: true, configurable: true },
b: { value: 8, writable: true, enumerable: true, configurable: true }
}
);
console.log(obj.a); // 输出 5
console.log(obj.b); // 输出 8
console.log(obj.x); // 输出 2
console.log(obj.y); // 输出 3