文章目录
实例对象与new命令
什么是对象
面向对象OOP是目前主流的编程范式,它将真实世界的复杂关系抽象为一个对象,然后由对象之间的分工合作完成对真实世界的模拟。
- 对象是单个实物的抽象
- 对象是一个容器,封装了属性和方法
构造函数
OOP编程首先生成对象。对于传统的面向对象语言C++和java,他们都有类
的概念,通过类来生成对象。而在js中却不是这样基于类的,它是基于构造函数(constructor)和原型链(prototype).
js中使用构造函数作为对象的模版,描述实例对象的基本看结构. 一个构造函数可以生成多个实例对象,这些实例对象都有相同的结构.
构造函数仅仅是一个普通的函数,有自己的特征和用法
var Vec = function(){
this.price = 1000;
};
上述代码中的Vec
函数既是一个构造函数, 一般习惯上将构造函数的第一个字母大写.
构造函数的特征点有两个
- 函数内部的`this `关键字,代表了所要生产的对象实例
- 生成对象时候必须使用`new`命令
new命令
new 命令用于执行构造
var Vec = function(){
this.price = 1000;
};
var v = new Vec();
v.price //1000
构造函数也可以接受参数
var Vehicle = function (p) {
this.price = p;
};
var v = new Vehicle(500);
如果忘记使用new
,那么构造变成了普通的函数调用,而构造函数中的this``指代全局对象, 所以在构造函数时忘记使用
new` 将是一件很危险的事情.
*** 为了保证构造函数必须与new命令一起使用, 我们可以使用严格模式,在改模式下忘记使用new
编译器会报错. ***
function Funca(foo,bar){
'use strict';
this._foo = foo;
this._bar = bar;
}
var fua = Funca(); // 报错
另一个解决办法,构造函数内部判断是否使用new命令,如果发现没有使用,则直接返回一个实例对象。
function Fubar(foo, bar) {
if (!(this instanceof Fubar)) {
return new Fubar(foo, bar);
}
this._foo = foo;
this._bar = bar;
}
Fubar(1, 2)._foo // 1
(new Fubar(1, 2))._foo // 1
new命令的原理
new
命令会执行一下步骤.
1. 创建一个空对象,作为将要返回对象的实例.
2. 将这个空对象的原型, 指向构造函数prototype属性
3. 将这个空对象赋值给函数内部的`this`指针
4. 开始执行构造函数内部的代码
如果构造函数内部有return语句,而且return后面跟着一个对象,new命令会返回return语句指定的对象;否则,就会不管return语句,返回this对象。
var Vehicle = function () {
this.price = 1000;
return 1000; // 在使用该构造时会忽略掉改语句,返回`this`
};
(new Vehicle()) === 1000
// false
如果对普通函数(内部没有this关键字的函数)使用new命令,则会返回一个空对象。
function getMessage() {
return 'this is a message';
}
var msg = new getMessage();
msg // {}
typeof msg // "object"
new.target
函数内部可以使用new.target
属性. 如果当前函数是new
命令调用,new.target
指向当前函数,否则为undefined
Object.create() 创建实例对象
构造函数作为模版生成实例对象. 我们可以根据现有对象来创建一个对象.
var person1 = {
name:'张三' ,
age:38,
greeting: function() {
console.log('Hi! I\'m ' + this.name+ '.');
}
};
var person2 = Object.create(person1);
person2.name
person2.greeting()
this 关键字
含义
简单说,this就是属性或方法“当前”所在的对象。
this.property
// 当前对象的property属性
js语言之中,一切皆为对象,运行环境也是对象,所以函数都是在某个对象中运行,this
就是函数运行是所在的对象. js中this
的指向是动态的,没有办法事先确定到底指向哪个对象.
实质
js中原始对象是以字典结构保存,每一个属性名都对应一个属性描述对象.
var obj = { foo: 5};
//以上对象中的foo属性实际上是以下面的形式保存
{
foo: {
[[value]]:5
[[writable]]:true
[[enumberable]]:true
[[configurable]]:true
}
}
当属性的值为函数时,引擎会将函数单独保存在内存中,然后将函数的地址赋值给foo属性的value属性.
{
foo : {
[[value]]: 函数地址
...
}
}
由于函数是一个单独的值,所以它可以在不同环境执行. 如果想要执行当前环境中的变量,那么需要使用this
关键字:
var f = function (){
console.log(this.x);
};
使用场合
全局环境
全局使用this
, 他指的是顶层对象window
.
this === window // true
function f() {
console.log(this === window);
}
f(); //true
构造函数
构造函数中的this
指的是实例对象
var Obj = function(p) {
this.p = p;
};
对象的方法
如果对象的方法里包含this
,this
的指向就是方法运行时所在的对象. 该方法给另一个对象就会改变this
的指向.
使用注意点
避免多层this
避免数组处理方法中的this
数组的map和foreach方法,允许提供一个函数作为参数。这个函数内部不应该使用this。
var o = {
v: 'hello',
p: [ 'a1', 'a2' ],
f: function f() {
this.p.forEach(function (item) {
console.log(this.v + ' ' + item);
});
}
}
o.f()
// undefined a1
// undefined a2
foreach方法的回调函数中的this
指代的是window
对象. ***内层this
不指向外层,而是指向顶层的window对象.
解决方法有两种
- 使用中间变量固定
- 将this当做foreach的第二个参数.
避免回调函数中使用this
回调函数中的this往往会改变指向, 最好避免使用.
var o = new Object() ;
o.f = function (){
console.log(this === o);
}
// jQuery的写法
$('#button').on('click', o.f);
点击按钮以后,控制台会显示false
. 原因是此时this
不再指向o
对象, 而是指向按钮对象. ***为了解决这个问题,可以对this进行绑定.
绑定this
的方法
this
的动态切换, 固然为Javasscript创造了灵活性. 有时, 需要把this
固定下来, 避免出现意想不到的情况.
js中提供了call
, apply
, bind
这三个方法,来固定和切换this
指针.
Function.prototype.call()
函数实例的call``方法,可以指定函数内部的
this`的指向 (即函数执行时所在的域),然后在指定的作用于中调用改函数
var obj = {};
var f = function (){
return this;
};
f() === window //true
f.call(obj) === obj //true
上述代码中,call
方法可以改变this
的指向为obj
, 然后在对象obj
的作用域中运行函数f
call
方法的参数应该是一个对象,如果为空, null
或 undefined
, 则默认传入全局对象.
call的第一个参数就是this所要指向的那个对象,后面的参数则是函数调用时所需的参数
call方法可以调用对象的原生方法
var obj = {};
obj.hasOwnProperty('toString') // false
// 覆盖掉继承的hasOwnProperty()方法
obj.hasOwnProperty = function () {
return true;
};
obj.hasOwnProperty('toString') // true
Object.protopype.hasOwnProperty.call(obj,'toString') //false
// 将hasOwnProperty方法的原始定义放到obj对象上执行,这样无论`obj`对象上是否有同名方法,都不会影响结果.
Function.prototype.apply()
apply() 方法的作用与call方法类似, 也是改变this
指向,然后再调用该函数. 他们的区别是,它接受一个数组作为函数的参数.
func.apply(thisValue, [arg1,arg2,arg3,...] )
利用apply()函数的特性, 可以做一些有趣的事情.
找出数组中的最大元素
var a = [10,2,4,15,9];
Math.max.apply(null,a) // 15
将数组的空元素变成undefined
通过apply()方法,利用Array的构造函数将数组的空元素变成undefined
.
Array.apply(null, ['a', ,'b'])
// [ 'a', undefined, 'b' ]
空元素与undefined的差别在于,数组的forEach方法会跳过空元素,但是不会跳过undefined。
转换类似数组的对象
另外,利用数组对象的slice方法,可以将一个类似数组的对象(比如arguments对象)转为真正的数组。
Array.prototype.slice.apply({0:1, length:1}) // [1]
Array.prototype.slice.apply({0:1}) // []
Array.prototype.slice.apply({0:1, length:2}) // [1,undefined]
Array.prototype.slice.apply({length: 1}) // [undefined]
上面代码的apply方法的参数都是对象,但是返回结果都是数组,这就起到了将对象转成数组的目的。从上面代码可以看到,这个方法起作用的前提是,被处理的对象必须有length属性,以及相对应的数字键。
绑定回调函数的对象
var o = new Object();
o.f = function () {
console.log(this === o);
}
var f = function (){
o.f.apply(o);
// 或者 o.f.call(o);
};
// jQuery 的写法
$('#button').on('click', f);
Function.prototype.bind()
bind
方法用于将函数体内的this
绑定到某个对象上, 然后返回一个新函数.
var d = new Date();
d.getTime()
var print = d.getTime;
print() //报错
上述代码报错是因为getTime
方法内部的this
绑定了Date
对象实例, 赋给变量print
以后, 内部的this
已经不在指向Date
实例了.
使用bind
方法即可解决这种问题.
var print = d.getTime.bind(d);
print() //正常输出
this
不仅可以绑定到原对象中,还可以绑定到其他对象中
var counter = {
count: 0,
inc: function() {
this.count++;
}
};
var obj = {
count: 100;
}
var func = counter.inc.bind(obj); //将inc方法中的`this 绑定到obj方法
func();
obj,count // 101
bind
方法需要注意
- 每一次返回一个新的函数
- 结合回调函数使用
- 结合call方法使用