- 面向对象:将数据和处理数据的程序封装到对象中
对象创建的方式
- 字面量方式
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;