OOP:Object Oriented Programming 面向对象编程。
题外话:面向对象的范围实在太大,先把这些大的东西理解理解。
根据高程和权威指南上的定义,对象是一组没有特定顺序的值,咱们能够把对象当作是从字符串到值的映射。
原型:编程
根据权威指南上的定义:每个js对象(null除外)都和另外一个对象相关联, “另外一个”对象就是咱们熟知的原型,每个对象都从原型上继承属性。原型也是对象。 通俗点讲,就是一个拥有prototype属性、且这个属性指向函数的原型对象的对象。
原型的好处就是: 原型上的方法和属性会被全部实例所共享。
原型链:app
当访问某个实例属性或方法时,会先自身对象中查找,查不到时再往当前对象原型上找; 若依然没找到,则会继续往原型对象的原型上找,一直到找到结果或者找到Object.prototype为止也没找到, 而后这时就会返回undefined,这么一个链式查找过程造成的结构就叫原型链。
每一个对象都有一个__proto__属性,函数也是对象,因此函数也有;
每一个函数都有一个prototype属性,而实例对象没有。
封装,继承,多态(指一个方法能够有多种调用方式:例若有参或无参)
let obj={};
函数
let obj=new Object();
this
let obj=Object.create({}/null);
spa
注意!!前面这三种方式的的构造函数都是Object,而Object已是原型链的最顶端了,因此Object.prototype都为undefined。能够用实例的__proto__.constructor查看构造函数是否指向Object构造函数。prototype
function person(name,job){ let o={}; o.name=name; o.job=job; o.sayName=function(){ console.log(this.name); } return o; } let p1=person('nagi','sleep'); console.log(p1.constructor); // 指向Object p1 instanceof person; // false;
优缺点:指针
这种模式虽然解决了量产对象的问题,但却没法获知当前对象是何类型 (例如类型:Array,Math等内置对象,或者BOM(window)/DOM的宿主对象,又或者自定义对象等)
注意点:函数首字母不用大写。code
function Person(name,job){ this.name=name; this.job=job; this.sayName=function(){ console.log(this.name); } } let p1=new Person('nagi','sleep'); console.log(p1.constructor); // 指向Person p1 instanceof Person; // true
与工厂模式区别:对象
a.没有显式建立对象; b.直接将属性和方法赋给了this对象 c.不有return语句;
拓展:new操做符作了些什么?继承
a.建立一个新对象; b.将构造函数的做用域赋给新对象(所以this就指向一这个新对象); c.执行构造函数中的代码(为这个新对象添加属性); d.返回新对象(默认返回当前对象,除非有显示返回某个对象)
优缺点:
首先是解决了工厂模式中不能判断类型的问题; 但缺点是每实例一次的同时还会把方法从新建立一遍,形成内存资源浪费; 其次,因构造函数与其它函数的惟一区别就是调用方式不同,因此当被看成普通函数调用时,其内部的this会指向全局,引起做用域问题。
function Person(){}; // 写法一: Person.prototype.name='nagi'; Person.prototype.job='sleep'; Person.prototype.sayName=function(){ console.log(this.name); }; // 写法二:注意,这种直接更改原型指向的写法,会改变constructor指向,指向Object构造函数 /* Person.prototype={ // constructor:Person, name:'nagi', job:'sleep', sayName:function (){ console.log(this.name) } }*/ let p1=new Person();
优缺点:
优势:解决了上述构造函数的缺点,原型对象上的属性和方法为全部实例所共享。 缺点: a.缺点也是全部实例共享方法和属性,所以其中一个实例更改了引用类型的属性值时,其余的实例也会被迫改变。(属性) b.默认状况下全部实例都取得相同的属性值。
function Person(name,job){ this.name=name; this.job=job; } Person.prototype.sayName=function(){ console.log(this.name); } let p1=new Person('nagi','sleep');
function Parent(){ this.name='nagi'; this.colors=['red','blue','green']; } Parent.prototype.sayName=function(){ console.log(this.name); } function Child(){ this.job='sleep'; }; Child.prototype=new Parent(); // 要注意:这种重写原型链的写法是会切断构造函数与最初原型之间的联系的, // 意味着此时Child.prototype.constructor指向Parent var child=new Child();
这种方式的基本思想就是利用原型让一个引用类型继承另外一个引用类型的属性和方法。
问题点:
a.上述例子中,经过原型继承方式继承至关于专门建立了一个Child.prototype.colors的属性, 由于引用类型的原型属性会被全部实例共享,也就意味着Child的全部实例都会共享colors这一属性, 当其中一实例修改它的值时,那么其余的实例的值也会跟着被改变。 b.在建立子类实例时,不能向超类型的构造函数中传递参数。鉴于此,实际不多单独用原型链继承。
超类型:好比Child类继承了Parent类的属性,那么Parent就是Child的超类(也叫父类)。
function Parent(name){ console.log(`子类传进的name值是:${name}`) this.name='nagi'; this.colors=['red','blue','green']; } Parent.prototype.sayName=function(){ console.log(this.name); } function Child(){ Parent.call(this,'Bob') this.job='sleep'; }; var child1=new Child(); var child2=new Child();
基本思想是在子类构造函数的内部调用超类(或父类)型构造函数。
优缺点:
优点:解决了原型链的两个问题; 缺点: a. 方法都在构造函数定义的话,那函数复用就无从谈起了。 b. 父类在原型中定义的方法,对于子类型来讲是不可见的。鉴于此,构造函数也不多用。[捂脸]
function Parent(name){ this.name=name; this.job='sleep'; this.colors=['red','blue','green']; } Parent.prototype.sayName=function(){ console.log(this.name); } function Child(name){ Parent.call(this,name); this.job='eat'; } Child.prototype=new Parent(); Child.prototype.constructor=Child; let child1=new Child('nagi'); let child2=new Child('Bob');
其实现思路是用原型链实现对原型属性和方法的继承,而借助构造函数实现对实例属性的继承。 优势:在前二者基础上,补足了构造函数和原型链的缺点,是比较经常使用的方式。
function Parent(name){ this.name=name; this.job='sleep'; this.colors=['red','blue','green']; } Parent.prototype.sayName=function(){ console.log(this.name); } function Child(){ Parent.call(this); // 保证构造函数指针指向Child } Child.prototype=Object.create(Parent.prototype,{name:{value:'nagi'},job:{value:'eat'}}) // 若是想同时继承多个,还可以使用Object.assign()添加属性 // Object.assign(A.prototype, B.prototype); let child=new Child();
类继承,class A extends B