学过C++等语言的话,你一定明白面向对象的两个基本概念:
- 类:类是对象的类型模板,例如,定义Student类来表示学生,类本身是一种类型,Student表示学生类型,但不表示任何具体的某个学生;
- 实例:实例是根据类创建的对象,例如,根据Student类可以创建出xiaoming、xiaohong等多个实例,每个实例表示一个具体的学生,他们全都属于Student类型。
所以,类和实例是大多数面向对象编程语言的基本概念。
BUT,JavaScript不区分类和实例的概念,而是通过原型(prototype)来实现面向对象编程。
创建对象
JavaScript的所有数据都可以看成对象(Object)。
三种创建对象的方法:
- 对象字面量
var xiaoming = {
name: '小明',
hello: function () {
console.log('Hello, ' + this.name + '!');
}
};
- 构造函数
function Student(name) {
this.name = name;
this.hello = function() {
console.log('Hello, ' + this.name + '!');
}
}
var xiaoming = new Student("小明")
- Object.create()构造
Object.create(proto [, propertiesObject ]) 是E5中提出的一种新的对象创建方式,第一个参数是要继承的原型,如果不是一个子函数,可以传一个null,第二个参数是对象的属性描述符,这个参数是可选的。
var Student = {
name: 'Robot',
hello: function () {
console.log('Hello, ' + this.name + '!');
}
};
var xiaoming = Object.create(Student);
其中,第二种构造函数,如果不写new,Student就是一个普通函数(也可以说是方法Function),它返回undefined。但是,如果写了new,它就变成了一个构造函数,它绑定的this指向新创建的对象,并默认返回this,也就是说,不需要在最后写return this;了。
\\proto\\属性与prototype属性
我们只看前两种方法,第一种是直接创建对象,第二种是通过方法(Function)来创建对象。
JavaScript对每个创建的对象都会设置一个__proto__
属性,指向该对象的构造函数的原型对象。而如果是通过第二种构造函数的方法来创建的创建的对象还会多出一个prototype
属性。
解释如下:
//构造函数
function Student(name) {
this.name = name;
this.hello = function() {
console.log('Hello, ' + this.name + '!');
}
}
var xiaoming = new Student("小明")
var xiaosha = new Student("小傻")
//对象字面量
var Student1 = {
name: 'XXX',
hello: function () {
console.log('Hello, ' + this.name + '!');
}
};
var xiaohong = {
name: "xiaohong"
}
xiaohong.__proto__ = Student1;
在创建xiaohong的时候我们直接把xiaohong.\proto\ = Student1;为甚么这样写呢,因为它不是Function创建的对象,当然也就不具备prototype属性哈哈。但是上述代码仅用于演示目的。在编写JavaScript代码时,不要直接用obj.__proto__
去改变一个对象的原型,并且,低版本的IE也无法使用__proto__
。
说明几点:
- 由Img1可以看出,通过构造函数创建的对象除了
__proto__
属性还有prototype
属性。事实上,只要是Function就会有该属性,也即普通函数也有prototype
属性,原因是方法(Function)这个对象搞特殊,他还有自己特有的属性——原型属性(prototype),这个属性是一个指针,指向一个对象(这个被指向的对象我们后面细说)。
2.在JS中,方法(Function)是对象,方法的原型(Function.prototype)也是对象啊。
通过原型(prototype)来实现面向对象编程
function Foo(){
y = 2;
}
typeof Foo.prototype; //"object"
var obj = new Foo();
上面这个函数Foo是作为一个构造器来使用,this指向一个对象,而这个对象的原型会指向构造器的prototype属性(Foo.prototype也是一个对象)
function Person(name, age){
this.name = name;
this.age = age;
}
正常直接调用Person函数的话,this指向全局对象(在浏览器中为window),不写return的话,返回undefined。如果用new调用的话。this会指向为一个原型为Person.prototype的空对象,通过this.name
给这个空对象赋值,绑定的this指向这个新创建的对象(此时已经不空了),并默认返回this,也就是说,不需要在最后写return this
了。
call、apply
call方法:
语法:call([thisObj[,arg1[, arg2[, [,.argN]]]]])
定义:调用一个对象的一个方法,以另一个对象替换当前对象。
说明: call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。 如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。
apply方法:
语法:apply([thisObj[,argArray]])
都知道这两个函数能改变函数执行的上下文,可是我总是搞不懂第一个参数到底传入的是什么。如果是this,那this又是什么。。。
所以今天测试一下,应该是懂了。
function Person(name, age){
this.name = name;
this.age = age;
}
function Stu(name, age, className){
console.log(this) //Stu{}
Person.call(this,name,age)
console.log(this) //Stu{name: "Beatrice", age: 12}
this.className = className;
console.log(this) /Stu {name: "Beatrice", age: 12, className: 6}
}
var stu1 = new Stu("Beatrice", 12, 6);
最开始传入的是一个原型为Stu.prototype的空Stu{}对象。最后,我们把这个对象赋值给stu1。如何让Stu的实例能继承Person.prototype上的一些方法呢?
Stu.prototype = Object.create(Person.prototype);
Stu.prototype.constructor = Stu;
Object.create()
创建一个空对象,并且对象的原型指向它的参数。这里就创建了一个原型指向Person.prototype的空对象。因为Stu.prototype会作为所有new Stu()
实例的原型,为了继承Person.prototype上的一些方法,我们就把这个创建出来的空对象赋值给Stu.prototype,这样实例继承Stu.prototype,也就相当于实例继承这个原型为Person.prototype的空对象了。
那你也许会问,为什么不直接赋值Stu.prototype = Person.prototype
,而是要通过Object.create
呢?
如果直接赋值,Stu有一些自己的方法,当我们想增加Stu自己的方法的时候,也会加在Person上面,这显然不是我们想要的。所以我们搞一个中间的桥梁,也就是这个空对象。
这时,这个创建出的空对象的prototype == Person.prototype == Student.prototype。每一个prototype属性下面自带一个constructor属性,这个属性是指向自身的(所以这是个无限循环了呵呵)。所以prototype属性里面的constructor当然也都一致的,都是Person了。为了保证一致性,所以我们这里把Stu的constructor重新设置为指向自身。
内置构造器的prototype属性
字面量创建的对象var object= {};
的原型是Object.prototype
ES5中使用defineProperty()函数来对原型添加属性,而不是直接Object.prototype.x = 1
这种形式来添加。
obj.hasOwnProperty()
方法,只查找对象obj的方法,而不继续向上查找。
instanceof
[1,2] instanceof Array === true
new Object() instanceof Array === false
在instanceof左侧是对象,右侧是构造器(函数),instanceof用于判断右边这个构造器的prototype属性是否在左侧这个对象的原型链上。
实现继承的方式
function Person(){
}
function Student(){
}
Student.prototype = Person.prototype; //1 这是错的!!
Student.prototype = new Person(); //2 有点小问题,当构造函数要传入参数的时候
Student.prototype = Object.create(Person.prototype); //3 这个方法较好,但要ES5
Student.prototype.constructor = Student;
//对于1,2,3步骤有一个比较好的方法,自己封装一个Object.create()方法
if(!Object.create){ //4.
Object.create = function(proto){
function F(){}
F.prototype = proto;
return new F;
}
}
- 错的,因为子类有自己的方法,修改Student不能同时也把Person改掉
- 调用构造函数方法,也能将Student。prototype指向一个原型为Person.prototype的对象,但是这个方法当构造函数要传入参数的时候会有问题,因为我们还没有实例化呢,你参数要传入什么呢?对吧~
- 较为理想,Student的原型是个空的对象,这样修改就不会改到Person,But ES5采用。所以我们自己封装了一个4.
- 在ES5之前,模拟一个Object.create(),原理是模拟一个空函数,并且把这个空函数的prototype属性赋值为proto这个我们想作为原型的对象,用
return new F
这样呢就创建出一个空对象,这个对象的原型指向proto这个参数。
题目描述:
请给Array本地对象增加一个原型方法,它用于删除数组条目中重复的条目(可能有多个),返回值是一个包含被删除的重复条目的新数组。
Array.prototype.distinct = function() {
var ret = [];
for (var i = 0; i < this.length; i++)
{
console.log(this);
for (var j = i+1; j < this.length;) {
if (this[i] === this[j]) {
var tmp = this.splice(j, 1);
//console.log(tmp)
ret.push(tmp[0]);
} else {
j++;
}
}
}
return ret;
}
//for test
console.info(['a','b','c','d','b','a','e'].distinct())