- 对象的创建
- 工厂模式
- new运算符
- 构造函数
- 原型prototype
- 构造函数继承
- 原型的继承
- 原型链
- 包装对象
- 面向对象和面向过程编程
- 类和对象概念
面向对象编程思想(理解)主要在“程序设计思维”
一、面向过程:注重解决问题的步骤,分析问题需要的每一步,实现函数依次调用;
二、面向对象:是一种程序设计思想。将数据和处理数据的程序封装到对象中;
三、面向对象特性: 抽象、 继承、封装、多态
优点:提高代码的复用性及可维护性;
<script>
// 小明去餐厅吃饭:
// 面向过程:1.小明走去餐厅看菜单点餐吃饭;
// 面向对象:1.小明(走、看、点餐、吃); 2.餐厅(菜单);
// 研究对象间的关系:小明.走餐厅.菜单 小明.看 小明.点餐 小明.吃;
</script>
对象
Javascript 是一种基于对象的语言,几乎所有东西都是对象;
1、对象创建方法:
- 字面量创建
- new Object()创建
- Object.create()创建:创建对象的原型;
<script>
// 对象创建
// 1.字面量方式
let str = 'name';
let obj = {
[str]:"张三",//[str]可以是表达式构成
age:"20",
hobby:function(){
console.log("喜欢篮球")
}
}
// 2.构造函数;
let obj = new Object();
obj.name = "张三";
obj.age = 20;
obj.hobby = function(){
console.log("喜欢篮球")
}
console.log(obj);
// 3.Object.create() ;属性方法放在原型上;
let obj = Object.create({
name:"张三",
age:20,
hobby(){
console.log("喜欢篮球");
}
})
console.log(obj)
// 对象的调用;
console.log(obj.name);
obj.hobby();
console.log(obj['name']);
// 以上对象中.name和中括号调用的区别
let str = "name";
// console.log( obj.str);//错误
console.log( obj[str]);
</script>
工厂模式
一、工厂模式
工厂模式解决了代码复用的问题;
<script>
// let zhangsan = {
// name:"张三",
// age:20,
// hobby(){
// console.log("喜欢篮球");
// }
// }
// let lisi = {
// name:"李四",
// age:21,
// hobby(){
// console.log("喜欢足球");
// }
// }
// 以上的写法:冗余
// 解决冗余(提高代码复用性,冗余少):工厂模式(类:不是代表哪一个,是代表某一类)
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);
</script>
对象和类
一、对象:具体的某个事物;(如:小明、叮当猫)
二、类:一类事物的抽象;(如:人类、猫类)
new运算符
- new的特点:
1.new执行函数
2.自动创建空对象;
3.this绑定到空对象;
4 隐式返还this; - 通过new来改造工厂模式
<script>
// let str = "";
// let str = new String();
// 1.执行函数;2.自动创建一个空对象; 3.把空对象和this绑定 4.如果没有返还,隐式返还this;
// function test(){
// console.log("test");
// }
// 执行函数
// test();
// new test();
// new test;
// function Test(){
// // let obj = {}; === this;
// // return this;
// }
// new Test();
// 工厂模式;
function Person(name,age,hobby){
// let obj = {}; // 添加原料 === this; 创建一个空对象
// 把空对象和this绑定
this.name = name;
this.age = age;
this.hobby = function(){
console.log(hobby);
}
// 加工原料
// return obj; //出厂;隐式返还this
}
let zhangsan = new Person("张三",20,"篮球");
console.log( zhangsan.name);
zhangsan.hobby();
</script>
构造函数
- 构造函数要通过new来调用、 this指向实例化对象
- 约定俗成构造函数首字母大写
- 静态属性及方法
- 静态方法里的this;
类是泛指(抽象)
对象是特指
<script>
// 构造函数;1.首字母大写;2.this指向实例化对象;
function Person(name){
// this.num = 0;
this.name = name;
this.age =20;
this.hobby = function(){
console.log("喜欢篮球");
}
}
// 静态成员;
Person.num = 0;
Person.fn = function(){
console.log("fn");
}
// new : 实例化;
let zhangsan = new Person("张三");
Person.num++;
let lisi = new Person("李四");
Person.num++;
console.log(Person.num);// 静态属性和方法;(属于类本身的);
</script>
构造函数性能
- 公共空间存放公共方法
<script>
function Person(name){
this.name = name;
this.age = 20;
this.hobby = function(){
console.log("喜欢篮球");
}
}
let zhangsan = new Person("张三");
let lisi = new Person("李四");
console.log(zhangsan.hobby===lisi.hobby);//false
//在内存内分配了两个地址,张三一个,李四一个,(内存占用)
</script>
构造函数原型
prototype原型(解决了性能空间,内存问题)
- 通过new实例化出来的对象其属性和行为来自两个部分,一部分来自构造函数,另一部分来自原型。
- 当声明一个函数的时候,同时也声明了一个原型 。
- 原型本身是一个对象。
- 对象属性方法查找规则;
<script>
function Person(name){
this.name = name;
this.age = 20;
// this.hobby = function(){
// console.log("喜欢篮球");
// }
}
// 功能空间原型;
Person.prototype.hobby = function(){
console.log("喜欢篮球");
}
Person.prototype.fn = function(){
console.log("fn");
}
// 不能这样写,已覆盖掉prototype ,无固有属性,需自己写上constructor
Person.prototype = {
constructor:Person,
hobby:function(){
console.log("hobby");
}
}
let zhangsan = new Person("张三");
let lisi = new Person("李四");
console.log(zhangsan.hobby===lisi.hobby);//true,这样存放在prototype空间内无需分配内存地址,同一地址-->true
// 实例化对象zhangsan的原型与Person的原型是同一个东西,只是表现形式有所不同
console.log(zhangsan.__proto__===Person.prototype);
// 原型的固有属性 === 构造函数
console.log( Person.prototype.constructor===Person);
console.log(zhangsan.constructor===Person);
// console.log(zhangsan);
// constructor作用:找到实例化对象是通过哪个构造函数来实例的(实例化对象的指向问题);通过constructor判断类型,判断指向
// 判断类型;
// let str = new String("abd");
// let str = "abc";
// console.log(str.constructor===String);true
</script>
原型构造函数及对象关系
<script>
let temp;
function Person(name){
this.name = name;
this.age = 20;
}
Person.prototype.fn = function(){
console.log("fn");
temp = this;
}
console.log( Person.prototype.constructor===Person);
let zhangsan = new Person("张三");
zhangsan.fn();
// console.log(zhangsan===temp)
// 构造函数Person内的this等于实例化对象
// 原型prototype的this等于实例化对象
</script>
工厂模式对比构造函数
1.但是却没有解决对象识别的问题。即创建的所有实例都是Object类型。(不清楚是哪个对象的实例)(无法判断工厂模式的指向问题,构造函数可以)
2.没有原型,占用内存。(构造函数节约性能,用的多一些)
<script>
// 工厂模式
function Person(name) {
let obj = {};
obj.name = name;
obj.age = 20;
obj.fn = function() {
console.log("fn..");
}
}
let zhangsan = Person("张三");
// console.log(zhangsan.constructor===Person);
// 构造函数;
// function Person(){
// this.name = name;
// this.age = 20;
// }
// Person.prototype.fn = function(){
// console.log("fn...");
// }
// let zhangsan = new Person("张三");
// console.log( zhangsan.constructor===Person);
let str = "abc";
console.log(str.constructor === String);
</script>
原型链
对象之间的继承关系,在JavaScript中是通过(原型对象)prototype对象指向父类对象,直到指向Object对象为止,这样就形成了一个原型指向的链条,称之为原型链;
1.当访问一个对象的属性或方法时,会先在对象自身上查找属性或方法是否存在,如果存在就使用对象自身的属性或方法。如果不存在就去创建对象的构造函数的原型对象中查找 ,依此类推,直到找到为止。如果到顶层对象(Object)中还找不到,则返回 undefined。
2.原型链最顶层为 Object 构造函数的 prototype 原型对象,给 Object.prototype 添加属性或方法可以被除 null 和 undefined 之外的所有数据类型对象使用。
<script>
// 构造函数
function Foo(name) {
this.name = name;
this.age = 20;
this.test = "你好111";//构造函数的属性
}
//原型对象;
Foo.prototype.fn = function() {
console.log("f");
}
Foo.prototype.test = "hello";//原型对象的属性
Object.prototype.test = "你好222";//原型链顶层的属性
let newFoo = new Foo("张三");
console.log(newFoo.test);// 先查找构造函数内是否有test,再查找原型对象内是否有test,最后到原型链顶层查找受否有test,都无则返回undefined
// let obj = new Object();
console.log(Object.prototype.__proto__);//查找原型链顶层
</script>
构造函数继承(类继承)
- 继承:子类继承父类所有属性和行为,父类不受影响。
- 目的:找到类之间的共性精简代码
- call,apply,bind------改变this指向
- call()-----第一个参数是改变this指向,剩下参数是传参,call接收多个参数
- apply()-----第一个参数是改变this指向,剩下参数是传参,apply接收数组
- bind()-----第一个参数是改变this指向,bind返回新的函数,再次执行函数
// 构造函数继承
function Person(name){
this.name = name;
this.eyes = "两只";
this.legs = "两条";
}
function Student(name){
Person.call(this,name)
this.className = "二班";
}
let newPerson = new Student("张三");
console.log(newPerson.className);
-
简单原型继承,出现影响父类的情况;
function Person(name){ this.name = name; this.eyes = "两只"; this.legs = "两条"; } function Student(name){ Person.call(this,name) this.className = "二班"; } Student.prototype = Person.prototype //直接赋值
// call、apply、bind ----改变this指向,实现构造函数的继承(类的继承)
<script>
function foo(name,age) {
console.log(this,"姓名是"+name+"年龄是"+age);
}
// foo();
let obj = {
name:"张三"
}
//第一个参数是改变this指向,剩余是传参
// foo.call(obj,"张三",20);call接收多个参数
// foo.apply(obj,["张三",20]);apply接收数组
// foo.bind(obj)("张三",20);bind返回新的函数,再次执行函数
</script>
<script>
// 构造函数的继承;
function Dad(name,age) {
this.name = name;
this.age = age;
this.money = "100000";
}
function Son(name,age) {
// 父类与子类this互通
// Dad.call(this,name,age);
// Dad.apply(this,[name,age])
Dad.bind(this)(name,age);
this.sex = "男";
}
let zhangsann = new Son("张三",20);
console.log( zhangsann.money);//通过改变this指向实现继承,拿到父类的money
console.log( zhangsann.sex);
</script>
// 原型的继承
<script>
// 继承;
function Dad(name, age) {
this.name = name;
this.age = age;
this.money = "100000";
}
//原型上的方法
Dad.prototype.fn = function() {
console.log("fn");
}
function Son(name, age) {
Dad.call(this, name, age);
this.sex = "男";
}
Son.prototype = Dad.prototype;//这样直接等于会涉及到传址问题,会互相影响
Son.prototype.fn = function() {// 覆盖父类的fn
console.log("重写的fn");
}
let zhangsan = new Son("张三", 20);
// console.log( zhangsann.money);
// console.log( zhangsann.sex);
zhangsan.fn();//重写的fn
let zhangyi = new Dad("张一", 20);
zhangyi.fn();//重写的fn
</script>
原型的深拷贝继承
-
传值和传址问题
- 基本数据类型:Number、String、Boolean、Null、Undefined
- 复杂数据类型/引用数据类型:Array、Date、Math、RegExp、Object、Function等
-
JSON序列化的不足
如果拷贝对象包含函数,或者undefined等值,此方法就会出现问题
-
浅拷贝和深拷贝
//递归深拷贝 function deepCopy(obj){ let newObj = Array.isArray(obj)?[]:{}; for(let key in obj){ if(obj.hasOwnProperty(key)){ if(typeof obj[key] == "object"){ newObj[key] = deepCopy(obj[key]); }else{ newObj[key] = obj[key]; } } } return newObj; }
<script>
//复杂数据类型传址;(传址问题)
//(复杂数据类型涉及传址问题,会互相影响,解决相互影响的办法(解决传址问题):深拷贝)
// let DadProto = {
// name:"张三",
// age:20
// }
// let SonProto = DadProto;
// SonProto.name = "李四";
// console.log(SonProto);{name:"李四",age:20}
// console.log(DadProto);{name:"李四",age:20}
// 简单数据类型:传值;(重新开辟内存地址,不会互相影响)
// let a = 10;
// let b = a;
// b = 20;
// console.log(a);10
// 深拷贝;(解决传址问题)【重新在内存内开辟新地址】
// let DadProto = {
// name:"张三",
// age:20,
// fn:function() {
// console.log("fn..");
// },
// test:undefined
// }
// 使用序列化进行深拷贝时,需要注意方法和undefined会丢失
// let SonProto = JSON.parse(JSON.stringify( DadProto));
// SonProto.name = "李四";
// console.log(DadProto);
// console.log(SonProto);
// 深拷贝
let obj = {
name:"张三",
age:20,
fn:function() {
console.log("fn..");
},
test:undefined,
arr:[],
}
let obj2 = deepCopy( obj);
obj2.name = "李四";
console.log(obj2);
console.log(obj);
//(封装一个深拷贝:开辟新的地址)
function deepCopy(obj) {
let newObj = Array.isArray(obj)?[]:{};//查看obj是否是数组,是数组则新定义一个数组,否则定义一个对象
for(let key in obj){//循环遍历obj
if(obj.hasOwnProperty(key)){//不需要循环原型和原型链上的属性和方法,只拿自身的属性;判断key循环出来的属性是不是自身obj的属性和方法
if(typeof obj[key] === "object"){//若是对象,继续遍历
newObj[key] = deepCopy(obj[key]) ;
}else{
newObj[key] = obj[key];//存入新定义的newObj内
}
}
}
return newObj;
}
</script>
原型的继承
- 深拷贝继承
// 原型深拷贝继承
<script>
function deepCopy(obj) {
let newObj = Array.isArray(obj)?[]:{};
for(let key in obj){
if(obj.hasOwnProperty(key)){
if(typeof obj[key] === "object"){
newObj[key] = deepCopy(obj[key]) ;
}else{
newObj[key] = obj[key];
}
}
}
return newObj;
}
function Dad(name,age) {
this.name = name;
this.age = age;
this.money = "100000";
}
Dad.prototype.fn = function () {
console.log("喜欢象棋");
}
function Son(name,age) {
Dad.call(this,name,age);
this.sex = "男";
}
// 深拷贝继承
Son.prototype =deepCopy(Dad.prototype);
Son.prototype.fn = function () {
console.log("喜欢篮球");
}
let zhangsan = new Son("张三",20);
// console.log( zhangsann.money);
// console.log( zhangsann.sex);
zhangsan.fn();
let zhangyi = new Dad("张一",50);
zhangyi.fn();
</script>
-
组合继承
function Dad(){ this.name = "张三"; } Dad.prototype.hobby = function(){ console.log("喜欢篮球"); } function Son(){ Dad.call(this); } let F = function(){} F.prototype = Dad.prototype; Son.prototype = new F(); Son.prototype.constructor = Son; let newSon = new Son(); newSon.hobby();
<script>
// 组合继承
function Dad(name,age) {
this.name = name;
this.age = age;
this.money = "100000";
}
Dad.prototype.fn = function () {
console.log("喜欢象棋");
}
function Son(name,age) {
Dad.call(this,name,age);
this.sex = "男";
}
// Son.prototype =deepCopy(Dad.prototype);
// 组合继承,等同于以上
let Link = function(){};// 空构造函数
Link.prototype = Dad.prototype;
Son.prototype = new Link();
Son.prototype.constructor = Son;
Son.prototype.fn = function () {
console.log("喜欢篮球");
}
let zhangsan = new Son("张三",20);
// console.log( zhangsann.money);
// console.log( zhangsann.sex);
zhangsan.fn();
let zhangyi = new Dad("张一",50);
zhangyi.fn();
</script>
包装对象
- 除过null,undefined,基本类型都有自己对应的包装对象:String Number Boolean
- 包装对象把所有的属性和方法给了基本类型,然后包装对象消失