1 对象和函数的原型
2 new、constructor
3 原型链的查找顺序
4 原型链实现的继承
5 借用构造函数继承
6 寄生组合实现继承
function 创建的名称如果开头是大写的,那这个创建的不是函数,是创建了类。
要注意区分!本章很多这样子的类。
实例有隐式的原型,声明的函数或者对象有显示和隐式的原型。显式原型里面有constructor和隐式原型。 各对象间的关系如下图。
普通对象的原型
对象的原型就是下图的Prototype,一般会被隐藏
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> var obj = { name: "why", age: 18 } console.log(obj) var info = {} // 获取对象的原型 console.log(obj.name, obj.age) console.log(obj.__proto__) console.log(Object.getPrototypeOf(obj)) console.log(obj.__proto__ === Object.getPrototypeOf(obj)) // true // 疑问: 这个原型有什么用呢? // 当我们通过[[get]]方式获取一个属性对应的value时 // 1> 它会优先在自己的对象中查找, 如果找到直接返回 // 2> 如果没有找到, 那么会在原型对象中查找 console.log(obj.name) // 在原型中添加一个参数,开发时候不建议这样子用 obj.__proto__.message = "Hello World" console.log(obj.message) </script> </body> </html>
函数对象的原型
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> var obj = {} function foo() {} // 1.将函数看成是一个普通的对象时, 它是具备__proto__(隐式原型) // 作用: 查找key对应的value时, 会找到原型身上 // console.log(obj.__proto__) // console.log(foo.__proto__) // 2.将函数看成是一个函数时, 它是具备prototype(显式原型) // 作用: 用来构建对象时, 给对象设置隐式原型的 console.log(foo.prototype) // console.log(obj.prototype) 对象是没有prototype </script> </body> </html>
new操作原型赋值
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> function Foo() { // 1.创建空的对象 // 2.将Foo的prototype原型(显式隐式)赋值给空的对象的__proto__(隐式原型) } console.log(Foo.prototype) var f1 = new Foo() var f2 = new Foo() var f3 = new Foo() var f4 = new Foo() var f5 = new Foo() console.log(f1.__proto__) console.log(f1.__proto__ === Foo.prototype) // true console.log(f3.__proto__ === f5.__proto__) // true </script> </body> </html>
将方法放在原型上
new操作的作用很重要。注意:定义的一个函数如果里面有几个函数,通过new来创建函数的话,就会创建函数个数*定义的函数里面的函数个数。这些函数很多重复的,浪费空间;所以使用原型的办法来作为应对的办法。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> /* 1.什么是函数的显式原型 * 区分和对象原型区别 2.函数的原型的作用 * 在通过new操作创建对象时, 将这个显式原型赋值给创建出来对象的隐式原型 3.案例Person, 将所有的函数定义放到了显式原型上 */ function Student(name, age, sno) { this.name = name this.age = age this.sno = sno // 1.方式一: 编写函数, 会创建很多个函数对象 // this.running = function() { // console.log(this.name + " running") // } // this.eating = function() { // console.log(this.name + " eating") // } // this.studying = function() { // console.log(this.name + " studying") // } } // 当我们多个对象拥有共同的值时, 我们可以将它放到构造函数对象的显式原型 // 由构造函数创建出来的所有对象, 都会共享这些属性 Student.prototype.running = function() { console.log(this.name + " running") } Student.prototype.eating = function() { console.log(this.name + " eating") } // 1.创建三个学生 var stu1 = new Student("why", 18, 111) var stu2 = new Student("kobe", 30, 112) var stu3 = new Student("james", 18, 111) // 隐式原型的作用 // 1> stu1的隐式原型是谁? Student.prototype对象 // 2> stu1.running查找: // * 先在自己身上查找, 没有找到 // * 去原型去查找 stu1.running() stu2.eating() </script> </body> </html>
显式原型中的属性
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> // 非常重要的属性: constructor, 指向Person函数对象 function Person() { } // 1.对constructor在prototype上的验证 var PersonPrototype = Person.prototype console.log(PersonPrototype) console.log(PersonPrototype.constructor) console.log(PersonPrototype.constructor === Person) console.log(Person.name) console.log(PersonPrototype.constructor.name) // 2.实例对象p var p = new Person() console.log(p.__proto__.constructor) console.log(p.__proto__.constructor.name) </script> </body> </html>
创建对象过程内存,具体内存图看ppt
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> function Person(name, age) { this.name = name this.age = age } Person.prototype.running = function() { console.log("running~") } var p1 = new Person("why", 18) var p2 = new Person("kobe", 30) // 进行操作 console.log(p1.name) console.log(p2.name) p1.running() p2.running() // 新增属性 Person.prototype.address = "中国" p1.__proto__.info = "中国很美丽!" p1.height = 1.88 p2.isAdmin = true // 获取属性 console.log(p1.address) console.log(p2.isAdmin) console.log(p1.isAdmin) console.log(p2.info) // 修改address p1.address = "广州市" console.log(p2.address) </script> </body> </html>
重写函数原型对象
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> function Person() { } console.log(Person.prototype) // 在原有的原型对象上添加新的属性 // Person.prototype.message = "Hello Person" // Person.prototype.info = { name: "哈哈哈", age: 30 } // Person.prototype.running = function() {} // Person.prototype.eating = function() {} // console.log(Person.prototype) // console.log(Object.keys(Person.prototype)) // 直接赋值一个新的原型对象,把原来的替换掉 Person.prototype = { message: "Hello Person", info: { name: "哈哈哈", age: 30 }, running: function() {}, eating: function() {}, // 加上constructor就和原本的原型差不多一样了 // 但是原来的constructor是不可枚举的Object.keys枚举不到,所以还得改不可枚举 // constructor: Person } // 通过下面对象属性描述符来给原型添加对应的constructor Object.defineProperty(Person.prototype, "constructor", { enumerable: false, configurable: true, writable: true, value: Person }) console.log(Object.keys(Person.prototype)) // 新建实例对象 var p1 = new Person() console.log(p1.message) </script> </body> </html>
重要-对象的原型链
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> // 1.{}的本质 // var info = {} // 相当于 // var info = new Object() // console.log(info.__proto__ === Object.prototype) // 2.原型链 var obj = { name: "why", age: 18 } // 查找顺序 // 1.obj上面查找 // 2.obj.__proto__上面查找 // 3.obj.__proto__.__proto__ -> null 上面查找(undefined) // console.log(obj.message) // 3.对现有代码进行改造 obj.__proto__ = { // message: "Hello aaa" } obj.__proto__.__proto__ = { message: "Hello bbbb" } obj.__proto__.__proto__.__proto__ = { message: "Hello ccc" } console.log(obj.message) </script> </body> </html>
原型链实现方法的继承
这里讲的是是如何让student这个函数在自己的属性找不到、在原型什么找不到。想办法让student去person函数的原型上面找需要的属性。
错误方法一:将父类的原型直接给子类(不要这样子做)
方式二:也不推荐,因为问题也挺多的,依赖父函数里面的属性,而且多个实例创建的属性会共用
方式三借用构造函数属性继承,看下一个大标题的代码。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> // 定义Person构造函数(类) function Person(name, age, height, address) { this.name = name this.age = age this.height = height this.address = address } Person.prototype.running = function() { console.log("running~") } Person.prototype.eating = function() { console.log("eating~") } // 定义学生类 function Student(name, age, height, address, sno, score) { this.name = name this.age = age this.height = height this.address = address this.sno = sno this.score = score } // 方式一: 父类的原型直接赋值给子类的原型 // 缺点: 父类和子类共享通一个原型对象, 修改了任意一个, 另外一个也被修改 // Student.prototype = Person.prototype // 方式二: 创建一个父类的实例对象(new Person()), 用这个实例对象来作为子类的原型对象 var p = new Person("why", 18) Student.prototype = p // Student.prototype.running = function() { // console.log("running~") // } // Student.prototype.eating = function() { // console.log("eating~") // } Student.prototype.studying = function() { console.log("studying~") } // 创建学生 var stu1 = new Student("kobe", 30, 111, 100) var stu2 = new Student("james", 25, 111, 100) stu1.running() stu1.studying() console.log(stu1.name, stu1.age) console.log(stu1) console.log(stu2.name, stu2.age) </script> </body> </html>
借用构造函数属性继承--重要
还是有缺点,
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> // 定义Person构造函数(类) function Person(name, age, height, address) { this.name = name this.age = age this.height = height this.address = address } Person.prototype.running = function() { console.log("running~") } Person.prototype.eating = function() { console.log("eating~") } // 定义学生类 function Student(name, age, height, address, sno, score) { // 重点: 借用构造函数 Person.call(this, name, age, height, address) // this.name = name // this.age = age // this.height = height // this.address = address this.sno = sno this.score = score } // 方式一: 父类的原型直接赋值给子类的原型 // 缺点: 父类和子类共享通一个原型对象, 修改了任意一个, 另外一个也被修改 // Student.prototype = Person.prototype // 方式二: 创建一个父类的实例对象(new Person()), 用这个实例对象来作为子类的原型对象 var p = new Person("why", 18) Student.prototype = p // Student.prototype.running = function() { // console.log("running~") // } // Student.prototype.eating = function() { // console.log("eating~") // } Student.prototype.studying = function() { console.log("studying~") } // 创建学生 var stu1 = new Student("kobe", 30, 111, 100) var stu2 = new Student("james", 25, 111, 100) stu1.running() stu1.studying() console.log(stu1.name, stu1.age) console.log(stu1) console.log(stu2.name, stu2.age) </script> </body> </html>
创建原型对象的方法
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> // 工具函数 // 创建对象的过程 function createObject(o) { function F() {} F.prototype = o return new F() } // 将Subtype和Supertype联系在一起 // 寄生式函数 function inherit(Subtype, Supertype) { Subtype.prototype = createObject(Supertype.prototype) Object.defineProperty(Subtype.prototype, "constructor", { enumerable: false, configurable: true, writable: true, value: Subtype }) } /* 满足什么条件: 1.必须创建出来一个对象 2.这个对象的隐式原型必须指向父类的显式原型 3.将这个对象赋值给子类的显式原型 */ function Person(name, age, height) {} function Student() {} inherit(Student, Person) // 1.之前的做法: 但是不想要这种做法 // var p = new Person() // Student.prototype = p // 2.方案一: var obj = {} // obj.__proto__ = Person.prototype Object.setPrototypeOf(obj, Person.prototype) Student.prototype = Person.prototype // 3.方案二: // function F() {} // F.prototype = Person.prototype // Student.prototype = new F() // 4.方案三: var obj = Object.create(Person.prototype) console.log(obj.__proto__ === Person.prototype) Student.prototype = obj </script> </body> </html>
最终继承的方案写法
创建一个工具inherit_utils.js文件,内容如下:
// 创建对象的过程 function createObject(o) { function F() {} F.prototype = o return new F() } // 将Subtype和Supertype联系在一起 // 寄生式函数 function inherit(Subtype, Supertype) { // Subtype.prototype.__proto__ = Supertype.prototype // Object.setPrototypeOf(Subtype.prototype, Subtype.prototype) Subtype.prototype = createObject(Supertype.prototype) Object.defineProperty(Subtype.prototype, "constructor", { enumerable: false, configurable: true, writable: true, value: Subtype }) Object.setPrototypeOf(Subtype, Supertype) // Subtype.__proto__ = Supertype }
在真正使用工具的地方引入工具:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script src="./js/inherit_utils.js"></script> <script> // 寄生组合式继承 // 原型链/借用/原型式(对象之间)/寄生式函数 function Person(name, age, height) { this.name = name this.age = age this.height = height } Person.prototype.running = function() { console.log("running~") } Person.prototype.eating = function() { console.log("eating~") } function Student(name, age, height, sno, score) { Person.call(this, name, age, height) this.sno = sno this.score = score } inherit(Student, Person) Student.prototype.studying = function() { console.log("studying") } // 创建实例对象 var stu1 = new Student("why", 18, 1.88, 111, 100) </script> </body> </html>
ES5-原型-寄生式继承方案
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> // 对象之间的继承 // 默认的对象 var obj = { name: "why", age: 18 } // 原型式继承 function createObject(o) { function F() {} F.prototype = o return new F() } // 寄生式函数 function createInfo(o, name, age, height, address) { var newObj = createObject(o) newObj.name = name newObj.age = age newObj.height = height newObj.address = address return newObj } // 创建另外一个对象, 这个对象可以继承自obj // var info1 = createObject(obj) // info1.height = 1.88 // info1.address = "广州市" // var info2 = createObject(obj) // info1.height = 1.88 // info1.address = "广州市" // var info3 = createObject(obj) // info1.height = 1.88 // info1.address = "广州市" // 创建一系列对象 var info1 = createInfo(obj, "why", 18, 1.88, "广州市") var info2 = createInfo(obj, "kobe", 30, 1.98, "洛杉矶市") </script> </body> </html>
ES5-Object是其他类的父类
也就是说只要是对象类型的,就能调用Object里面的函数。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script src="./js/inherit_utils.js"></script> <script> function Person() {} function Student() {} function Teacher() {} inherit(Student, Person) console.log(Person.prototype.__proto__ === Object.prototype) // 在Object的原型上添加属性 Object.prototype.message = "coderwhy" var stu = new Student() console.log(stu.message) // Object原型上本来就已经存放一些方法 console.log(Object.prototype) console.log(stu.toString()) // 函数对象也是最终继承自Object function foo() {} console.log(foo.message) </script> </body> </html>
ES5-对象判断方法补充
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script src="./js/inherit_utils.js"></script> <script> var obj = { name: "why", age: 18 } var info = createObject(obj) info.address = "中国" info.intro = "中国大好河山" console.log(info.name, info.address) console.log(info) // 1.hasOwnProperty // console.log(info.hasOwnProperty("name")) // false // console.log(info.hasOwnProperty("address")) // true // 2.in操作符 console.log("name" in info) console.log("address" in info) // 注意: for in遍历不仅仅是自己对象上的内容, 也包括原型对象上的内容 for (var key in info) { console.log(key) } // 3.instanceof // instanceof用于判断对象和类(构造函数)之间的关系 function Person() {} function Student() {} inherit(Student, Person) // stu实例(instance)对象 var stu = new Student() console.log(stu instanceof Student) console.log(stu instanceof Person) console.log(stu instanceof Object) console.log(stu instanceof Array) // 4.isPrototypeOf console.log(Student.prototype.isPrototypeOf(stu)) console.log(Person.prototype.isPrototypeOf(stu)) // 可以用于判断对象之间的继承 console.log(obj.isPrototypeOf(info)) </script> </body> </html>
ES5-构造函数的类方法
添加到原型上面的方法叫实例方法,添加到类上面的叫类方法。
没有创建实例的情况下,不能使用实例方法,但是可以使用类方法。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> // var obj = { // friend: { // play: function() { // console.log("play~") // } // } // } // obj.play() function Person(name, age) { this.name = name this.age = age } Person.totalCounter = "70亿" // 添加Person原型上的方法也成为 实例方法 Person.prototype.running = function() { console.log(this.name + "running~") } Person.prototype.eating = function() { console.log("eating~") } // 添加Person对象本身的方法成为 类方法 var names = ["abc", "cba", "nba", "mba"] Person.randomPerson = function() { var randomName = names[Math.floor(Math.random() * names.length)] return new Person(randomName, Math.floor(Math.random() * 100)) } // 实例对象 var p1 = new Person("why", 18) var p2 = new Person("kobe", 30) p1.running() // 没有实例对象的情况下, 能不能调用函数? 不可以调用 var p = Person.randomPerson() console.log(p) </script> </body> </html>