原型

构造函数

在js中如果想批量创建对象,不可能一个一个创建对象,通常会通过 工厂函数 来创建对象,也就是 工厂模式

  1. function User(name, age) {
  2. var person = {} // 定义一个person 对象
  3. person.name = name; // 往对象中绑定传参
  4. person.age = age;
  5. person.greet = function () {
  6. console.log('你好, 我是' + this.name + ',我' + this.age + '岁');
  7. }
  8. return person // 返回生成的新对象
  9. }
  10. var zhangsan = User('张三', 20);
  11. var lisi = User('李四', 22);

工厂模式解决了批量创建对象的问题,但是无法识别对象属于哪种类型。

构造函数应运而生,可以识别创建的对象所属的类型。构造函数通常首字母大写,通过new来调用。
构造函数本身也是函数,只不过可以用来创建对象而已。
构造器的英文就是 constructor,在 JavaScript 中,函数都可以用作构造器。构造器我们也可以称之为类,User 构造器不就可以称之为 User 类嘛。

  1. function User(name, age) {
  2. this.name = name; // 这里面的this,就代表了即将生成的那个对象 ,并且绑定传参
  3. this.age = age;
  4. this.greet = function () {
  5. console.log('你好, 我是' + this.name + ',我' + this.age + '岁');
  6. }
  7. }
  8. var zhangsan = new User('张三', 20);
  9. var lisi = new User('李四', 22);

image.png
和 工厂 函数区别:

  • 没有显示的创建对象
  • 直接将属性和方法赋值给了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

  1. <a name="GMmjw"></a>
  2. ## 原型

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

  1. 我们发现,User对象中的greet方法被实例化两次!<br />如果实例100个对象,岂不是要拷100份?完全没必要呀。有没有什么方法将这些通用的属性,放到一个地方呢?
  2. 原型!prototype
  3. 每个函数都有 "prototype" 属性,即使我们没有提供它。<br />默认的 "prototype" 是一个只有属性 constructor 的对象,属性 constructor 指向函数自身。
  4. ![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)
  5. 原型的英文应该叫做 `prototype`,任何一个对象都有原型,我们可以通过非标准属性 `__proto__` 来访问一个对象的原型:

// 纯对象的原型默认是个空对象 console.log({}); // => {}

function User(name) { this.name = name; }

const user = new User(“guanqingchao”); // Student 类型实例的原型 console.log(user);

  1. ![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属性,指向该构造函数
  2. 使用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

  1. ![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__属性指向构造函数对应的原型
  2. <a name="LHOlj"></a>
  3. ## 原型链
  4. 每个对象拥有一个原型对象,通过 `__proto__` 指针指向上一个原型 ,并从中**继承方法和属性**,同时原型对象也可能拥有原型,这样一层一层,最终指向 `null`,这种关系被称为**原型链**(prototype chain)。根据定义,`null` 没有原型,并作为这个原型链中的最后一个环节。
  5. 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的原型中寻找,以及该对象的原型的原型,一层一层向上查找,直到找到一个名字匹配的属性 / 方法或到达原型链的末尾(`null`
  6. 原型链的基本思想是利用原型,让一个引用类型继承另一个引用类型的属性及方法。

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)

  1. 思考:如何实现继承?使得Teacher继承User的属性
  2. 其中,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)
  3. 宇宙的尽头是什么?Object => null
  4. > 由于 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

  1. ![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)
  2. **可以使用 **`**instanceof**`** 判断一个函数是否为一个变量的构造函数**
  3. <a name="BDp5r"></a>
  4. ## 手写instanceOf
  5. instanceof 来判断对象的具体类型,其实 instanceof 主要的作用就是判断一个实例是否属于某种类型

let person = function () { } let nicole = new person() nicole instanceof person // true

  1. instanceof 也可以判断一个实例是否是其父类型或者祖先类型的实例。

let person = function () { } let programmer = function () { } programmer.prototype = new person() let nicole = new programmer() nicole instanceof person // true nicole instanceof programmer // true

  1. 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));

  1. <a name="UZpfg"></a>
  2. # 继承
  3. <a name="Ck2Xb"></a>
  4. ## 原型链继承

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

  1. **特点**:利用原型,让一个引用类型继承另一个引用类型的属性及方法<br />**优点**:**继承了父类的模板,又继承了父类的原型对象**<br />**缺点**:
  2. - 在子类构造函数中,为子类实例增加实例属性。如果要**新增原型属性和方法**,则必须放在 Teacher.prototype = new User("User", 30); 这样的语句**之后**执行。
  3. - 无法实现多继承 **因为不能给父类传参**
  4. - 来自原型对象的所有属性被**所有实例共享,**当属性是引用类型的时候,会被更新,参见colors
  5. - **创建子类实例时,无法向父类构造函数传参**,或者说是,没办法在不影响所有对象实例的情况下,向超类的构造函数传递参数 ,参见17行。
  6. <a name="OR5KD"></a>
  7. ## 构造函数继承
  8. **基本思想**:在子类的构造函数内部调用父类型构造函数。<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. **优点**:**解决了1中子类实例共享父类引用对象的问题,实现多继承,创建子类实例时,可以向父类传递参数**<br />**缺点**:
  2. - 实例并不是父类的实例,只是子类的实例 【参见28行】
  3. - **只能**继承父类的实例属性和方法,**不能**继承原型属性/方法 【参见29行】
  4. - **无法实现函数复用**,每个子类都有父类实例函数的副本,影响性能 【参见33行】
  5. <a name="BOA1S"></a>
  6. ## 组合继承
  7. **基本思想**:使用**原型链**继承使用对原型属性和方法的继承,通过**构造函数**继承来实现对实例属性的继承。这样既能通过在原型上定义方法实现函数复用,又能保证每个实例都有自己的属性。
  8. **通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用**

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

  1. ![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. - **优点**:**弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法,不存在引用属性共享问题,可传参,可复用**
  3. - **缺点**:
  4. - 调用了两次父类构造函数,生成了**两份实例**(子类实例将子类原型上的那份屏蔽了)
  5. <a name="9WBDy"></a>
  6. ## 寄生组合继承
  7. 在组合继承中,调用了两次父类构造函数,这里 **通过通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点**
  8. **主要思想**:借用 **构造函数** 继承 **属性** ,通过 **原型链的混成形式** 来继承 **方法**

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();

  1. ![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)
  2. Object.create()

Object.create(proto,[propertiesObject])

  1. - proto:新创建对象的原型对象
  2. - propertiesObject:可选。要添加到新对象的**可枚举**(新添加的属性是其自身的属性,而不是其原型链上的属性)的属性。

const Girl = { name: “guanqingchao”, age: 22 };

const girl = Object.create(Girl);

console.log(‘girl’, girl.proto);

  1. 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 />**优点**:
  2. - 只调用一次 `SuperType` 构造函数,只创建一份父类属性
  3. - 原型链保持不变
  4. - 能够正常使用 `instanceof` `isPrototypeOf`
  5. <a name="Y1a5j"></a>
  6. ## 原型式继承
  7. **实现思路就是将子类的原型设置为父类的原型**<br />**实现:**
  8. - `SuperType.call` 继承实例属性方法
  9. - `Object.create()` 来继承原型属性与方法
  10. - 修改 `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);

  1. 如果希望能 **多继承 **,可使用 **混入** 的方式

// 父类 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()

  1. <a name="bKOBH"></a>
  2. # Object.create vs Object.setPrototypeOf
  1. function Animal (name,sound) {
  2. this.name = name
  3. this.sound = sound
  4. }
  5. Animal.prototype.shout = function () {
  6. console.log(this.name + this.sound)
  7. }
  8. let dog = new Animal('pipi','wang!wang!')
  9. // 定义Plants
  10. function Plants (name) {
  11. this.name = name
  12. this.sound = null
  13. }
  14. // 函数接收参数用于区分
  15. Plants.prototype.shout = function (xssss) {
  16. console.log(this.name + this.sound +'plants tag')
  17. }
  18. Plants.prototype.genO2 = function () {
  19. console.log(this.name + '生成氧气。')
  20. }
  1. ![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)
  2. <a name="BXTJP"></a>
  3. ## Object.setPrototypeOf

Object.setPrototypeOf(obj, prototype) => obj.proto = prototype

obj 要设置其原型的对象。 prototype 该对象的新原型(一个对象 或 null).

  1. ```
  2. Object.setPrototypeOf(Animal.prototype,Plants.prototype)
  3. dog.shout() // pipi wang!wang!
  4. cat.shout() // mimi miao~miao~
  5. cat.genO2() // mimi 生成氧气。

使用Object.setPrototypeOf则会将Animal.prototype将会指向Animal原有的prototype,然后这个prototype的prototype再指向Plants的prototytpe。所以我们优先访问的Animal,然后再是plants。

image.png

Object.create

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的proto
返回一个新对象,带着指定的原型对象和属性。

  1. Object.create(proto,[propertiesObject])
  2. proto
  3. 新创建对象的原型对象。
  4. propertiesObject
  5. 可选。需要传入一个对象,该对象的属性类型参照Object.defineProperties()的第二个参数。
  6. 如果该参数被指定且不为 undefined,该传入对象的自有可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)将为新创建的对象添加指定的属性值和对应的属性描述符。
  7. const person = {
  8. isHuman: false,
  9. printIntroduction: function() {
  10. console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
  11. }
  12. };
  13. const me = Object.create(person);
  14. me.name = 'Matthew'; // "name" is a property set on "me", but not on "person"
  15. me.isHuman = true; // inherited properties can be overwritten
  16. me.printIntroduction();
  1. Animal.prototype = Object.create(Plants.prototype)
  2. let cat = new Animal('mimi','miao~miao~')
  3. dog.shout() // pipi wang!wang!
  4. cat.shout() // mimi miao~miao~
  5. cat.genO2() // mimi 生成氧气。

使用Object.create,Animal.prototype将会指向一个空对象,空对象的原型属性指向Plants的prototytpe。所以我们不能再访问Animal的原有prototypoe中的属性。Object.create的使用方式也凸显了直接重新赋值。

image.png

ES6类和继承

  1. class User {
  2. static _age = 30;
  3. _name = "Chaochao"; //实例的属性也可以定义在顶层,原型上无法访问
  4. //constructor中定义实例的属性
  5. constructor(name, age) {
  6. this.name = name;
  7. this.age = age;
  8. this.fruite = "apple";
  9. }
  10. //User.prototype 原型上的方法
  11. say() {
  12. console.log(
  13. "你好, 我是" + this._name + this.name + ",我" + this.age + "岁"
  14. );
  15. }
  16. //静态方法
  17. static eat() {
  18. console.log("I want to eat");
  19. }
  20. }
  21. class Teacher extends User {
  22. constructor(name, age, job) {
  23. super(name, age); // super 相当于属性的继承 替换了 User.call(this, name, age) 等同于调用父类的constructor(name, age)
  24. this.job = job;
  25. }
  26. teach() {
  27. console.log("My job is", this.job);
  28. }
  29. }
  30. // ===== 静态方法/属性调用 =====
  31. User.eat();
  32. Teacher.eat(); //父类的静态方法可以被子类继承
  33. console.log(User._age, Teacher._age);
  34. const teacher = new Teacher("Nana", 30, "teacher");
  35. console.log(typeof User); // function 类的本质是函数
  36. console.log(User.prototype.constructor === User); // true 对象的constructor()属性,直接指向“类”的本身,这与 ES5 的行为是一致的。
  37. console.log(Object.keys(User.prototype)); //[] 类的内部所有定义的方法,都是不可枚举的 ES5 的行为不一致
  38. teacher.say();
  39. teacher.teach();
  40. //teacher.eat(); //静态方法不可以被继承

constructor() 方法是类的默认方法,定义实例对象的属性。通过new命令生成对象实例时,自动调用该方法。constructor()方法默认返回实例对象,

受保护的属性

以下划线_开头。 属于实例的,prototype上无法访问

静态static:

静态属性和方法 :相当于Class.staticProp ,this指向Clas

如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。

  • 如果静态方法包含this关键字,这个this指的是类
  • 父类的静态方法,可以被子类继承。

静态属性 static
静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。

  • 类直接访问,实例不可以访问
  • 可以被子类继承


私有方法和属性

通常#开头
私有方法和私有属性,是只能在类的内部访问this.#XX的方法和属性,外部不能访问。
实例和类都不能访问

  1. class Person {
  2. static country = 'China'; //静态属性 只能类访问 实例不能继承
  3. #job = '私有变量JOB'; //Private私有变量 只能在本类中访问
  4. _age = '受保护变量年龄'; //仅实例例可以访问 原型上没有 可以被继承
  5. constructor(name) {
  6. this.name = name;
  7. }
  8. sayHi() {
  9. this.#handleJob();
  10. console.log(`Hi: ${this.name}`);
  11. }
  12. #handleJob() {
  13. console.log(this.#job);
  14. }
  15. }
  16. class Man extends Person {
  17. constructor(name, sex) {
  18. super(name); //
  19. this.sex = sex;
  20. }
  21. walk() {
  22. super.sayHi();
  23. console.log(this.name + this.sex);
  24. }
  25. }
  26. const man = new Man('男人', 'man');
  27. 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中调用

  1. class A {}
  2. class B extends A {
  3. constructor() {
  4. super(); // this指向B的实例 代表 A的constructor
  5. }
  6. }

2.作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。

  1. class Person {
  2. static country = 'China'; //静态属性 只能类访问 实例不能继承
  3. #job = '私有变量JOB'; //Private私有变量 只能在本类中访问
  4. _age = '受保护变量年龄'; //仅实例例可以访问 原型上没有 可以被继承
  5. constructor(name) {
  6. this.name = name;
  7. }
  8. sayHi() {
  9. this.#handleJob();
  10. console.log(`Hi: ${this.name}`);
  11. }
  12. #handleJob() {
  13. console.log(this.#job);
  14. }
  15. }
  16. class Man extends Person {
  17. constructor(name, sex) {
  18. super(name); //
  19. this.sex = sex;
  20. }
  21. walk() {
  22. super.sayHi(); //Person.prototype.sayHi 指向父类的原型对象 只能获取到原型上的方法 实例属性和方法获取不到
  23. console.log(this.name + this.sex);
  24. }
  25. }
  26. const man = new Man('男人', 'man');
  27. man.walk();

extends

继承的核心代码如下,其实现和上述的寄生组合式继承方式一样

  1. function _inherits(subType, superType) {
  2. // 创建对象,Object.create 创建父类原型的一个副本
  3. // 增强对象,弥补因重写原型而失去的默认的 constructor 属性
  4. // 指定对象,将新创建的对象赋值给子类的原型 subType.prototype
  5. subType.prototype = Object.create(superType && superType.prototype, {
  6. constructor: { // 重写 constructor
  7. value: subType,
  8. enumerable: false,
  9. writable: true,
  10. configurable: true
  11. }
  12. });
  13. if (superType) {
  14. Object.setPrototypeOf
  15. ? Object.setPrototypeOf(subType, superType)
  16. : subType.__proto__ = superType;
  17. }
  18. }

与ES5区别

  • 类调用必须用new,ES不用new也可以调用
  • 类不存在变量提升
  • 类的内部所有定义的方法,都是不可枚举的

    Mixin模式

    有上面可以看到,JS中我们只能单继承。

例如,我有一个 StreetSweeper 类和一个 Bicycle 类,现在想要一个它们的 mixin:StreetSweepingBicycle 类。

或者,我们有一个 User 类和一个 EventEmitter 类来实现事件生成(event generation),并且我们想将 EventEmitter 的功能添加到 User 中,以便我们的用户可以触发事件(emit event)。

有一个概念可以帮助我们,叫做 “mixins” 根据维基百科的定义,mixin 是一个包含可被其他类使用而无需继承的方法的类。

在 JavaScript 中构造一个 mixin 最简单的方式就是构造一个拥有实用方法的对象,以便我们可以轻松地将这些实用的方法合并Object.assign()到任何类的原型中。

  1. let sayMixin = {
  2. say(phrase) {
  3. alert(phrase);
  4. }
  5. };
  6. let sayHiMixin = {
  7. __proto__: sayMixin, // (或者,我们可以在这儿使用 Object.create 来设置原型)
  8. sayHi() {
  9. // 调用父类方法
  10. super.say(`Hello ${this.name}`); // (*)
  11. },
  12. sayBye() {
  13. super.say(`Bye ${this.name}`); // (*)
  14. }
  15. };
  16. class User {
  17. constructor(name) {
  18. this.name = name;
  19. }
  20. }
  21. // 拷贝方法
  22. Object.assign(User.prototype, sayHiMixin);
  23. // 现在 User 可以打招呼了
  24. new User("Dude").sayHi(); // Hello Dude!

实战

  1. let eventMixin = {
  2. /**
  3. * 订阅事件,用法:
  4. * menu.on('select', function(item) { ... }
  5. */
  6. on(eventName, handler) {
  7. if (!this._eventHandlers) this._eventHandlers = {};
  8. if (!this._eventHandlers[eventName]) {
  9. this._eventHandlers[eventName] = [];
  10. }
  11. this._eventHandlers[eventName].push(handler);
  12. },
  13. /**
  14. * 取消订阅,用法:
  15. * menu.off('select', handler)
  16. */
  17. off(eventName, handler) {
  18. let handlers = this._eventHandlers?.[eventName];
  19. if (!handlers) return;
  20. for (let i = 0; i < handlers.length; i++) {
  21. if (handlers[i] === handler) {
  22. handlers.splice(i--, 1);
  23. }
  24. }
  25. },
  26. /**
  27. * 生成具有给定名称和数据的事件
  28. * this.trigger('select', data1, data2);
  29. */
  30. trigger(eventName, ...args) {
  31. if (!this._eventHandlers?.[eventName]) {
  32. return; // 该事件名称没有对应的事件处理程序(handler)
  33. }
  34. // 调用事件处理程序(handler)
  35. this._eventHandlers[eventName].forEach(handler => handler.apply(this, args));
  36. }
  37. };
  38. class Menu {
  39. choose(value) {
  40. this.trigger("select", value);
  41. }
  42. }
  43. // 添加带有事件相关方法的 mixin
  44. Object.assign(Menu.prototype, eventMixin);
  45. let menu = new Menu();
  46. // 添加一个事件处理程序(handler),在被选择时被调用:
  47. menu.on("select", value => alert(`Value selected: ${value}`));
  48. // 触发事件 => 运行上述的事件处理程序(handler)并显示:
  49. // 被选中的值:123
  50. menu.choose("123")

练习

1.Animal类 有A,B两个方法,Dog继承Animal,且仅仅继承A方法

  1. //Animal类 有A,B两个方法
  2. function Animal(name) {
  3. this.name = name;
  4. }
  5. Animal.prototype.A = function() {
  6. console.log(`animal is ${this.name}`);
  7. };
  8. Animal.prototype.B = function() {
  9. console.log(`animal B func`);
  10. };
  11. //Dog类 只继承Animal的A方法
  12. function Dog(name, color) {
  13. Animal.call(this, name);
  14. this.color = color;
  15. }
  16. Dog.prototype.A = Animal.prototype.A;
  17. // Dog.prototype = new Animal();
  18. // Dog.prototype.constructor = Dog;
  19. Dog.prototype.Bark = function() {
  20. console.log(`${this.color} ${this.name} is barking`);
  21. };
  22. let dog = new Dog("dog", "balck");
  23. dog.A();
  24. dog.Bark();
  25. //cat类 只继承Dog的Bark方法
  26. function Cat(name,color,age){
  27. Dog.call(this,name,color);
  28. this.age = age||0
  29. }
  30. Cat.prototype = new Dog();
  31. Cat.prototype.sayAge = function(){
  32. console.log(`Age is ${this.age} years old`)
  33. }
  34. let cat = new Cat('cat','white',2)
  35. cat.A();
  36. cat.Bark();
  37. cat.sayAge();