Javascript——面向对象程序设计和继承
面向对象设计模式
创建对象——单个对象
- 字符字变量
- new 操作符
- 使用create
//字符自变量
var a ={
name:"wang"
}
//使用new操作符,构造函数模式
var b = new Object()
//使用create
//创建一个具有指定原型且可选择性地包含指定属性的对象。
var c = Object.create(Object.prototype)
创建对象——多个对象
工厂模式
创建对象的过程用一个函数包裹起来——创建多个相似对象
function createPerson(name,age){
var o = new Object()
o.name = name
o.age = age
o.sayName = function(){
alert(this.name)
}
return o
}
var person1 = createPerson(wang,18)
优点:快速创建多个相似对象
缺点:就是普通对象,无法识别对象的类型
构造函数模式
function Person(name,age){
this.name = name
this.age = age
this.sayName = ()=> {alert(this.name)}
}
var person1 = new Person(wang,18)
var person2 = new Person(li,12)
/instanceof:判断变量是否属于构造函数的实例
person1 instanceof(Person) //true
person1 instanceof(Object) //true
/constructor:指向实例的构造函数
person1.constructor //Person
person2.constructor //Person
Person.call(o)
优点:解决工厂模式缺点
- constructor,工厂模式创造的对象的construct是object,构造函数创建的对象的construct是该构造函数
- 将构造函数当做普通函数使用:这个时候的this指向了全局作用域
缺点:对象的属性和方法都是独一无二的,占内存。如sayname等方法功能完全一样,却在每个对象上实例化
原型模式
每一个函数都有prototype,指向当前函数的原型对象
原型对象.constructor=当前函数
函数.prototype = 原型对象
function Person(){
//空函数
}
Person.prototype.name = wang
Person.prototype.age = 18
Person.prototype.sayName = () => {alert (this.name)}
var person1 = new Person()
person1.sayName() //wang
var person2 = new Person();
person2.sayName() //wang
alert(person1.sayName == person2.sayName) //true
person1.__proto__ //实例对象的构造函数的原型对象
缺点:所有的实例都共用相同的属性和方法,person1和person2互相影响
删除属性:delete person1.name;仅能删除自由属性,无法删除原型属性
同名屏蔽:普通属性和方法——原型对象的属性和方法
-
getPrototypeof——获得对象的原型
var proto = Object.getPrototypeOf(Person) //proto就是Person的原型对象
-
hasOwnProperty——判断参数是否是对象上的自由属性
var obj = { name:wang, age:12 } obj.hasOwnProperty("name")
for - in -——枚举对象上的所有属性(还会顺着原型链查找可枚举属性)
Object.keys(对象)——返回一个数组(所有属性的name)
Object.getOwnPropertyNames()——返回一个数组,由指定对象的所有自身属性的属性名
自定义属性和方法都是可枚举的,Object的原型中所有方法不可枚举,constructor es3可枚举,es5不可枚举
-
Person.prototype = { } 将原型对象直接指向一个新对象,Person.prototype.constructor===Object,为了保险应该将Person.prototype.constructor=Person
一般不建议如此做
//原型和实例的动态关系 var friend = new Person() Person.prototype.sayHi = function(){ alert("hi") } friend.sayHi() //"hi" 运行时先看自由属性,再看原型对象上的属性 function Person(){} var friend = new Person() //此时已经定型,friend的只有sayHi的方法 Person.prototype = { name:"wang" sayName:()=>alert(this.name) } friend.sayName() //error 此时friend指向的还是原来的原型对象
组合模式
伪经典模式——构造函数+原型
function Person(name,age){
this.name = name
this.age = age
}
Person.prototype = {
constructor : Person,
sayName : ()=>alert(this.name)
}
Person.prototype.sayName = ()=>alert(this.name)
重点
- 所有的函数上都有一个特殊的属性,prototype——原型对象,原型对象默认只有一个属性——constructor——指向构造函数
- proto对象构造函数的原型对象
javascript的继承
js中从技术上来讲并不存在继承,js是一个面向过程的语言,并不存在类(class),但是可以通过一些特殊的手段模拟继承
继承——
eg-1:玛丽,基类:跑,跳,吃金币,踩怪物;吃了不同的蘑菇,可以发子弹或者变大,但跑,跳功能也有
eg-2:
function Person(name,age){
this.name = name
this.age = age
}
function Student(name,age,schoolNumber){
this.name = name
this.age = age
this.schoolNumber
}
原型链
本质是重写子类的原型对象
function SuperPerson(name){
this.name = name;
}
SuperPerson.prototype.getName = ()=>{
return this.name
}
function Person(age){
this.age = age
}
//继承 本质是重写子类的原型对象
Person.prototype = new SuperPerson("wang") //将子类的原型对象 = 父类的一个实例
var person1 = new Person(12)
alert(person1.getName()) //wang
缺点:
- 为子类添加原型方法时,必须写在继承之前
- 无法实现多继承
- 不同实例互相影响,如下代码——当属性在父类的原型对象上时,是共享的
- 无法传递参数(最大的缺点)
function SuperType(){
this.colors = ["red"]
}
function SonType(){}
SonType.prototype = new SuperType() //继承,原型对象 = 父类实例
var color1 = new SonType()
color1.colors.push("black") //改变了原型对象,所有实例都会共享
var color2 = new SonType()
alert(color2.colors) //接收了改变后的值
借用构造函数
本质是父类的构造函数添加入子类中
优点:
- 传递参数
- 可实现多继承
- 解决了共享问题
-
传递参数
function SuperPerson(name){
this.name = name
}
function SonPerson(name,age){
SuperPerson.call(this,name)
this.age = age
}
var person1 = new SonPerson("wang",12)
-
互相不影响
function SuperType(){
this.colors = ['red']
}
function SonType(){
SuperType.call(this) //将父类的构造函数在子类中运行
}
var color1 = new SonType()
color1.colors.push("black")
var color2 = new SonType()
alert(color2.colors) //'red'
缺点:
- 方法都在构造函数中定义,都是独一无二的,函数无法复用
- 创建的实例只是子类的实例,不是父类的实例
- 只能继承构造函数中的属性和方法,不能继承原型上的属性和方法
- 无法复用,创造出来的每一个对象的属性和方法都是独一无二的
组合式继承
借用构造函数写属性,原型链写公共方法
function Father(name){
this.name = name //构造函数写属性
}
Father.prototype.sayName = function(){
alert(this.name) //原型对象上写方法
}
function Son(name,age){
SuperType.call(this,name) //继承父类构造函数中的属性
this.age = age
}
Son.prototype = new Father() //重写子类的原型对象
Son.prototype.constructor = Son //将子类原型对象的构造函数指向子类本身
Son.prototype.sayAge = function(){ //再给子类原型对象添加事件
alert(this.age)
}
var tt = new Son("wang",12)
优点:结合了前两种模式的优点,避免了其缺点,伪经典模式
缺点:
- 父类的构造函数被调用两次,call/new
- 同名覆盖,属性在原型和实例上各占一份,占用多余内存
原型式继承
var a = {
name:"wang"
}
var b = {
age:12
}
b.__proto__ = a
//__proto__ 在IE中支持不好
//创造一个跳板
function F(){}
F.prototype = a
var c = new F()
//原型式继承
function object(o){
function F(){}
F.prototype = o
return new F()
}
//写法二
Object.create(prototype, descriptors) //es5
//创建一个空函数
//让空函数的protetype==继承的对象
//使用new 操作符
缺点:看起来不像继承
寄生式继承
本质等同于原型式继承
完美继承——组合寄生式继承
解决组合式继承的两个问题
function Father(name){
this.name = name
}
Father.prototype.sayName = function(){
alert(this.name)
}
function Son(name,age){
Father.call(this,name) //父类构造函数仅运行了一次
this.age = age
}
var p = Object.create(Father.prototype)
p.constructor = Son
//Son.prototype = new Father()
//Son.prototype.__proto__ = Father.prototype
Son.prototype = p
ES6的class
//父类
class Person {
constructor(name) {
this.name = name;
}
say () {
console.log("say hi");
}
};
//子类
class SMan extends Person {
constructor (name, age) { //指向构造函数本身
super(name); //先运行父类的构造函数 this.name=name
this.age = age;
}
show () {
console.log(this.age);
}
}
var eg = new SMan("wang",12)
eg.show();
eg.say()
- 就是对组合继承的封装使用
其他
new 操作符的四个步骤
- 创建一个对象
- 将构造函数的this指向新创建的对象
- 实行函数里的所有代码
- 将创建的对象返回
改完之后和工厂模式一样
区别点:
- 没有显示创建对象,没有显式的return
- 构造函数的第一个字母大写(约定俗成)
- con
this的指向分类——都是指向对象
- 纯函数的调用——全局作用域(window)
- 作为对象的方法和属性调用——指向对象
- 作为构造函数调用——创建出来的新对象
- apply/call——强制指向一个对象
不确定this指向时,一般console.log(this)或打断点
深拷贝和浅拷贝——最简单的继承
继承就是把别人的属性拿过来,最简单的方法就是复制一份
JS五种基础类型,一种引用类型
浅拷贝
遍历复制一份
深拷贝
遍历递归复制一份
//非拷贝
var a = {
name:"wang"
}
var b = a
//浅拷贝
//此递归方法不包含数组对象
var a = {
a:1,
arr: [2,3]
}
var b = shallowCopy(a)
function shallowCopy(obj) {
var newobj = {};
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) { //判断自身属性
newobj[prop] = obj[prop];
}
}
return newobj;
}
//深拷贝
var a = {
a:1,
arr: [1,2],
nation : '中国',
birthplaces:['北京','上海','广州']
};
var b = {name:'杨'};
b = deepCopy(obj,b);
console.log(obj2);
//深复制,要想达到深复制就需要用递归
function deepCopy(o,c){
var c = c || {};
for(var i in o){
if(typeof o[i] === 'object'){
//要考虑深复制问题了
if(o[i].constructor === Array){
//这是数组
c[i] =[];
}else{
//这是对象
c[i] = {};
}
deepCopy(o[i],c[i]);
}else{
c[i] = o[i];
}
}
return c
}