js面向对象 原型

1、面向对象在这里插入图片描述

创建对象:
a.使用new
b.使用字面量形式 : let obj = {name: ‘chen’}

1.1、属性描述符

可以精准的添加和修改对象的属性,需要使用Object.defindProperty
在这里插入图片描述

var obj = {
  name: 'chen',
  age: 12,
};

// 属性描述符是一个对象. 返回的是同一个对象
var newObj = Object.defineProperty(obj, 'height', {
  value: 1.8
})

console.log(obj) //{ name: 'chen', age: 12 }
console.log(newObj) //{ name: 'chen', age:12}
console.log(obj === newObj) // true

// 因为他是不可枚举的,但是我们obj.height是可以拿到的
console.log(obj.height);  // 1.8

在这里插入图片描述在这里插入图片描述

a、数据属性描述符

// 这种方式定义,name和age虽然没有使用属性描述符来定义,但是他们也是具备特性的
// value: 就是赋值的value
// configurable: ture
var obj = {
  name: "chen",
  age: 23,
};

// 数据属性描述符, 默认值都是false,value:undefined
Object.defineProperty(obj, "address", {
  value: "深圳",
  // configurable: false:该属性不可删除,也不可修改或者重新定义属性描述符
  configurable: false,
  
});

delete obj.name;
console.log(obj);
delete obj.address;
console.log(obj, obj.address); //{ age: 23 }  深圳
console.log('----------------------------------------------------')


var obj2 = {
  name: "chen",
  age: 23,
};

Object.defineProperty(obj2, "address", {
  value: "深圳",
  configurable: false,
  enumerable: true,
  // 是否可以修改值
  writable: false,
});

// 测试enumerable的作用
console.log(obj2);
for (const key in obj2) {
  console.log(key);
}
console.log(Object.keys(obj2));
obj2.address = '北京'
console.log(obj2);

b、存取属性描述符

var obj = {
  name: "chen",
  age: 23,
  _address: '北京市'
};


// 有get、set这种就叫存取属性描述符
Object.defineProperty(obj, "address", {
  configurable: true,
  enumerable: true,
  // value跟get不能共存
  // value: "上海",
  // 1、属性不想暴露可以使用这种, 隐藏私有属性
  // 2、如果我们希望截获一个属性访问和设置值的过程时
  get: function () {
    return this._address;
  },
  // writable跟set也不能共存
  // writable: true,
  set: function (value) {
    this._address = value;
  },
});
console.log(obj.address);
obj.address = '广州'
console.log(obj.address);

2、创建对象

2. 1、new操作符的作用

在这里插入图片描述
在这里插入图片描述

2. 2、创建对象方案-构造函数(方案1)
// 规范: 构造函数的首字母大写
function Person(name, age) {
  // this 是指向我们创建出来的对象的
  this.name = name;
  this.age = age;
  this.eating = function () {
    console.log(this.name + "正在吃饭");
  };
}

//批量创建对象
var p1 = new Person("张三", 23);
var p2 = new Person("张三", 23);
console.log(p1);

console.log('---');
console.log(p1.eating === p2.eating); // false

//es6新语法
//class Person{}
2. 3、创建对象-构造函数的缺点分析

如果属性是函数的话,会创建相同的对象,没有必要,我们需要用原型优化

function foo() {
  function bar() {}
  return bar;
}

// foo返回一个函数对象,但是每次都是不同的对象。
var fn1 = foo();
var fn2 = foo();
// 用变量接受,产生了两个函数对象
console.log(fn1 === fn2);
2.4、对象的原型的理解

每个对象都是有原型的

//1、我们每个对象中都有一个[[prototype]], 这个属性可以称之为对象的原型(隐式原型)
var obj = { name: "chen" }; //[[prototype]]
var info = {}; //[[prototype]]

// 早期的ECMA是没有规范如何去查看[[prototype]],
// 给对象中提供了一个属性,可以让我们查看一下这个原型对象(浏览器提供)
// __proto__

console.log(obj.__proto__); //[Object: null prototype] {}

let obj1 = {
  name: "fu",
  // 这么定义,相当于下面还有一行代码 __proto__: {}
};

// 开发中是不推荐使用__proto__的           //obj.__proto__拿到的我们称为隐式原型
console.log(Object.getPrototypeOf(obj)); //拿到的我们称为隐式原型

// 2、原型的作用: 当我们从一个对象中获取某一个属性时,它会触发[[get]]操作
// a.在当前对象中查找对应的属性,如果找到就直接使用
// b.如果没有找到,那么会沿着它的原型链去查找[[prototype]]
console.log(obj.age);
2.5、函数的原型的理解
function foo() {}

//函数也是一个对象,也是有隐式原型的
console.log(foo.__proto__); //函数作为对象来说,他也是有[[prototype]]隐式原型的

// 函数因为是一个函数, 所以它还会多出了一个显示原型属性:prototype
console.log(foo.prototype);

// foo.prototype 显示原型的作用:(对象的隐式原型__proto__ -> 指向 构造函数的显示原型[[prototype]])
// 在我们使用new的时候,这个对象内部的[[prototype]]属性会被赋值为该构造函数的[[prototype]]属性

var f1 = new foo();
var f2 = new foo();

f1.__proto__ === foo.prototype;
f2.__proto__ === foo.prototype;

a、创建对象的内存表现

在这里插入图片描述

b、Person构造函数原型内存图

function Person(){

}
var p1 = new Person();
var p2 = new Person();

p1.__proto__.name = 'fnCu'
console.log(p1.name);
// 我们也可以这么添加name
Person.prototype.name = 'fuer'
console.log(p1.name);

在这里插入图片描述

2.6、函数原型上的属性
2.7、创建对象方案 - 原型和构造函数结合(方案2)
function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.eating = function () {
  // this 判定的是调用的那个对象,比如p1; this是动态绑定的,运行的时候绑定的
  console.log(this.name + "吃东西");
};

var p1 = new Person("chen", 12);
var p2 = new Person("fuyi", 23);
console.log(p1.eating() === p2.eating()) //true

3、原型链 和 继承

3.1、原型链的理解 在这里插入图片描述
3.2、Object的原型的理解
var obj = {
  name: 'chen'
};
console.log(obj.address);

// 字面对象obj的原型是 
console.log(obj.__proto__); // [Object: null prototype] {}
console.log(obj.__proto__.__proto__); // null

[Object: null prototype] :已经是最顶层的原型了,它的原型属性指向的是null, 该对象上有很多默认的属性和方法

3.3、Person构造函数原型
function Person(){

}

console.log(Person.prototype);  // {}

// {
//   constructor: {
//     value: [Function: Person],
//     writable: true,
//     enumerable: false,
//     configurable: true
//   }
// }
console.log(Object.getOwnPropertyDescriptors(Person.prototype)); 
console.log(Person.prototype.__proto__); //[Object: null prototype] {}
3.4、继承 - 原型链的继承方案(实现继承的方案1)
// 开发中不会实现下面的实现继承的所有代码, 下面的只是帮助理解的
// 父类: 公共属性和方法
function Person() {
  this.name = "chen";
  this.friend = [];
}

Person.prototype.eating = function () {
  console.log(this.name + " eating~");
};
// 子类: 特有属性和方法
function Student() {
  this.sno = 111;
}

// 现在我们使用原型方式继承
Student.prototype = new Person();

Student.prototype.studying = function () {
  console.log(this.name + " studying");
};

var stu = new Student();
console.log(stu); // Student { sno: 111 }
console.log(stu.name);
stu.eating(); // stu中没有eating属性, 我们把undefined当成函数调用了,报错

// 原型链实现继承弊端
// 1.打印stu对象,继承的属性是看不到的
console.log(stu);

// 2.创建出两个stu
var stu1 = new Student();
var stu2 = new Student();
// 获取引用,修改引用中的值,会相互影响
// stu1.friend.push("fuyi");
console.log(stu1.friend); // [ 'fuyi' ]
// 我们发现, 下面的stu2也改了
console.log(stu2.friend); // [ 'fuyi' ]

// 但是,如果是下面这种方式,不会改的,只添加在stu1对象上
// 直接修改对象上的属性,是给本对象添加了一个新属性
stu1.name = 'chen'
console.log(stu1, stu2);

// 3.第三个弊端:在前面实现类的过程中都没有传参数 
var stu3 = new Student('fuer', 23);

原型分析图:在这里插入图片描述

原型链的继承方案弊端:

  • 打印stu对象, 某些属性是看不见的(原型上的、继承的属性是打印不出来的)

3.5、继承 - 借用构造函数 继承方案(方案2:方案1的优化)

function Person(name, age, friend) {
  // 这里的this就是传过来的studnet对象了
  this.name = name;
  this.age = age;
  this.friend = friend;
}

Person.prototype.eating = function () {
  console.log(this.name + " eating~");
};
function Student(name, age, friend) {
  // 这个this是创建Student出来的对象, call 交给Person函数进行统一处理
  Person.call(this, name, age, friend);
  this.sno = 111;
}

Student.prototype = new Person();

Student.prototype.studying = function () {
  console.log(this.name + " studying");
};

var stu = new Student('fuer', 23, ['fuyi']);
console.log(stu); //Person { name: 'fuer', age: 23, friend: [ 'fuyi' ], sno: 111 }
console.log(stu.name); //fuer
stu.eating();//fuer eating~

var stu1 = new Student('fuer', 23, ['fuyi']);
var stu2 = new Student('fuer', 23, ['zhangsan']);
console.log(stu1.friend); // [ 'fuyi' ]
console.log(stu2.friend); // [ 'fuyi' ]

stu1.name = "chen";
console.log(stu1, stu2); // 不一样

// 强调: 借用过程也是存在弊端的
// 弊端1:Person函数至少被调用了两次
// 弊端2:stu的原型对象上会多出一些属性,但是当前没有存在的必要
  • 内存原型图
    在这里插入图片描述

3.6、继承 - 父类原型直接赋值给子类 继承方案(方案3:针对方案2存在的问题)

// 不建议使用的
function Person(name, age, friend) {
  this.name = name;
  this.age = age;
  this.friend = friend;
}

Person.prototype.eating = function () {
  console.log(this.name + " eating~");
};
function Student(name, age, friend) {
  Person.call(this, name, age, friend);
  this.sno = 111;
}

// 父类原型直接赋值给子类 
Student.prototype = Person.prototype;

// 弊端: 这styding会被加到Person.prototype中,是不对的,应该加到父类上的
Student.prototype.studying = function () {
  console.log(this.name + " studying");
};

var stu = new Student('fuer', 23, ['fuyi']);
console.log(stu); 
console.log(stu.name); 
stu.eating();

// var stu1 = new Student('fuer', 23, ['fuyi']);
// var stu2 = new Student('fuer', 23, ['zhangsan']);
// console.log(stu1.friend); // [ 'fuyi' ]
// console.log(stu2.friend); // [ 'fuyi' ]

// stu1.name = "chen";
// console.log(stu1, stu2); // 不一样

弊端: 这styding会被加到Person.prototype中,是不对的,应该加到父类上的

3.7、继承 - 原型式继承函数-对象(方案4:针对方案3存在的问题)

var obj = {
  name: "chen",
  age: 23,
};

// 原型式继承函数:方式1
function createObject(o) {
  var newObj = {};
  // 设置newObj的原型是o
  Object.setPrototypeOf(newObj, o);
  return newObj;
}

function createObject2(o) {
  function Fn() {}
  Fn.prototype = o;
  return new Fn();
}

// 原型式继承函数:方式2
var info = createObject2(obj);
console.log(info); //{}
console.log(info.__proto__); //{ name: 'chen', age: 23 }

注: 现在的版本中,有Object.create(obj), 它实现的功能跟我们方式一、方式二是一样的

3.8、继承 - 寄生式继承-对象

了解

var personObj = {
  running: function() {
    console.log('runing');
  }
};

// var stuObj = Object.create(personObj);
// // 如果我们想在stuObj上扩展对象,需要.的方式
// stuObj.name = 'chen';
// stuObj.studing = function() {
// }

// 寄生式继承: 原型 + 工厂函数
function createStudent(name){
  var stu = Object.create(personObj);
  stu.name = name;
  stu.styding = function(){
    console.log('studying');
  }
  return stu;
}

var stu1 = Object.create('fuyi');
var stu2 = Object.create('fuer');
// var stu1 = Object.create(personObj);
// var stu2 = Object.create(personObj);

3.9、继承 - 寄生式组合式继承

// 继承函数封装
function inheritPrototype(SubType, SuperType) {
  SubType.prototype = Object.create(SuperType.prototype);
  Object.defineProperty(SubType.prototype, "constructor", {
    enumerable: false,
    writable: true,
    configurable: true,
    value: SubType,
  });
}

function Person(name, age, friend) {
  this.name = name;
  this.age = age;
  this.friend = friend;
}

Person.prototype.eating = function () {
  console.log(this.name + " eating~");
};
function Student(name, age, friend, sno, score) {
  Person.call(this, name, age, friend);
  this.sno = sno;
  this.score = score;
}

// Student.prototype = Object.create(Person.prototype);
// Object.defineProperty(Student.prototype, "constructor", {
//   enumerable: false,
//   writable: true,
//   configurable: true,
//   value: Student,
// });
// 01180D1D.png的代码不用,只需要下面的就行
inheritPrototype(Student, Person);

Student.prototype.studying = function () {
  console.log(this.name + " studying");
};

var stu = new Student("chen", 23, ["fuyi"], 222, 30);
console.log(stu);
stu.studying();
stu.eating();
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值