OOP
实例对象与new命令
构造函数
JS中的面向对象是通过构造函数(constructor)和原型链(prototype)来实现的。JavaScript 语言使用构造函数(constructor)作为对象的模板。
var Vehicle = function () {
this.price = 1000;
};
为了与普通函数区别,构造函数名字的第一个字母通常大写。
- 函数体内使用
this
关键词代表了所要生成的对象实例 - 生成对象的时候,必须使用
new
命令
new命令
new 命令就是生成一个实例对象。
var v = new Vehicle();
v.price // 1000
new
命令执行时,构造函数内部的this
,就代表了新生成的实例对象,this.price
表示实例对象有一个price
属性,值是1000。
可以通过在构造函数第一行加上use strict
以防止不使用new,直接调用函数,此时会报错。同时也可以使用new.target属性来判断是否通过new命令调用构造函数。
function f() {
if (!new.target) {
throw new Error('请使用 new 命令调用!');
}
// ...
}
f() // Uncaught Error: 请使用 new 命令调用!
new 命令的原理:
(1)创建一个空对象,作为要返回的对象实例
(2)将这个空对象的原型,指向构造函数的prototype
属性
(3)将这个空对象赋值给函数内部的this关键词
(4)继续执行构造函数内部操作
构造函数之所以叫构造函数,是说这个函数的目的是操作空对象,构造成需要的样子。
如果构造函数内部有return
语句,而且return
后面跟着一个对象,new
命令会返回return
语句指定的对象;否则,就会不管return
语句,返回this
对象。
//返回this对象的例子:
var Vehicle = function () {
this.price = 1000;
return 1000;
};
(new Vehicle()) === 1000// false
//返回对象的例子:
var Vehicle = function (){
this.price = 1000;
return {
price: 2000 };
};
(new Vehicle()).price
// 2000
有时候我们拿不到构造函数,可以使用Object.create方法来创建实例对象,person2继承了person1的方法和属性。
var person1 = {
name: '张三',
age: 38,
greeting: function() {
console.log('Hi! I\'m ' + this.name + '.');
}
};
var person2 = Object.create(person1);
person2.name // 张三
person2.greeting() // Hi! I'm 张三.
this 关键字
简单说,this
就是属性或方法“当前”所在的对象。
由于对象的属性可以赋给另一个对象,所以属性所在的当前对象是可变的,即this
的指向是可变的。
var A = {
name: '张三',
describe: function () {
return '姓名:'+ this.name;
}
};
var B = {
name: '李四'
};
B.describe = A.describe;
B.describe()
// "姓名:李四"
上面代码中,A.describe
属性被赋给B
,于是B.describe
就表示describe
方法所在的当前对象是B
,所以this.name
就指向B.name
。
只要函数赋值给另一个变量,this
指向就会变。下述例子中,A.describe被赋值给f后,this.name就会选择f运行时所在的name
var A = {
name: '张三',
describe: function () {
return '姓名:'+ this.name;
}
};
var name = '李四';
var f = A.describe;
f() // "姓名:李四"
总结一下,JavaScript 语言之中,一切皆对象,运行环境也是对象,所以函数都是在某个对象之中运行,this就是函数运行时所在的对象(环境)。
实质
看似简单的一句代码,其实JS引擎会在内存中存储一个对象{foo:5}
,然后让obj指向该内存地址(或者说obj就是一个地址),所以当访问obj.foo
时,会首先从内存地址找到那个对象,然后再去拿他的foo
属性。
var obj = {
foo: 5 };
在内存中该对象的存储形式为字典形式。
{
foo: {
[[value]]: 5
[[writable]]: true
[[enumerable]]: true
[[configurable]]: true
}
}
当对象中属性不是一个值而是一个函数时,会将函数的地址存储到value
中。
{
foo: {
[[value]]: 函数的地址
...
}
}
而函数可能在不同的环境下执行。
var f = function () {
};
var obj = {
f: f };
// 单独执行
f()
// obj 环境执行
obj.f()
JavaScript 允许在函数体内部,引用当前环境的其他变量。比如下列函数中的x
变量,允许由环境来提供。
var f = function () {
console.log(x);
};
设计this
的原因,是要能够在函数体内部获得当前的运行环境(找到调用函数所在的当前环境)。
因此在f函数中会使用this来指代当前的运行环境。下面为一个典型的例子,如果单独执行的话,属于在最外层运行环境,因此x是取全局变量1;而如果是在obj环境执行,则x是取局部变量2。
var f = function () {
console.log(this.x);
}
var x = 1;
var obj = {
f: f,
x: 2,
};
// 单独执行
f() // 1
// obj 环境执行
obj.f() // 2
使用场合
(1)全局变量
全局环境使用this,它指的就是顶层对象window。
this === window // true
function f() {
console.log(this === window);
}
f() // true
(2)构造函数
构造函数中使用this,它指的是实例对象。
var Obj = function (p) {
this.p = p;
};
var o = new Obj('Hello World!');
o.p // "Hello World!"
(3)对象的方法
如果对象的方法里面包含this
,this
的指向就是方法运行时所在的对象。该方法赋值给另一个对象,就会改变this
的指向。
这个很难理解,先看下面的例子,此时调用结果是obj,因为方法运行时所在的对象或环境就是obj。
var obj ={
foo: function () {
console.log(this);
}
};
obj.foo() // obj
而接下来三种情况都是最外层的window,可以这样理解:obj.foo()
相当于是从obj的地址去调用foo的地址,所以运行环境是在obj,因此this指向obj。
而下述三种情况都是直接去调用的foo,可以理解为直接从window调用的,所以this就指向window了。
// 情况一
(obj.foo = obj.foo)() // window
// 情况二
(false || obj