面向对象基础
面向对象,指的是一种新的编程思路,不会改变预期的效果,会提高代码复用性,便于迭代
面向对象即将审视角度从总体转变为个体,从整体的框架转变为每个个体的对象
创建对象:
字面量方式:
let sex = "性别";
let obj = {
name:"小明",
sex:"男",
age:"17",
hobby:function(){
console.log("喜欢睡觉");
}
};
通过构造函数:
let obj = new Object();
工厂模式:
function Person(name,age,sex,fn){ //(形参) --> 运输材料
let obj = {}; //创建对象并赋值 --> 加工材料
obj.name = name;
obj.age = age;
obj.sex = sex;
obj.hobby = fn;
return obj; //出厂
}
let obj = new Person(); //通过new运算符,调用函数形成新的对象
new运算符
new运算符,也叫做new构造器, 配合使用的函数,被称为构造函数,函数名的首字母使用大写, 以此来和普通函数进行区分
作用:
- 执行该函数
- 隐式创建一个对象
- 把该对象和函数的 this指向 互相绑定
- 可以把this中的属性和方法都加到 该对象上
- 函数执行后,返回该对象
对象的使用:
通过点语法:
obj.name
通过中括号:
obj["nan"]
两种调用方式的区别:
如果通过点语法无法获取到对象中“正确”的属性/方法名,可以使用中括号的方式来获取
原型
每次通过构造函数去实例化对象,都会生成一个新的内存空间,项目开发中,往往大量的内存使用,会严重影响性能
解决方法: 将公有的方法,放到原型中,每次实例化对象,原型中的内容是公有的,不会再分配新的空间给对象 (构造函数中的属性/方法仍会)
// 原型
Person.prototype.s = function(){ //原型
console.log(this.sex);
}
let san = new Person("张三","男");
每调用一次person,都会使用一次原型
继承
继承:通过某种方式,可以让一个对象访问并使用另一个对象内容的行为
作用: 节省各个构造函数中,大量重复声明的属性和方法提高了开发效率,并节省了内存
构造函数的继承:
又称类式继承
优点 : 简约易使用
缺点 : 只可以继承(借用) 构造函数中的内容,无法继承(借用) 原型中的内容
直接给原型赋值一个对象,可以简化批量赋值。 但,等于重构了原型
重构原型时,需要手动创建constructor: 构造函数。 否则原型的指向链条会断
function Animal(name,sex){
this.name = name;
this.sex = sex;
}
Animal.prototype = {
constructor:Animal, //创建constructor 指回构造函数
};
- call: 对象冒充, 又叫做对象借用
function Tiger(name,age,sex){
Animal.call(this,name,sex);
this.age = age;
}
- apply: 作用和call 一致,都借用对象
function Person(name,age,sex){
let arr = [name,age,sex];
Tiger.apply(this,arr);
}
两者区别:
call接受参数时,数量根据被借者需要的参数而定
apply只有两个参数1.借用者 2.数组(这里存放着所有的参数)
原型链的继承
将父类的实例化对象赋值 给子类的原型
优点: 类似于复制了一个对象,构造函数和原型中所有的数据都可以获取到,简约方便使用
缺点:
1.原型链继承自身没有办法传参
2.父类所有的属性和方法都可以被所有子类共享
Son.prototype = new Parent();
组合继承
将类式继承与原型链继承相结合,互相弥补了缺点同时继承了两者优点
function Parent(x,y){ //父类
this.x = x;
this.y = y;
}
Parent.prototype.s = function(){
console.log("x"+"y")
}
function Son(x,y,z){ //子类
Parent.call(this,x,y) // 类式继承
this.z = z;
}
Son.prototype = new Parent(); //指定原型
let a = new Son(1,9,6)
传址和传值
传值: 传递简单的数值,新开辟内存(简单的数据类型)
传址: 传递了数据和地址,共享内存(复杂数据类型)
浅拷贝:
只拷贝数值不拷贝地址,节省内存,但会互相影响数据
let Parent = {
age:"70"
}
let Father = GroundPa;
深拷贝:
复制详细的数据与内存空间,不影响被复制的数据
- 方法一: 通过JSON数据转换
let father = JSON.parse(JSON.stringify(Parent))
此方式会返回一个新的对象,用来达成创建新地址的目的,但是会丢失function和undefind - 方法二: 使用deepCopy封装
function deep(obj){
let newObj = Array.isArray(obj)?[]:{};
for(let key in obj){ // for-in 会遍历对象和原型上 和 原型链的属性和方法,但是我们作深拷贝的时候,是不需要拷贝原型链
if(obj.hasOwnProperty(key)){ // 判断是否是对象自身的内容,如果是,才去做深拷贝
if(typeof obj[key] === "object"){ // 判断是否是对象(也就是复杂的数据类型)
newObj[key] = deep(obj[key]); // 如果内部依然有更深层的对象那么还需要按照原有步骤,再循环检测一遍
}else{ // 如果不是,只是简单数据类型
newObj[key] = obj[key] //传值
}
}
}
return newObj; //最后将检测赋值后的新对象返回
}
操作符
- **instanceof:**判断一个实例对象 是否是某个函数的实例
对象名 instanceof 函数名
例:son instanceof Panrets
- isPrototypeof( ): 判断是否是由该原型链产生的原型(从该圆形向上逐层寻找)
原型.isPrototypeOf(对象名)
例:Son.prototype.isPrototypeOf(fun)
- hasOwnProperty( ): 用来检测当前对象中是否有该属性(只会去查找构造函数中的内容,不会检查原型和原型链上的内容)
对象名.hasOwnProperty( " 属性/方法名" );
例:func.hasOwnProperty("name")
- delete: 用以删除对象属性
delete 对象名.属性名
例:delete func.name
- in: 检查该对象是否包含指定属性(这个属性可以是对象的直接属性,也可以是prototype继承而来的属性.但是原型自身的属性/方法不行,因为原型链继承,重构原型,覆盖掉了)
属性/方法名 in 对象名
例:"name" in func