主要也是记录下自己的粗浅地理解的过程,顺便加深一下印象。
javascript语言和其他语言的面向对象不太一样。
其他语言的面向对象基本有1.类(类型)。2.实例(实际创建的对象)
而javascript不区别类和 实例 ,只通过原型prototype来实现面向对象编程。
方式一:没有类,但可以通过现成的对象来创建具体对象。
var Student = { name :'robot', height:1.2, run:function(){ console.log(this.name+' is running'); } };
再用Student 来创建学生小明xiaoming
var Student = { name: 'Robot', height: 1.2, run: function () { console.log(this.name + ' is running...'); } }; var xiaoming = { name: '小明' }; xiaoming.__proto__ = Student;//关键 这样小明就继承了Student 但是廖雪峰不推荐直接用obj.__proto__去改变一个对象的原型
如果把xiaoming的原型指向其他对象:
var Bird = { fly: function () { console.log(this.name + ' is flying...'); } }; xiaoming.__proto__ = Bird;
现在xiaoming已经无法run()了,他已经变成了一只鸟:
xiaoming.fly(); // 小明 is flying...
说一下高级一点的方法 :Object.create()方法可以传入一个原型对象,并创建一个基于该原型的新对象,但是新对象什么属性都没有,因此,我们可以编写一个函数来创建xiaoming
// 原型对象: var Student = { name: 'Robot', height: 1.2, run: function () { console.log(this.name + ' is running...'); } }; function createStudent(name) { // 基于Student原型创建一个新对象: var s = Object.create(Student); // 初始化新对象: s.name = name; return s; } var xiaoming = createStudent('小明'); xiaoming.run(); // 小明 is running... xiaoming.__proto__ === Student; // true
以上的例子使用Object.create()来用实例创建对象就避免之前用object.__proto__=Student 来指向实例方式。
在来说一下方式二,在介绍方式二之前,我们先了解一下:
JavaScript对每个创建的对象都会设置一个原型,指向它的原型对象。
当我们用obj.xxx访问一个对象的属性时,JavaScript引擎先在当前对象上查找该属性,如果没有找到,就到其原型对象上找,如果还没有找到,就一直上溯到Object.prototype对象,最后,如果还没有找到,就只能返回undefined。
例如,创建一个Array对象:
var arr = [1, 2, 3];
其原型链是:
arr ----> Array.prototype ----> Object.prototype ----> null
Array.prototype定义了indexOf()、shift()等方法,因此你可以在所有的Array对象上直接调用这些方法。
注意:在看完方式一的时候,我看到上面廖雪峰说的是arr--> Array.prototype-->Object.prototype--->null (我去 这个是什么意思) 不是应该是arr.__proto__ -->Array这样才合理嘛? 我一开始也有点被绕晕。不过我看完后面大概有点明白。 方式一是以实例来创建实例,而方式二是以构造函数来创建实例,它是更高级的一种方式(如果我没理解错上面这个原型链表示指向就是按构造函数的方式来描述的)。话不多说,先来看看方式二。
方式二:构造函数
function Student(name) { this.name = name; this.hello = function () { alert('Hello, ' + this.name + '!'); } }// 其实就是一个普通函数 只不过函数体的内容有些不一样
但是在JavaScript中,可以用关键字new来调用这个函数,并返回一个对象:
var xiaoming = new Student('小明'); xiaoming.name; // '小明' xiaoming.hello(); // Hello, 小明!
注意,如果不写new,这就是一个普通函数,它返回undefined。但是,如果写了new,它就变成了一个构造函数,它绑定的this指向新创建的对象,并默认返回this,也就是说,不需要在最后写return this;。
新创建的xiaoming的原型链是:
xiaoming ----> Student.prototype ----> Object.prototype ----> null
看到上面的的这条原型链是不是和我们刚开始说方式二的时候那条arr的差不多
也就是说,xiaoming的原型指向函数Student的原型。如果你又创建了xiaohong、xiaojun,那么这些对象的原型与xiaoming是一样的:
xiaoming ↘ xiaohong -→ Student.prototype ----> Object.prototype ----> null xiaojun ↗
请注意这个时候xiaoming.__protype__已经不是指向Student本身了 ,而是指向Student.prototype, 你可以把Student.prototype当做一个Student老板的请来的管家,是方式一的一种升级。
用new Student()创建的对象还从原型上获得了一个constructor属性,它指向函数Student本身:
xiaoming.constructor === Student.prototype.constructor; // true 创建的实例都有一个共同的标志代表constructor:我们和管家都是Student老板的人 Student.prototype.constructor === Student; // true 管家有个constructor标志代表他对老板Student负责 Object.getPrototypeOf(xiaoming) === Student.prototype; // true 我们有什么事都找管家而不是老板 xiaoming instanceof Student; // true 我们都是老板Student的员工
现在我们就认为xiaoming、xiaohong这些对象“继承”自Student。
不过还有一个小问题,注意观察:
xiaoming.name; // '小明' xiaohong.name; // '小红' xiaoming.hello; // function: Student.hello() xiaohong.hello; // function: Student.hello() xiaoming.hello === xiaohong.hello; // false
xiaoming和xiaohong各自的name不同,这是对的,否则我们无法区分谁是谁了。
xiaoming和xiaohong各自的hello是一个函数,但它们是两个不同的函数,虽然函数名称和代码都是相同的!
如果我们通过new Student()创建了很多对象,这些对象的hello函数实际上只需要共享同一个函数就可以了,这样可以节省很多内存(看到这里大家应该明白了,这也就是为什么要Student老板要引入管家prototype的原因了)。
要让创建的对象共享一个hello函数,根据对象的属性查找原则,我们只要把hello函数移动到xiaoming、xiaohong这些对象共同的原型上就可以了,也就是Student.prototype:
修改代码如下:
function Student(name) { this.name = name; } Student.prototype.hello = function () { alert('Hello, ' + this.name + '!'); };
在来把代码提升包装一下 :
包装的原因:
如果一个函数被定义为用于创建对象的构造函数,但是调用时忘记了写new怎么办?
在strict模式下,this.name = name将报错,因为this绑定为undefined,在非strict模式下,this.name = name不报错,因为this绑定为window,于是无意间创建了全局变量name,并且返回undefined,这个结果更糟糕。
所以,调用构造函数千万不要忘记写new。为了区分普通函数和构造函数,按照约定,构造函数首字母应当大写,而普通函数首字母应当小写,这样,一些语法检查工具如jslint将可以帮你检测到漏写的new。
最后,我们还可以编写一个createStudent()函数,在内部封装所有的new操作。一个常用的编程模式像这样:
function Student(props) { this.name = props.name || '匿名'; // 默认值为'匿名' this.grade = props.grade || 1; // 默认值为1 } Student.prototype.hello = function () { alert('Hello, ' + this.name + '!'); }; function createStudent(props) { return new Student(props || {}) }//这样就包装起来了 总结一下构造函数的三个步骤 // 1.首先定义个构造函数注意首字母大写 function Student(props){ this.name = props.name||'unnamed'; this.grade = props.grade || 1; // this.say = function(){ // console.log('hey,i\'m '+this.name +' what\'s your name'); // }; } // 2.将构造函数的公共方法提取出来 Student.prototype.say = function(){ console.log('hey,i\'m '+this.name +' i\'m in grade '+this.grade+' what\'s your name'); }; // 3.包装new关键字 function createStudent(props){ return new Student(props ||{}); } var xiaoming = createStudent({name:'xiaoming',grade:2}); xiaoming.say(); var xiaohong = createStudent(); xiaohong.say();
【关键:
1.JavaScript不区分类和实例的概念,而是通过原型(prototype)来实现面向对象编程;
2.xiaoming.__proto__ = Student;//直接指向来进行原型继承不推荐
3.Object.create()方法可以传入一个原型对象,并创建一个基于该原型的新对象,但是新对象什么属性都没有
4.JavaScript对每个创建的对象都会设置一个原型,指向它的原型对象
5.如果方法函数都在构造函数内定义,new 的时候创建出来对象内的函数都是独立一份,这时候虽然创建的对象都会指向同一个原型Stduent.prototype ,但是prototype 里面是空空如也,(就是说没有给管家权利)所以需要给管家赋权,要把公共的处理放到Stduent.prototype对象。
】