面对对象编程----Object Oriented Programming,简称OOP,是一种编程开发思想
在JavaScript中,所有数据类型都可以视为对象,当然也可以自定义对象
自定义对象数据类型就是面对对象中类发概念
假设我们要处理学生的成绩表,为了表示一个学生的成绩,面向过程的程序可以用一个对象表示:
var std1 = { name: 'Michael', score: 98 }
var std2 = { name: 'Bob', score: 81 }
而处理学生的成绩可以通过函数实现,比如打印学生的成绩:
function printScore (student) {
console.log('姓名:' + student.name + ' ' + '成绩:' + student.score)
}
function Student(name, score) {
this.name = name;
this.score = score;
this.printScore = function() {
console.log('姓名:' + this.name + ' ' + '成绩:' + this.score);
}
}
根据模板创建具体实例对象(instance):
var std1 = new Student('Michael', 98)
var std2 = new Student('Bob', 81)
实例对象具有自己的具体行为(给对象发消息)
std1.printScore() // => 姓名:Michael 成绩:98
std2.printScore() // => 姓名:Bob 成绩 81
new object()
var person = new Object()
person.name = 'Jack'
person.age = 18
person.sayName = function () {
console.log(this.name)
}
每次创建通过new object()比较麻烦,所以可以通过它的简写形式对象字面量来创建
var person={
name:'jack',
age:18,
sayNmae:function(){
console.log(this.name);
}
}
对于上面的写法固然没有问题,但是假如我们要生成两个person对象?
var person1 = {
name: 'Jack',
age: 18,
sayName: function () {
console.log(this.name)
}
}
var person2 = {
name: 'Mike',
age: 16,
sayName: function () {
console.log(this.name)
}
}
function createPerson (name, age) {
return {
name: name,
age: age,
sayName: function () {
console.log(this.name)
}
}
}
然后生成实例对象
var p1 = createPerson('Jack', 18)
var p2 = createPerson('Mike', 18)
function Person (name, age) {
this.name = name
this.age = age
this.sayName = function () {
console.log(this.name)
}
}
var p1 = new Person('Jack', 18)
p1.sayName() // => Jack
var p2 = new Person('Mike', 23)
p2.sayName() // => Mike
person()和creatPerson()的不同
function Person (name, age) {
// 当使用 new 操作符调用 Person() 的时候,实际上这里会先创建一个对象
// var instance = {}
// 然后让内部的 this 指向 instance 对象
// this = instance
// 接下来所有针对 this 的操作实际上操作的就是 instance
this.name = name
this.age = age
this.sayName = function () {
console.log(this.name)
}
// 在函数的结尾处会将 this 返回,也就是 instance
// return this
}
使用构造函数的好处不仅仅在于代码的简洁性,更重要的是我们可以识别对象的具体类型
在每一个实例对象中同时有一个constructor属性,该属性指向创建改实例的构造函数
console.log(p1.constructor === Person) // => true
console.log(p2.constructor === Person) // => true
console.log(p1.constructor === p2.constructor) // => true
对象的constructor属性最初是用来标识对象类型的,但是如果要检测对象的类型,还是使用instanceof操作符更可靠
console.log(p1 instanceof Person) // => true
console.log(p2 instanceof Person) // => true
总结:
使用构造函数带来的最大好处就是创建对象更方便了,但是其本身也存在一个浪费内存的问题:
function Person (name, age) {
this.name = name
this.age = age
this.type = 'human'
this.sayHello = function () {
console.log('hello ' + this.name)
}
}
var p1 = new Person('Tom', 18)
var p2 = new Person('Jack', 16)
上面的示例每一个type和sayhello都是一模一样的内容,每一次生成一个实例,都必须为重复的内容,多占用一些内存如果实例对象很多,会造成极大的内存浪费。
console.log(p1.sayHello === p2.sayHello) // => false
对于这种问题我们可以定义到构造函数外部
function sayHello = function () {
console.log('hello ' + this.name)
}
function Person (name, age) {
this.name = name
this.age = age
this.type = 'human'
this.sayHello = sayHello
}
var p1 = new Person('Top', 18)
var p2 = new Person('Jack', 16)
console.log(p1.sayHello === p2.sayHello) // => true
这样确实可以了,但是如果有多个需要共享的函数的话就会造成全局命名空间冲突的问题
var fns = {
sayHello: function () {
console.log('hello ' + this.name)
},
sayAge: function () {
console.log(this.age)
}
}
function Person (name, age) {
this.name = name
this.age = age
this.type = 'human'
this.sayHello = fns.sayHello
this.sayAge = fns.sayAge
}
var p1 = new Person('lpz', 18)
var p2 = new Person('Jack', 16)
console.log(p1.sayHello === p2.sayHello) // => true
console.log(p1.sayAge === p2.sayAge) // => true
小结:
JavaScript规定:每一个构造函数都有一个prototype属性指向另一个对象。
这个对象的所有属性和方法,都会被构造函数的所拥有
function Person (name, age) {
this.name = name
this.age = age
}
console.log(Person.prototype)
Person.prototype.type = 'human'
Person.prototype.sayName = function () {
console.log(this.name)
}
var p1 = new Person(...)
var p2 = new Person(...)
console.log(p1.sayName === p2.sayName) // => true
这时所有实例的type属性和sayname()方法
其实都是同一个内存地址,指向prototype对象,因此提高了运行效率
function F () {}
console.log(F.prototype) // => object
F.prototype.sayHi = function () {
console.log('hi!')
}
构造函数的prototype对象默认都有一个constructor属性,指向prototype对象所在函数。
console.log(F.prototype.constructor === F) // => true
通过构造函数得到的实例对象内部都会包含一个指向构造函数的prototype对象的指针_ _proto _ _
var instance = new F()
console.log(instance.__proto__ === F.prototype) // => true
总结:
读取:
function Person (name, age) {
this.name = name
this.age = age
}
Person.prototype = {
type: 'human',
sayHello: function () {
console.log('我叫' + this.name + ',我今年' + this.age + '岁了')
}
}
在该示例中,我们将person.prototype重置到一个新的对象
这样做的好处就是为person.prototype添加成员简单了,但是也会带来一个问题,那就是原型对象丢失了constructor成员
function Person (name, age) {
this.name = name
this.age = age
}
Person.prototype = {
constructor: Person, // => 手动将 constructor 指向正确的构造函数
type: 'human',
sayHello: function () {
console.log('我叫' + this.name + ',我今年' + this.age + '岁了')
}
}
原生对象的原型
所有函数都有prototype属性对象
function Person (name, age) {
this.type = 'human'
this.name = name
this.age = age
}
function Student (name, age) {
// 借用构造函数继承属性成员
Person.call(this, name, age)
}
var s1 = Student('张三', 18)
console.log(s1.type, s1.name, s1.age) // => human 张三 18
function Person (name, age) {
this.type = 'human'
this.name = name
this.age = age
}
Person.prototype.sayName = function () {
console.log('hello ' + this.name)
}
function Student (name, age) {
Person.call(this, name, age)
}
// 原型对象拷贝继承原型对象成员
for(var key in Person.prototype) {
Student.prototype[key] = Person.prototype[key]
}
var s1 = Student('张三', 18)
s1.sayName() // => hello 张三
function Person (name, age) {
this.type = 'human'
this.name = name
this.age = age
}
Person.prototype.sayName = function () {
console.log('hello ' + this.name)
}
function Student (name, age) {
Person.call(this, name, age)
}
// 利用原型的特性实现继承
Student.prototype = new Person()
var s1 = Student('张三', 18)
console.log(s1.type) // => human
s1.sayName() // => hello 张三
function foo () {
}
var foo = function () {
}
if (true) {
function f () {
console.log(1)
}
} else {
function f () {
console.log(2)
}
}
调用方式 | this指向 |
---|---|
普通函数调用 | window |
构造函数调用 | 实例对象 |
对象方法调用 | 该方法所属对象 |
所有函数都是function的实例
call()方法调用一个函数,其具有一个指定的this值和分别地提供参数
注意:该方法的作用和apply()方法有点类似,只有一个区别,就是call()方法接受的是若干个参数的列表,而apply()方法接受的是一个包含多个参数的数组
语法:
fun.call(thisArg[, arg1[, arg2[, ...]]])
参数:
apply()方法调用一个函数,其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数。
语法:
fun.apply(thisArg, [argsArray])
参数:
fun.apply(this, ['eat', 'bananas'])
bind() 函数会创建一个新函数,新函数与被调函数具有相同的函数体
在目标函数被调用时this值绑定到bind()的第一个参数,该参数不能被重写。绑定函数被调用时,bind()也接受预设的参数提供给原函数
一个绑定函数也能使用new操作符创建对象,这种行为就像把原函数当成构造器。提供的this值被忽略,同时调用时的参数被提供给模拟函数。
语法:
fun.bind(thisArg[, arg1[, arg2[, ...]]])
参数:
thisArg
arg1, arg2, …
返回值:
返回由指定的this值和初始化参数改造的原函数拷贝。
示例:
this.x = 9;
var module = {
x: 81,
getX: function() { return this.x; }
};
module.getX(); // 返回 81
var retrieveX = module.getX;
retrieveX(); // 返回 9, 在这种情况下,"this"指向全局作用域
// 创建一个新函数,将"this"绑定到module对象
// 新手可能会被全局的x变量和module里的属性x所迷惑
var boundGetX = retrieveX.bind(module);
boundGetX(); // 返回 81
function LateBloomer() {
this.petalCount = Math.ceil(Math.random() * 12) + 1;
}
// Declare bloom after a delay of 1 second
LateBloomer.prototype.bloom = function() {
window.setTimeout(this.declare.bind(this), 1000);
};
LateBloomer.prototype.declare = function() {
console.log('I am a beautiful flower with ' +
this.petalCount + ' petals!');
};
var flower = new LateBloomer();
flower.bloom(); // 一秒钟后, 调用'declare'方法
让伪数组使用数组的方法
var obj = {
0: 1,
1: 8,
2: 10,
3: 3,
4: 2,
length: 5
};
Array.prototype.splice.call(obj, 0, 1);
Array.prototype.push.call(obj, 100);
console.log(obj);
对象内部使用使用定时器
var o = {
name: 'hanmeimei',
func1: function () {
console.log(this.name);
},
func2: function () {
setInterval(function () {
console.log(this);
}.bind(this), 2000);
}
};
o.func2();
让数组的每一项作为方法的参数
var arr = [2, 1, 8, 9, 10, 3];
console.log(Math.max.apply(Math, arr));
console.log.apply(console, arr);
call 和 apply 特性一样
this
的指向null
或者 undefined
则内部 this 指向 windowbind