搞懂javascript面向对象之基础篇

107 阅读6分钟
  • 面向对象:将数据和处理数据的程序封装到对象中

对象创建的方式

  • 字面量方式
var obj={}
  • 通过构造函数
let obj=new Object()
  • 通过Object.creat()
var obj=Object.creat({x:1})

面试题:模拟实现Object.creat()方法
基本思路:属性和方法放在新创建出来的对象的原型__proto__上面

function creat(obj){
	var f=function(){}
	f.prototype=obj
	return new f()
}

var obj1=creat({x:1});
console.log(obj1)


Object.creat()的使用技巧

  • 通过Object.create()方法创建空对象时,对象是没有原型属性的
  • Object.create() 用第二个参数来创建非空对象的属性描述符默认是为false的
var o=Object.create({},{p:{value:2}});
console.log(o);
Object.getOwnPropertyDescriptors(o);

o.p=22;
console.log(o.p)  //2(不可重写)

  • 而构造函数或字面量方法创建的对象属性的描述符默认为true。

对象的调用方式

  • 点和中括号调用
var str='name';
var obj={
	str:'小明',
	[str]:'小花', //中括号里面还可以接收字符串连接
	age:18
}

console.log(obj['name']) //'小花'
console.log(obj['str']) //'小明'
var evage='age'
console.log(obj[evage]) //18

工厂模式vs构造函数模式

  • 工厂模式
function Person(name,age,hobby){
    let obj = {}; // 添加原料
    obj.name = name;
    obj.age = age;
    obj.hobby = function(){
        console.log(hobby);
    }
    // 加工原料
    return obj; //出厂;
}

let zhangsan  = Person("张三",20,"喜欢篮球");
let lisi  = Person("李四",21,"喜欢足球");
console.log(zhangsan);
console.log(lisi);

  • 构造函数模式

1.自动创建一个空对象

2.把空对象和this绑定

3.如果没有返还,隐式返回this

function Person(name,age,hobby){
    //let obj = {};
    //自动创建一个空对象把空对象和this绑定
    
    this.name = name;
    this.age = age;
    this.hobby = function(){
        console.log(hobby);
    }
    
    
    // 加工原料
    //如果没有返还,隐式返回this
    //return obj; //出厂;
}

let zhangsan  =new Person("张三",20,"喜欢篮球");

function test(){
	console.log('test')
}

new test() //会直接执行函数

//this指向实例化对象(这里是指向zhangsan)
function Person(name,age,fv){
    this.name=name,
    this.age=age,
    this.fv=fv
}

Person.prototype.hobby=function(){
    console.log(`我喜欢${this.fv}`)
}


//通过new元素运算符实例化出一个对象
var zhangsan=new Person('张三','20','篮球')
console.log(zhangsan)
console.log(zhangsan.hobby())

认识构造函数中的静态属性方法

function Person(name,age,fv){

	var privary=1,
    this.name=name,
    this.age=age,
    this.fv=fv
}

Person.prototype.hobby=function(){
    console.log(`我喜欢${this.fv}`)
}

//静态私有属性(函数是一等对象)
Person.num=0  
Person.fn=function(){
	console.log('我是静态私有方法')
}


//通过new元素运算符实例化出一个对象
var zhangsan=new Person('张三','20','篮球')
console.log(zhangsan)
console.log(zhangsan.hobby());



//每一实例化出来的对象都在内存中新引用了一块内存地址
//不同实例化对象引用对象中的方法时引用的都是不同的内存地址
//解决方法是通过提供一个公共的空间(原型)去存放我们相同的方法,可以节约内存
var lisi=new Person('lisi','22','篮球');
console.log(lisi);

  • 通过构造函数创建对象
let obj=new Object();
obj.name='zhangsan';
obj.age='20';
obj.hobby=function(){
	console.log('喜欢篮球')
}
console.log(obj)

原型

解决性能问题,节约内存空间

function Person(name){
	this.name=name;
	this.age=20
	//this.hobby=function(){console.log('喜欢篮球')}
}

//原型里面的this也会指向实例化对象
Person.prototype.hobby=function(){console.log('喜欢篮球')}

let zhangsan=new Person('张三')
let lisi=new Person('李四')

//hobby方法写在了原型上,不会每次在实例化对象的时侯为里面的方法肚子开辟一块内存空间
console.log(zhangsan.hobby===lisi.hobby) //true


console.log(zhangsan.__proto__===Person.prototype )  //true


//原型的固有属性constructor
console.log(Person.prototype.constructor=Person)//true


console.log(console.log()zhangsan.constructor===Person) //true

  • 注意事项:原型上constructor属性被覆盖的问题
Person.prototype={
	hobby:function(){
		console.log('hobby')
	}
}


console.log(zhangsan.constructor===Person) //false


这种添加原型方法会覆盖掉原型上的固有属性constructor
但可以手动添加constructor属性

Person.prototype={
	constructor:Person,
	hobby:function(){
		console.log('hobby')
	}
}
  • 引申---通过constructor判断实例化对象的类型
let str=new String('abc');
console.log(str.constructor===String) //true

let str2='abc';
console.log(str2.constructor===String) //true

原型、构造函数、实例化对象的关系

//tmp、that用来验证this指向
let tmp;
let that;
function Person(name){
	this.name=name;
	this.age=20;
	tmp=this
}

Person.prototype.fn=function(){
	console.log('追加了一个原型方法')
	thhat=this
}



let zhangsan=new Person('张三')
console.log(zhangsan===this) //true


zhangsan.fn()
console.log(zhangsan===that) //true

结论:
不管是构造函数的this,还是原型上面的this,都是指向实例化对象


console.log(Person.prototype.constructor===Person) //true

工厂模式vs构造函数

工厂模式创建出来的对象没有解决对象识别的问题,即创建的所有实例都是object类型(不清楚是哪个对象的实例)

call、apply、bind---基于Function.prototype

  • 面试题:手写模拟实现bind方法
基础版本一:

Function.prototype.mybind=function(context){
    var self=this;
    return function(){
        return self.apply(context,arguments)
    }
};
var obj={name:'seven'};
var func=function(){
	console.log(this.name)
}.mybind(obj);

func()//seven

Function.prototype.mybinds=function(){
    var self=this;
    context=[].shift.call(arguments)
    args=[].slice.call(arguments)
    return function(){
        return self.apply(context,args)
    }
};
var obj={name:'lth'};
var funcs=function(a,b,c,d){console.log(this.name);console.log(a,b,c,d)}.mybinds(obj,1,2);
funcs() //'lth'  1 2 undefined undefined

Function.prototype.mybinds=function(){
    var self=this;
    context=[].shift.call(arguments)
    args=[].slice.call(arguments)
    return function(){
        return self.apply(context,[].concat.call(args,[].slice.call(arguments)))
    }
};
var obj={name:'lwl'};
var funces=function(a,b,c,d){console.log(this.name);console.log(a,b,c,d)}.mybinds(obj,1,2);
funces(3,4) // 'lwl' 1 2 3 4

  • 通过call实现父子继承
function Dad(name,age){
	this.name=name;
	this.age=age;
	this.money='100'
}


function Son(name,age){
	Dad.call(this,name,age);
	//Dad.apply(this,[name,age]);
	//Dad.bind(this)(name,age);
	this.sex='male'
}


let zhangsan=new Son('张三',20)
console.log(zhangsan.money) //'100'
console.log(zhangsan.sex) //'male'

问题一:

function Dad(name,age){
	this.name=name;
	this.age=age;
	this.money='100'
}

Dad.prrototype.fn=function(){
	console.log('通过call实现的父子继承能获取到父类原型上的方法吗?')
}


function Son(name,age){
	Dad.call(this,name,age);
	//Dad.apply(this,[name,age]);
	//Dad.bind(this)(name,age);
	this.sex='male'
}





let zhangsan=new Son('张三',20)
zhangsan.fn()  //报错(没有继承过来)


//除非通过显式的让两个原型相等
Son.prototype=Dad.prototype
zhangsan.fn() //成功输出


//利用Son.prototype=Dad.prototype所带来的影响:
//在此基础的代码上继续在子类Son的原型中添加同名fn方法时
Son.prototype.fn=function(){
	console.log('这是Son的fn方法')
}

//先在子类身上调用fn方法可以正常输出
zhangsan.fn() //'这是Son的fn方法'


//父类的原型方法被子类身上的同名原型方法fn影响了
let zhangyi=new Dad('张一'22)
zhangyi.fn(); //这是Son的fn方法'

传值和传址引出的深拷贝方案

  • 复杂数据类型:传址问题
  • 简单数据类型:传值
let DadProto={
	name:'zhangsan'
}

SonProto=DadProto
SonProto.name='lisi'
console.log(SonProto.name) //'lisi'
console.log(DadProto.name) //'lisi'

//引申到上面的Son.prototype=Dad.prototype问题
  • 面试题:手写深拷贝方法
let DadProto={
	name:'zhangsan'
}

//深拷贝一:缺点是拷贝后会丢失原对象的所有方法和值为undefined的属性
SonProto=JSON.parse(JSON.stringify(DadProto))
SonProto.name='lisi'
console.log(SonProto.name) //'lisi'
console.log(DadProto.name) //'zhangsan'

//深拷贝二:
const obj1={
  age:20,
  name:'lth',
  address:{
  	city:'xiamen'
  },
  arr:['l','t','h'],
  test:function(){console.log('11')}
}

function deepClone(obj){
	
    //obj==null 判断了null和undefined两种情况
    //还判断了是'function'的情况
	if(typeof obj != 'object' ||  obj==null){
    	return obj;  //这三种情况可以直接返回原值
    }
    
    let result; //接下来还得继续判断是对象还是数组
    if(obj instanceof Array){
    	result=[]
    }else{
    	result={}
    }
    
    for(let key in obj){
    	if(obj.hasOwnProperty(key)){
        	result[key]=deepClone(obj[key])   
        }
    }
    
    return result;

}

const obj2=deepClone(obj1)

//深拷贝三:
function deepClone(obj){
	let newObj=Array.isArray(obj)?[]:{};
	for(let key in obj){
		if(obj.hasOwnProperty(key)){
			if(typeof obj[key]==='object'){
				newObj[key]=deepClone(obj[key])
			}else{
				newObj[key]=obj[key]
			}
		}
	}
	return newObj;
}

利用深拷贝的方式解决原型与原型直接赋值的问题

利用深拷贝可以解决上面的Son.prototype=Dad.prototype问题


function Dad(name,age){
	this.name=name;
	this.age=age;
	this.money='100'
}

Dad.prrototype.fn=function(){
	console.log('通过call实现的父子继承能获取到父类原型上的方法吗?')
}


function Son(name,age){
	Dad.call(this,name,age);
	//Dad.apply(this,[name,age]);
	//Dad.bind(this)(name,age);
	this.sex='male'
}





let zhangsan=new Son('张三',20)
zhangsan.fn()  //报错(没有继承过来)



Son.prototype=deepClone(Dad.prototype)
//在此基础的代码上继续在子类Son的原型中添加同名fn方法时
Son.prototype.fn=function(){
	console.log('这是Son的fn方法')
}

//先在子类身上调用fn方法可以正常输出
zhangsan.fn() //'这是Son的fn方法'


//父类的原型方法被子类身上的同名原型方法fn影响了
let zhangyi=new Dad('张一'22)
zhangyi.fn(); //'通过call实现的父子继承能获取到父类原型上的方法吗?'

组合继承

function Dad(name,age){
	this.name=name;
	this.age=age;
	this.money='100'
}

Dad.prrototype.fn=function(){
	console.log('通过call实现的父子继承能获取到父类原型上的方法吗?')
}


function Son(name,age){
	Dad.call(this,name,age);
	//Dad.apply(this,[name,age]);
	//Dad.bind(this)(name,age);
	this.sex='male'
}





let zhangsan=new Son('张三',20)
zhangsan.fn()  //报错(没有继承过来)


//利用中间件
let Link=function(){}
Link.prototype=Dad.prototype;
//问题一:直接赋值Son.prototype会导致Son的constructor被覆盖
//Son.prototype.__proto__===Link.prototype===Dad.prototype
Son.prototype=new Link();
Son.prototype.constructor=Son;