原型
构造函数
在js中如果想批量创建对象,不可能一个一个创建对象,通常会通过 工厂函数 来创建对象,也就是 工厂模式
function User(name, age) {
var person = {} // 定义一个person 对象
person.name = name; // 往对象中绑定传参
person.age = age;
person.greet = function () {
console.log('你好, 我是' + this.name + ',我' + this.age + '岁');
}
return person // 返回生成的新对象
}
var zhangsan = User('张三', 20);
var lisi = User('李四', 22);
工厂模式解决了批量创建对象的问题,但是无法识别对象属于哪种类型。
构造函数应运而生,可以识别创建的对象所属的类型。构造函数通常首字母大写,通过new来调用。
构造函数本身也是函数,只不过可以用来创建对象而已。
构造器的英文就是 constructor
,在 JavaScript 中,函数都可以用作构造器。构造器我们也可以称之为类,User 构造器不就可以称之为 User 类嘛。
function User(name, age) {
this.name = name; // 这里面的this,就代表了即将生成的那个对象 ,并且绑定传参
this.age = age;
this.greet = function () {
console.log('你好, 我是' + this.name + ',我' + this.age + '岁');
}
}
var zhangsan = new User('张三', 20);
var lisi = new User('李四', 22);
和 工厂 函数区别:
- 没有显示的创建对象
- 直接将属性和方法赋值给了this(this指向实例)
- 没有return
要创建User的新实例,必须使用new操作符
- 创建一个新对象
- 将构造函数的作用域指向新对象(因此this指向新对象)
- 执行构造函数中的代码,为对象添加属性和方法
- 返回对象
手写new
``` function myNew() { var obj = new Object(); // 创建一个实例对象 const fn = […arguments][0]; // 取得外部传入的构造函数 obj.proto = fn.prototype; // 实现继承,实例可以访问构造函数的属性 const res = fn.apply(obj, […arguments].slice(1)); // 调用构造器,并改变其 this 指向到实例 // 如果构造函数返回值是对象则返回这个对象,如果不是对象则返回新的实例对象 return typeof res === “object” ? res : obj; }
// ========= 无返回值 ============= function User(name, age) { this.name = name; // 这里面的this,就代表了即将生成的那个对象 ,并且绑定传参 this.age = age; }
const user = myNew(User, “管庆超”, “30”); console.log(user.name, user.age); // { name: “foo” } console.log(user instanceof User); // true
//========= 有返回值 ============= function User1(name, age) { this.name = name; // 这里面的this,就代表了即将生成的那个对象 ,并且绑定传参 this.age = age; return {}; }
const user1 = myNew(User1, “管成江”); console.log(user1); // {} console.log(user1 instanceof User1); // false
<a name="GMmjw"></a>
## 原型
function User(name, age) { this.name = name; // 这里面的this,就代表了即将生成的那个对象 ,并且绑定传参 this.age = age; this.greet = function() { console.log(“你好, 我是” + this.name + “,我” + this.age + “岁”); }; }
var zhangsan = new User(“张三”, 20); var lisi = new User(“李四”, 22);
zhangsan.greet(); // 你好我是张三,我20岁 lisi.greet(); // 你好我是李四,我22岁 console.log(zhangsan.say === lisi.say); //false
我们发现,User对象中的greet方法被实例化两次!<br />如果实例100个对象,岂不是要拷100份?完全没必要呀。有没有什么方法将这些通用的属性,放到一个地方呢?
原型!prototype
每个函数都有 "prototype" 属性,即使我们没有提供它。<br />默认的 "prototype" 是一个只有属性 constructor 的对象,属性 constructor 指向函数自身。
![image.png](https://cdn.nlark.com/yuque/0/2021/png/248010/1638625168885-ae70ac70-ebea-484b-9c55-80d415871bbe.png#clientId=ua5a5abec-73da-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=228&id=u470c5e56&margin=%5Bobject%20Object%5D&name=image.png&originHeight=456&originWidth=1390&originalType=binary&ratio=1&rotation=0&showTitle=false&size=66936&status=done&style=none&taskId=u43d893d4-baf6-4fe3-8a84-c6346a8987e&title=&width=695)
原型的英文应该叫做 `prototype`,任何一个对象都有原型,我们可以通过非标准属性 `__proto__` 来访问一个对象的原型:
// 纯对象的原型默认是个空对象 console.log({}); // => {}
function User(name) { this.name = name; }
const user = new User(“guanqingchao”); // Student 类型实例的原型 console.log(user);
![image.png](https://cdn.nlark.com/yuque/0/2021/png/248010/1614079312324-70e68042-0c4b-4bdb-b9c8-b4569e62cdee.png#crop=0&crop=0&crop=1&crop=1&height=443&id=qAKPb&margin=%5Bobject%20Object%5D&name=image.png&originHeight=886&originWidth=878&originalType=binary&ratio=1&rotation=0&showTitle=false&size=120010&status=done&style=none&title=&width=439)<br />每个函数都有prototype属性,也就是通过构造函数 创建的实例 的原型对象。默认情况下,prototype都有一个constructor属性,指向该构造函数
使用prototype,可以使得所有通过构造函数创建的实例共享原型上的属性和方法
function User(name, age) { this.name = name; // 这里面的this,就代表了即将生成的那个对象 ,并且绑定传参 this.age = age; } User.prototype.say = function() { console.log(“你好, 我是” + this.name + “,我” + this.age + “岁”); };
var user1 = new User(“张三”, 20); var user2 = new User(“李四”, 22);
user1.say(); // 你好我是张三,我20岁 user2.say(); // 你好我是李四,我22岁 console.log(user1.say === user2.say); //true 通过原型,方法不会被多次实例化 console.log(User.prototype.constructor === User); //true console.log(user1.proto === user2.proto); //true
![image.png](https://cdn.nlark.com/yuque/0/2021/png/248010/1614082249013-d66b6fb0-f5ff-4b02-99cf-48981b3dd583.png#crop=0&crop=0&crop=1&crop=1&height=284&id=NAfjU&margin=%5Bobject%20Object%5D&name=image.png&originHeight=568&originWidth=700&originalType=binary&ratio=1&rotation=0&showTitle=false&size=561188&status=done&style=none&title=&width=350)<br />可以看出,实例和构造函数通过原型链接起来。<br />构造函数、实例、原型的关系:每个构造函数都有对应的原型prototype,原型的constructor属性指向构造函数。实例的__proto__属性指向构造函数对应的原型
<a name="LHOlj"></a>
## 原型链
每个对象拥有一个原型对象,通过 `__proto__` 指针指向上一个原型 ,并从中**继承方法和属性**,同时原型对象也可能拥有原型,这样一层一层,最终指向 `null`,这种关系被称为**原型链**(prototype chain)。根据定义,`null` 没有原型,并作为这个原型链中的最后一个环节。
当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的原型中寻找,以及该对象的原型的原型,一层一层向上查找,直到找到一个名字匹配的属性 / 方法或到达原型链的末尾(`null`)
原型链的基本思想是利用原型,让一个引用类型继承另一个引用类型的属性及方法。
function User(name, age) { this.name = name; // 这里面的this,就代表了即将生成的那个对象 ,并且绑定传参 this.age = age; } User.prototype.say = function() { console.log(“你好, 我是” + this.name + “,我” + this.age + “岁”); };
function Teacher(job) { this.job = job; } Teacher.prototype = new User();
const teacher = new Teacher(“teacher”); teacher.say(); //你好, 我是undefined,我undefined岁 console.log(teacher.job);
console.log(Teacher.prototype.proto === User.prototype); //true console.log(teacher.proto === Teacher.prototype); //true
// ======instanceof ======== console.log(teacher instanceof Teacher,teacher instanceof User,teacher instanceof Object)
思考:如何实现继承?使得Teacher继承User的属性
其中,Teacher.prototype = new User(); 的意图是让Teacher.prototype的__proto__指向User的原型<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/248010/1614083710649-0e9be127-3961-43a4-91b4-b5bf7e819b2b.png#crop=0&crop=0&crop=1&crop=1&height=395&id=bBuu7&margin=%5Bobject%20Object%5D&name=image.png&originHeight=790&originWidth=722&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1062829&status=done&style=none&title=&width=361)
宇宙的尽头是什么?Object => null
> 由于 Object 本身是一个函数,由 Function 所创建,所以 Object.proto 的值是 Function.prototype,而 Function.prototype 的 proto 属性是 Object.prototype,所以我们可以判断出,Object instanceof Object 的结果是 true 。
function Foo(){};
//一切函数继承自Function Foo.proto === Function.prototype; Object.proto === Function.prototype;//function Object(){} Function.proto === Function.prototype;
//一切都继承自Object Function.prototype.proto === Object.prototype;
console.log(Object.prototype.proto); //null
Function instanceof Object // true Object instanceof Function // true
![image.png](https://cdn.nlark.com/yuque/0/2021/png/248010/1614074280306-361492e5-ebd3-4a5d-a6a6-9d786e362ad5.png#crop=0&crop=0&crop=1&crop=1&height=760&id=O3TGH&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1519&originWidth=1221&originalType=binary&ratio=1&rotation=0&showTitle=false&size=281136&status=done&style=none&title=&width=610.5)<br />让我们聚焦Object和Function<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/248010/1614086979892-12e6cf7a-6676-4395-aa9a-f1a63f58fde5.png#crop=0&crop=0&crop=1&crop=1&height=403&id=eppKm&margin=%5Bobject%20Object%5D&name=image.png&originHeight=806&originWidth=1202&originalType=binary&ratio=1&rotation=0&showTitle=false&size=323506&status=done&style=none&title=&width=601)
**可以使用 **`**instanceof**`** 判断一个函数是否为一个变量的构造函数**
<a name="BDp5r"></a>
## 手写instanceOf
instanceof 来判断对象的具体类型,其实 instanceof 主要的作用就是判断一个实例是否属于某种类型
let person = function () { } let nicole = new person() nicole instanceof person // true
instanceof 也可以判断一个实例是否是其父类型或者祖先类型的实例。
let person = function () { } let programmer = function () { } programmer.prototype = new person() let nicole = new programmer() nicole instanceof person // true nicole instanceof programmer // true
instanceof 主要的实现原理就是只要右边变量的 prototype 在左边变量的原型链上即可
//方法一: function instanceOf(left,right) { let proto = left.proto; let prototype = right.prototype while(true) { if(proto === null) return false if(proto === prototype) return true proto = proto.proto; } }
//方法二 function myInstanceof(left, right) { const proto = left.proto; if (proto) { if (right.prototype === proto) { return true; } else { return myInstanceof(proto, right); } } else { return false; } } console.log(“测试myInstanceof”, myInstanceof(teacher, Teacher));
<a name="UZpfg"></a>
# 继承
<a name="Ck2Xb"></a>
## 原型链继承
function User(name, age) { this.name = name; this.age = age; this.colors = [“red”, “blue”, “green”]; } // function User() { // this.name = ‘User’; // this.age = 30; // } User.prototype.say = function() { console.log(“你好, 我是” + this.name + “,我” + this.age + “岁”); };
function Teacher(job) { this.job = job; } Teacher.prototype = new User(“User”, 30); // **
Teacher.prototype.teach = function() { console.log(“My job is”, this.job); };
const teacher1 = new Teacher(“teacher”); const teacher2 = new Teacher(“ school teacher “); teacher1.teach(); teacher1.say(); teacher1.colors.push(“purple”); console.log(teacher2.colors); // [“red”, “blue”, “green”, “purple”] console.log(Teacher.prototype instanceof User); // true
**特点**:利用原型,让一个引用类型继承另一个引用类型的属性及方法<br />**优点**:**继承了父类的模板,又继承了父类的原型对象**<br />**缺点**:
- 在子类构造函数中,为子类实例增加实例属性。如果要**新增原型属性和方法**,则必须放在 Teacher.prototype = new User("User", 30); 这样的语句**之后**执行。
- 无法实现多继承 **因为不能给父类传参**
- 来自原型对象的所有属性被**所有实例共享,**当属性是引用类型的时候,会被更新,参见colors
- **创建子类实例时,无法向父类构造函数传参**,或者说是,没办法在不影响所有对象实例的情况下,向超类的构造函数传递参数 ,参见17行。
<a name="OR5KD"></a>
## 构造函数继承
**基本思想**:在子类的构造函数内部调用父类型构造函数。<br />**注意**:函数只不过是在特定环境中执行代码的对象,所以这里使用 apply/call 来实现。<br />**使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)**
function User(name, age) { this.name = name; this.age = age; this.colors = [“red”, “blue”, “green”]; this.eat = function(){ console.log(‘eat) } }
User.prototype.say = function() { console.log(“你好, 我是” + this.name + “,我” + this.age + “岁”); };
function Teacher(name, age, job) { User.call(this, name, age); //在子类构造函数中,向父类构造函数传参 // 为了保证子父类的构造函数不会重写子类的属性,需要在调用父类构造函数后,定义子类的属性 this.job = job; }
Teacher.prototype.teach = function() { console.log(“My job is”, this.job); };
const teacher1 = new Teacher(“NANA”, 30, “teacher”); const teacher2 = new Teacher(“Joy”, 33, “school teacher “); teacher1.teach(); teacher1.eat(); console.log(teacher1 instanceof User); // false // teacher1.say(); // * Error: teacher1.say is not a function 因为构造函数继承只是复制父类的属性和方法,不会复制父类原型上的属性和方法 teacher1.colors.push(“purple”); console.log(teacher1.colors); //[“red”, “blue”, “green”, “purple”] console.log(teacher2.colors); //[“red”, “blue”, “green”] console.log(teacher1.eat === teacher2.eat); // false
**优点**:**解决了1中子类实例共享父类引用对象的问题,实现多继承,创建子类实例时,可以向父类传递参数**<br />**缺点**:
- 实例并不是父类的实例,只是子类的实例 【参见28行】
- **只能**继承父类的实例属性和方法,**不能**继承原型属性/方法 【参见29行】
- **无法实现函数复用**,每个子类都有父类实例函数的副本,影响性能 【参见33行】
<a name="BOA1S"></a>
## 组合继承
**基本思想**:使用**原型链**继承使用对原型属性和方法的继承,通过**构造函数**继承来实现对实例属性的继承。这样既能通过在原型上定义方法实现函数复用,又能保证每个实例都有自己的属性。
**通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用**
function User(name, age) { this.name = name; this.age = age; this.colors = [“red”, “blue”, “green”]; }
User.prototype.say = function() { console.log(“你好, 我是” + this.name + “,我” + this.age + “岁”); };
function Teacher(name, age, job) { User.call(this, name, age); // ——第二次调用 User 继承属性—— this.job = job; }
// ——第一次调用 User 继承方法—— Teacher.prototype = new User(); // 重写原型对象,代之以一个新类型的实例 Teacher.prototype.constructor = Teacher;
Teacher.prototype.teach = function() { console.log(“My job is”, this.job); };
const teacher1 = new Teacher(“NANA”, 30, “teacher”); const teacher2 = new Teacher(“Joy”, 33, “school teacher “); console.log(‘teacher1’,teacher1); teacher1.teach(); teacher1.say(); console.log(teacher1.say === teacher2.say); // true
![image.png](https://cdn.nlark.com/yuque/0/2021/png/248010/1614144871384-9e6cc9f3-c9c4-4fe2-bf9c-70c76eec8bd2.png#crop=0&crop=0&crop=1&crop=1&height=313&id=Sfivg&margin=%5Bobject%20Object%5D&name=image.png&originHeight=626&originWidth=1250&originalType=binary&ratio=1&rotation=0&showTitle=false&size=132150&status=done&style=none&title=&width=625)<br />第一次调用 User 构造函数时,Teacher.prototype 会得到属性age、name、colors;<br />当调用 Teacher 构造函数时,第二次调用 User 构造函数,这一次又在新对象上创建了age、name、colors属性,这三个属性就会屏蔽原型对象上的同名属性。
- **优点**:**弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法,不存在引用属性共享问题,可传参,可复用**
- **缺点**:
- 调用了两次父类构造函数,生成了**两份实例**(子类实例将子类原型上的那份屏蔽了)
<a name="9WBDy"></a>
## 寄生组合继承
在组合继承中,调用了两次父类构造函数,这里 **通过通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点**
**主要思想**:借用 **构造函数** 继承 **属性** ,通过 **原型链的混成形式** 来继承 **方法**
function User(name, age) { this.name = name; this.age = age; this.colors = [“red”, “blue”, “green”]; }
User.prototype.say = function() { console.log(“你好, 我是” + this.name + “,我” + this.age + “岁”); };
function Teacher(name, age, job) { User.call(this, name, age); // * 继承父类的实例属性和方法 this.job = job; }
Teacher.prototype = Object.create(User.prototype); // 相当于 只继承 User原型上的属性和方法 // console.log(Teacher.prototype.proto === User.prototype) // true Teacher.prototype.teach = function() { console.log(“My job is”, this.job); };
const teacher1 = new Teacher(“NANA”, 30, “teacher”); const teacher2 = new Teacher(“Joy”, 33, “school teacher “); console.log(“teacher1”, teacher1); teacher1.teach(); teacher1.say();
![image.png](https://cdn.nlark.com/yuque/0/2021/png/248010/1614146319432-5bbc3482-fea3-488b-88c0-e140ab384d57.png#crop=0&crop=0&crop=1&crop=1&height=235&id=cUink&margin=%5Bobject%20Object%5D&name=image.png&originHeight=470&originWidth=1220&originalType=binary&ratio=1&rotation=0&showTitle=false&size=65297&status=done&style=none&title=&width=610)
Object.create()
Object.create(proto,[propertiesObject])
- proto:新创建对象的原型对象
- propertiesObject:可选。要添加到新对象的**可枚举**(新添加的属性是其自身的属性,而不是其原型链上的属性)的属性。
const Girl = { name: “guanqingchao”, age: 22 };
const girl = Object.create(Girl);
console.log(‘girl’, girl.proto);
Object.create()和{}的区别:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/248010/1614146007235-8022ea3f-96d6-4075-ba26-33fb6914f7d0.png#crop=0&crop=0&crop=1&crop=1&height=515&id=XP0HE&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1030&originWidth=1218&originalType=binary&ratio=1&rotation=0&showTitle=false&size=157119&status=done&style=none&title=&width=609)<br />**优点**:
- 只调用一次 `SuperType` 构造函数,只创建一份父类属性
- 原型链保持不变
- 能够正常使用 `instanceof` 与 `isPrototypeOf`
<a name="Y1a5j"></a>
## 原型式继承
**实现思路就是将子类的原型设置为父类的原型**<br />**实现:**
- `SuperType.call` 继承实例属性方法
- 用 `Object.create()` 来继承原型属性与方法
- 修改 `SubType.prototype.constructor `的指向
function User(name, age) { this.name = name; this.age = age; this.colors = [“red”, “blue”, “green”]; }
User.prototype.say = function() { console.log(“你好, 我是” + this.name + “,我” + this.age + “岁”); };
/ 第一步 / // 子类,通过 call 继承父类的实例属性和方法,不能继承原型属性/方法 function Teacher(name, age, job) { User.call(this, name, age); // this.job = job; }
/* 第二步 / // 解决 call 无法继承父类原型属性/方法的问题 // Object.create 方法接受传入一个作为新创建对象的原型的对象,创建一个拥有指定原型和若干个指定属性的对象 // 通过这种方法指定的任何属性都会覆盖原型对象上的同名属性
Teacher.prototype = Object.create(User.prototype, { constructor: { // 注意指定 Teacher.prototype.constructor = Teacher value: Teacher, enumerable: false, writable: true, configurable: true }, run: { value: function() { // override User.prototype.run.apply(this, arguments); // call super }, enumerable: true, configurable: true, writable: true } });
Teacher.prototype.teach = function() { console.log(“My job is”, this.job); }; /* 第三步 / // 最后:解决 Teacher.prototype.constructor === User 的问题 // 这里,在上一步已经指定,这里不需要再操作 // Teacher.prototype.constructor = Teacher; const teacher1 = new Teacher(“NANA”, 30, “teacher”); const teacher2 = new Teacher(“Joy”, 33, “school teacher “); console.log(“teacher1”, teacher1);
如果希望能 **多继承 **,可使用 **混入** 的方式
// 父类 SuperType function SuperType () {} // 父类 OtherSuperType function OtherSuperType () {}
// 多继承子类 function AnotherType () { SuperType.call(this) // 继承 SuperType 的实例属性和方法 OtherSuperType.call(this) // 继承 OtherSuperType 的实例属性和方法 }
// 继承一个类 AnotherType.prototype = Object.create(SuperType.prototype);
// 使用 Object.assign 混合其它 Object.assign(AnotherType.prototype, OtherSuperType.prototype); // Object.assign 会把 OtherSuperType 原型上的函数拷贝到 AnotherType 原型上,使 AnotherType 的所有实例都可用 OtherSuperType 的方法
// 重新指定 constructor AnotherType.prototype.constructor = AnotherType;
AnotherType.prototype.myMethod = function() { // do a thing };
let instance = new AnotherType()
<a name="bKOBH"></a>
# Object.create vs Object.setPrototypeOf
function Animal (name,sound) {
this.name = name
this.sound = sound
}
Animal.prototype.shout = function () {
console.log(this.name + this.sound)
}
let dog = new Animal('pipi','wang!wang!')
// 定义Plants
function Plants (name) {
this.name = name
this.sound = null
}
// 函数接收参数用于区分
Plants.prototype.shout = function (xssss) {
console.log(this.name + this.sound +'plants tag')
}
Plants.prototype.genO2 = function () {
console.log(this.name + '生成氧气。')
}
![image.png](https://cdn.nlark.com/yuque/0/2021/png/248010/1624427895075-7fb21809-257a-49b2-9bc9-761c3f2b1e90.png#clientId=uf65f56cb-9b36-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=254&id=u9279ec8f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=508&originWidth=1144&originalType=binary&ratio=2&rotation=0&showTitle=false&size=73373&status=done&style=none&taskId=ufe818e68-3de5-4bbd-802a-01a5e88cbac&title=&width=572)
<a name="BXTJP"></a>
## Object.setPrototypeOf
Object.setPrototypeOf(obj, prototype) => obj.proto = prototype
obj 要设置其原型的对象。 prototype 该对象的新原型(一个对象 或 null).
```
Object.setPrototypeOf(Animal.prototype,Plants.prototype)
dog.shout() // pipi wang!wang!
cat.shout() // mimi miao~miao~
cat.genO2() // mimi 生成氧气。
使用Object.setPrototypeOf则会将Animal.prototype将会指向Animal原有的prototype,然后这个prototype的prototype再指向Plants的prototytpe。所以我们优先访问的Animal,然后再是plants。
Object.create
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的proto
返回一个新对象,带着指定的原型对象和属性。
Object.create(proto,[propertiesObject])
proto
新创建对象的原型对象。
propertiesObject
可选。需要传入一个对象,该对象的属性类型参照Object.defineProperties()的第二个参数。
如果该参数被指定且不为 undefined,该传入对象的自有可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)将为新创建的对象添加指定的属性值和对应的属性描述符。
const person = {
isHuman: false,
printIntroduction: function() {
console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
}
};
const me = Object.create(person);
me.name = 'Matthew'; // "name" is a property set on "me", but not on "person"
me.isHuman = true; // inherited properties can be overwritten
me.printIntroduction();
Animal.prototype = Object.create(Plants.prototype)
let cat = new Animal('mimi','miao~miao~')
dog.shout() // pipi wang!wang!
cat.shout() // mimi miao~miao~
cat.genO2() // mimi 生成氧气。
使用Object.create,Animal.prototype将会指向一个空对象,空对象的原型属性指向Plants的prototytpe。所以我们不能再访问Animal的原有prototypoe中的属性。Object.create的使用方式也凸显了直接重新赋值。
ES6类和继承
class User {
static _age = 30;
_name = "Chaochao"; //实例的属性也可以定义在顶层,原型上无法访问
//constructor中定义实例的属性
constructor(name, age) {
this.name = name;
this.age = age;
this.fruite = "apple";
}
//User.prototype 原型上的方法
say() {
console.log(
"你好, 我是" + this._name + this.name + ",我" + this.age + "岁"
);
}
//静态方法
static eat() {
console.log("I want to eat");
}
}
class Teacher extends User {
constructor(name, age, job) {
super(name, age); // super 相当于属性的继承 替换了 User.call(this, name, age) 等同于调用父类的constructor(name, age)
this.job = job;
}
teach() {
console.log("My job is", this.job);
}
}
// ===== 静态方法/属性调用 =====
User.eat();
Teacher.eat(); //父类的静态方法可以被子类继承
console.log(User._age, Teacher._age);
const teacher = new Teacher("Nana", 30, "teacher");
console.log(typeof User); // function 类的本质是函数
console.log(User.prototype.constructor === User); // true 对象的constructor()属性,直接指向“类”的本身,这与 ES5 的行为是一致的。
console.log(Object.keys(User.prototype)); //[] 类的内部所有定义的方法,都是不可枚举的 ES5 的行为不一致
teacher.say();
teacher.teach();
//teacher.eat(); //静态方法不可以被继承
constructor() 方法是类的默认方法,定义实例对象的属性。通过new命令生成对象实例时,自动调用该方法。constructor()方法默认返回实例对象,
受保护的属性
以下划线_开头。 属于实例的,prototype上无法访问
静态static:
静态属性和方法 :相当于Class.staticProp ,this指向Clas
如果在一个方法前,加上static
关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
- 如果静态方法包含
this
关键字,这个this
指的是类 - 父类的静态方法,可以被子类继承。
静态属性 static
静态属性指的是 Class 本身的属性,即Class.propName
,而不是定义在实例对象(this
)上的属性。
- 类直接访问,实例不可以访问
- 可以被子类继承
私有方法和属性
通常#开头
私有方法和私有属性,是只能在类的内部访问this.#XX的方法和属性,外部不能访问。
实例和类都不能访问
class Person {
static country = 'China'; //静态属性 只能类访问 实例不能继承
#job = '私有变量JOB'; //Private私有变量 只能在本类中访问
_age = '受保护变量年龄'; //仅实例例可以访问 原型上没有 可以被继承
constructor(name) {
this.name = name;
}
sayHi() {
this.#handleJob();
console.log(`Hi: ${this.name}`);
}
#handleJob() {
console.log(this.#job);
}
}
class Man extends Person {
constructor(name, sex) {
super(name); //
this.sex = sex;
}
walk() {
super.sayHi();
console.log(this.name + this.sex);
}
}
const man = new Man('男人', 'man');
man.walk();
继承 super
通过super来实现继承,相当于调用父类的constructor()
- 子类必须在
constructor
方法中调用super
方法,否则新建实例时会报错。这是因为子类自己的this
对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super
方法,子类就得不到this
对象。 - 只有调用
super
之后,才可以使用this
关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有super
方法才能调用父类实例。
ES5 的继承,实质是先创造子类的实例对象
this
,然后再将父类的方法添加到this
上面(Parent.call(this)
)。 ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this
上面(所以必须先调用super
方法),然后再用子类的构造函数修改this
。
super有两种用法:
1.作为函数调用时,代表父类的构造函数,super中的this指向子类的实例,只能在子类的constructor中调用
class A {}
class B extends A {
constructor() {
super(); // this指向B的实例 代表 A的constructor
}
}
2.作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
class Person {
static country = 'China'; //静态属性 只能类访问 实例不能继承
#job = '私有变量JOB'; //Private私有变量 只能在本类中访问
_age = '受保护变量年龄'; //仅实例例可以访问 原型上没有 可以被继承
constructor(name) {
this.name = name;
}
sayHi() {
this.#handleJob();
console.log(`Hi: ${this.name}`);
}
#handleJob() {
console.log(this.#job);
}
}
class Man extends Person {
constructor(name, sex) {
super(name); //
this.sex = sex;
}
walk() {
super.sayHi(); //Person.prototype.sayHi 指向父类的原型对象 只能获取到原型上的方法 实例属性和方法获取不到
console.log(this.name + this.sex);
}
}
const man = new Man('男人', 'man');
man.walk();
extends
继承的核心代码如下,其实现和上述的寄生组合式继承方式一样
function _inherits(subType, superType) {
// 创建对象,Object.create 创建父类原型的一个副本
// 增强对象,弥补因重写原型而失去的默认的 constructor 属性
// 指定对象,将新创建的对象赋值给子类的原型 subType.prototype
subType.prototype = Object.create(superType && superType.prototype, {
constructor: { // 重写 constructor
value: subType,
enumerable: false,
writable: true,
configurable: true
}
});
if (superType) {
Object.setPrototypeOf
? Object.setPrototypeOf(subType, superType)
: subType.__proto__ = superType;
}
}
与ES5区别
例如,我有一个 StreetSweeper 类和一个 Bicycle 类,现在想要一个它们的 mixin:StreetSweepingBicycle 类。
或者,我们有一个 User 类和一个 EventEmitter 类来实现事件生成(event generation),并且我们想将 EventEmitter 的功能添加到 User 中,以便我们的用户可以触发事件(emit event)。
有一个概念可以帮助我们,叫做 “mixins” 根据维基百科的定义,mixin 是一个包含可被其他类使用而无需继承的方法的类。
在 JavaScript 中构造一个 mixin 最简单的方式就是构造一个拥有实用方法的对象,以便我们可以轻松地将这些实用的方法合并Object.assign()到任何类的原型中。
let sayMixin = {
say(phrase) {
alert(phrase);
}
};
let sayHiMixin = {
__proto__: sayMixin, // (或者,我们可以在这儿使用 Object.create 来设置原型)
sayHi() {
// 调用父类方法
super.say(`Hello ${this.name}`); // (*)
},
sayBye() {
super.say(`Bye ${this.name}`); // (*)
}
};
class User {
constructor(name) {
this.name = name;
}
}
// 拷贝方法
Object.assign(User.prototype, sayHiMixin);
// 现在 User 可以打招呼了
new User("Dude").sayHi(); // Hello Dude!
实战
let eventMixin = {
/**
* 订阅事件,用法:
* menu.on('select', function(item) { ... }
*/
on(eventName, handler) {
if (!this._eventHandlers) this._eventHandlers = {};
if (!this._eventHandlers[eventName]) {
this._eventHandlers[eventName] = [];
}
this._eventHandlers[eventName].push(handler);
},
/**
* 取消订阅,用法:
* menu.off('select', handler)
*/
off(eventName, handler) {
let handlers = this._eventHandlers?.[eventName];
if (!handlers) return;
for (let i = 0; i < handlers.length; i++) {
if (handlers[i] === handler) {
handlers.splice(i--, 1);
}
}
},
/**
* 生成具有给定名称和数据的事件
* this.trigger('select', data1, data2);
*/
trigger(eventName, ...args) {
if (!this._eventHandlers?.[eventName]) {
return; // 该事件名称没有对应的事件处理程序(handler)
}
// 调用事件处理程序(handler)
this._eventHandlers[eventName].forEach(handler => handler.apply(this, args));
}
};
class Menu {
choose(value) {
this.trigger("select", value);
}
}
// 添加带有事件相关方法的 mixin
Object.assign(Menu.prototype, eventMixin);
let menu = new Menu();
// 添加一个事件处理程序(handler),在被选择时被调用:
menu.on("select", value => alert(`Value selected: ${value}`));
// 触发事件 => 运行上述的事件处理程序(handler)并显示:
// 被选中的值:123
menu.choose("123")
练习
1.Animal类 有A,B两个方法,Dog继承Animal,且仅仅继承A方法
//Animal类 有A,B两个方法
function Animal(name) {
this.name = name;
}
Animal.prototype.A = function() {
console.log(`animal is ${this.name}`);
};
Animal.prototype.B = function() {
console.log(`animal B func`);
};
//Dog类 只继承Animal的A方法
function Dog(name, color) {
Animal.call(this, name);
this.color = color;
}
Dog.prototype.A = Animal.prototype.A;
// Dog.prototype = new Animal();
// Dog.prototype.constructor = Dog;
Dog.prototype.Bark = function() {
console.log(`${this.color} ${this.name} is barking`);
};
let dog = new Dog("dog", "balck");
dog.A();
dog.Bark();
//cat类 只继承Dog的Bark方法
function Cat(name,color,age){
Dog.call(this,name,color);
this.age = age||0
}
Cat.prototype = new Dog();
Cat.prototype.sayAge = function(){
console.log(`Age is ${this.age} years old`)
}
let cat = new Cat('cat','white',2)
cat.A();
cat.Bark();
cat.sayAge();