JavaScript this 使用及面向对象编程
简介
JavaScript 并没有提供传统面向对象语言中的类式继承,而是通过原型委托的方式来实现对象与对象之间的继承
ECMAScript 6 带来了新的 Class 语法,这让JavaScript 看起来像是一门基于类的语言,但其背后仍是通过原型机制来创建对象
this
javascript 的
this
总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境绑定的,而非函数被声明时的环境
1. 作为对象的方法调用
当函数作为对象的方法调用时,
this
指向该对象
var name = null;
var o = {
name: 'name',
getName: function () {
console.log(this.name);
}
}
o.getName(); // name
2. 作为普通函数调用
此时的
this
总是指向全局对象
window.name = 'globalName';
var getName = function(){
console.log(this); // window
// ECMAScript 5 的 strict 模式下 这里的this不会指向全局变量,而是 undefined
return this.name;
};
console.log( getName() ); // globalName
3. 构造器调用
javascript 中通过构造器来创建对象,同时提供了 new 运算符用来调用,并且返回一个对象,通常情况下,构造器里的
this
指向这个返回的对象
var Anm = function (name) {
this.name = name;
}
var anm = new Anm('xiaobai');
console.log(anm.name); // xiaobai
// 如果构造函数显示的返回了一个引用类型数据,那么 new 出来的结果就是这个对象,而不是之前的this
var Anm = function (name) {
this.name = name;
return {
test: 'test'
}
}
var anm = new Anm('xiaobai');
console.log(anm); // {test: "test"}
console.log(anm.name); // undefined
// 如果返回的不是引用类型的数据,或者没有显示的返回数据,则返回 this
var Anm = function (name) {
this.name = name;
return 'test';
}
var anm = new Anm('xiaobai');
console.log(anm); // {name: "xiaobai"}
console.log(anm.name); // xiaobai
4. Function.prototype.call
或者 Function.prototype.apply
这两个方法可以动态的改变传入函数的
this
,两个方法的作用一致,区别在于
apply
方法接受两个参数(第一个参数指定函数体内this的指向,第二个参数为一个集合,可以是数组也可以是类数组,apply
方法把这个集合中的元素作为参数传递给被调用的函数)call
传入的参数数量是不固定的,第一个参数代表函数体内this
的指向,第二个参数开始为需要传入函数体的参数
用途
- 改变
this
指向
var a = {
name: 'a'
};
var b = {
name: 'b',
getName: function () {
console.log(this.name);
}
};
b.getName(); // b
b.getName.call(a); // a
- 借用其他对象的方法
var func = function () {
// var temp = arguments.shift(); // 报错 arguments.shift is not a function
var temp = [].shift.call(arguments); // 11
console.log(temp);
}
func(11, 22, 33, 44);
// 借用内置对象的方法
var a = Math.max.apply(null, [1, 44, 22, 3]);
console.log(a); // 44
- 当 call 或者 apply 的第一个参数为 null 时,函数体内的指针会默认指向宿主对象,浏览器中就是 window ,但如果在严格模式下,this 指向 null
var name = 'global';
function getName(ages) {
console.log(this); // window 或者 严格模式先的 null
console.log(this.name, ages);
}
var obj = {
name: 'in obj'
};
getName.call(null, [1, 23, 3]);
面向对象的三大特性
1. 封装
封装的目的是将信息隐藏,包括封装数据、封装实现、封装类型、封装变化,将操作封闭,对外提供接口使用
// 构造函数将同一类对象的实现封装起来
var F = function (name) {
this.name = name;
}
F.prototype.test = function () {
console.log(this.name);
}
var f1 = new F('xiaobai');
var f2 = new F('xiaolan');
f1.test(); // xiaobai
f2.test(); // xiaolan
2. 多态
同一操作作用于不同的对象上,可以产生不同的解释和执行不同的结果,实现上是将‘不变的事物’与‘可能改变的事物’分离
多态的思想实际上是把‘做什么’和‘谁去做’分离开,对象在接受命令的时候如何处理不是临时决定,每个对象应该做什么,已经成为了该对象的一个方法,安装在对象内部,每个对象对自己的行为负责,所以这些对象可以根据同一个命令,分别进行各自的工作。
// 隔离出不变的部分
var testFn = function (obj) {
obj.test();
}
// 可变的部分各自封装
var a = {
name: 'a',
test: function () {
console.log(this.name);
}
}
var b = {
name: 'b',
test: function () {
console.log('name is ' + this.name);
}
}
testFn(a); // a
testFn(b); // name is b
3. 继承
在以类为中心的面向对象语言中,对象总是从类中创建,而 javascript 是基于原型的,对它来说,类不是必须的,对象未必需要从类中创建而来,一个对象可以通过克隆另一个对象所得到
JavaScript 遵守原型编程的基本准则
所有的数据都是对象(其实是绝大多数都是对象 出了
undefined
)要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并且克隆它(引擎内部实现 通过 new 运算符调用函数创建对象时,实际上也是先克隆了
Object.prototype
对象,然后在进行其他的额外操作)对象会记住它的原型 (JavaScript 给对象提供了一个隐藏的属性
__proto__
它会指向它的构造器的原型对象)如果不对象无法响应某个请求,他会把这个请求委托给自己的原型