14、工厂函数
专门用于创建对象的函数,我们称为工厂函数。
function createPerson(name, age) {
let obj = new Object()
obj.name = name
obj.age = age
obj.say = function() {
console.log('世界杯我来了!')
}
return obj
}
// 使用方式
let obj1 = createPerson('梅西', 35)
let obj2 = createPerson('内马尔', 30)
15、构造函数
构造函数和工厂函数一样,都是专门用于创建对象的
构造函数的本质是工厂函数的简写
构造函数和工厂函数的区别在于:
- 构造函数的函数名称首字母必须大写
- 构造函数的只能通过new关键字来调用
- 构造函数使用了this
- 构造函数不需要return
function Person(name, age){
this.name = name
this.age = age
this.say = function() {
console.log('我们是冠军!')
}
}
// 使用方式
let obj1 = new Person('梅西', 35)
let obj2 = new Person('C罗', 38)
new Person('梅西', 35)系统给我们做了什么事情?
// 1.创建一个空对象
let obj = {}
// 2.将空对象的原型指向构造函数的原型
obj.__proto__ = Person.prototype
// 3.将this指向空对象
Person.call(obj)
// 4.返回对象
return obj
结果和工厂函数非常相似
构造函数优化
let obj1 = new Person('梅西', 35)
let obj2 = new Person('C罗', 38)
console.log(obj1.say === obj2.say) // 输出:false
因为两个对象中的say方法的实现方式都是一样的,但是保存到了不同的存储空间中,所以有性能问题
解决方案:利用prototype解决
function Person(name, age){
this.name = name
this.age = age
}
Person.prototype = {
say: function() {
console.log('我们是冠军!')
}
}
let obj1 = new Person('梅西', 35)
let obj2 = new Person('C罗', 38)
console.log(obj1.say === obj2.say) // 输出:true
16、prototype
特点
- 存储在prototype中的方法可以被对应构造函数创建出来的所有对象共享
- prototype中除了可以存储方法以外,还可以存储属性
- prototype如果出现了和构造函数中同名的属性或者方法,对象在访问的时候,访问到的是构造函数中的数据
function Person(name, age){
this.name = name
this.age = age
this.country = '中国'
say: function() {
console.log('我在构造函数中')
}
}
Person.prototype = {
// 为了不破坏原有的关系,在给prototype赋值的时候,需要在自定义的对象中手动添加constructor属性,手动的指定需要指向谁
constructor: Person,
country: '阿根廷',
say: function() {
console.log('我们是冠军!')
}
}
let obj1 = new Person('梅西', 35)
obj1.say() // 输出:我在构造函数中
console.log(obj1.country) // 输出:中国
prototype中一般情况下用于存储所有对象都相同的一些属性以及方法,如果是对象特有的属性或者方法,我们会存储到构造函数中
17、对象三角恋关系
- 每个构造函数中都有一个默认的属性,叫做prototype,prototype属性保存着一个对象,这个对象我们称之为原型对象
- 每个原型对象中都有一个默认的属性,叫做constructor,constructor指向当前原型对象对应的那个构造函数
- 通过构造函数创建出来的对象我们称之为实例对象,每个实例对象中都有一个默认的属性,叫做__proto__,__proto__指向创建它的那个构造函数的原型对象,即构造函数prototype===实例对象.proto
function Person(name, age){
this.name = name
this.age = age
this.country = '中国'
this.say = function() {
console.log('我在构造函数中')
}
}
console.log(Person.prototype);
console.log(Person.prototype.constructor);
console.log(obj1.__proto__);
console.log(Person.prototype === obj1.__proto__); // 输出:true
console.log(Person.prototype.constructor === obj1.constructor); // 输出:true
18、构造函数私有属性
function Person(name, age){
this.name = name
this.age = age
this.say = function() {
console.log('你好!')
}
}
const person = new Person('张三', 24)
person.age = -20
console.log(person.age) // 输出:-20
// 构造函数的属性有些应该能支持修改,有些应该不能支持修改,或者说不能支持改成某些错误的值,比如年龄不能小于0
function Person(name, age) {
this.name = name
let ageValue = age
// 增加set,get方法,类似于闭包
this.setAge = function(myAge) {
if(myAge >= 0){
ageValue = myAge
}
}
this.getAge = function() {
return ageValue
}
this.say = function() {
console.log('你好!')
}
}
const person = new Person('张三', 24)
person.ageValue = -20 // 报错
person.setAge(10)
person.setAge(-20) // 无效
console.log(person.getAge()) // 输出:10
19、原型链继承
function Person() {
this.name = '张三'
this.age = 24
this.say = function() {
console.log('你好!')
}
}
function Student() {
this.school = '南京大学'
}
// 关键:创建Person的实例,并将这个实例赋值给Student的prototype
Student.prototype = new Person()
const student = new Student()
// 能用Person的属性和方法
console.log(student.name) // 输出:张三
console.log(student.age) // 输出:24
student.say() // 输出:你好!
// 也能用Student的属性和方法
console.log(student.school) // 输出:南京大学
缺点:
function Person() {
this.list = [1, 2, 3, 4]
}
function Student() {
this.school = '南京大学'
}
// 实现原型链继承
Student.prototype = new Person()
// 创建两个学生
const student1 = new Student()
const student2 = new Student()
// 对student1的list进行操作
student1.list.push(-1)
console.log(student1.list) // 输出:[1, 2, 3, 4, -1]
console.log(student2.list) // 输出:[1, 2, 3, 4, -1]
- 多个原型指向同一个实例,当有多个实例化子对象时,修改一个会影响其他对象
- 无法对父类进行传参,即无法对Person进行传参
20、bind、call、apply
- bind
-
bind方法可以修改this的指向
-
bind方法返回的是修改之后的新的函数,所以需要重新调用
-
bind方法可以传递参数,第一个参数是this指向的对象,其他的参数需要写在this对象后面
let obj = {
name: '张三'
}
function test(a, b){
console.log(a, b)
console.log(this)
}
test.bind(obj, 11, 20)()
// console.log(a, b) 输出:11 20
// console.log(this) 输出的是上面定义的obj对象
- call
- call方法可以修改this的指向
- call方法会立即调用修改之后的函数
- call方法可以传递参数,第一个参数是this指向的对象,其他的参数需要写在this对象后面
let obj = {
name: '张三'
}
function test(a, b){
console.log(a, b) // 输出:11 20
console.log(this) // 输出的是上面定义的obj对象
}
test.call(obj, 11, 20)
- apply
- apply方法可以修改this的指向
- apply方法会立即调用修改之后的函数
- apply方法可以传递参数,第一个参数是this指向的对象,其他的参数按数组的方式传递
let obj = {
name: '张三'
}
function test(a, b){
console.log(a, b) // 输出:11 20
console.log(this) // 输出的是上面定义的obj对象
}
test.apply(obj, [11, 20])
21、借用构造函数继承
function Person(name, age) {
this.name = name
this.age = age
this.say = function() {
console.log('你好!')
}
}
function Student(name, age) {
this.school = '南京大学'
// 改变this的指向
Person.call(this, name, age)
}
const student = new Student('张三', 24)
console.log(student.name) // 输出:张三
console.log(student.age) // 输出:24
student.say() // 输出:你好!
// 也能用Student的属性和方法
console.log(student.school) // 输出:南京大学
缺点:
function Person() {
this.name = '张三'
}
Person.prototype.say = function () {
console.log('你好!')
}
function Student(name, age) {
this.school = '南京大学'
// 改变this的指向
Person.call(this, name, age)
}
const student = new Student()
student.say() // 报错
- 只能继承父类的实例属性和方法,不能继承原型属性/方法
- 无法实现复用,每个子类都有父类实例函数的副本,影响性能
22、组合继承
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.say = function () {
console.log('你好!')
}
function Student(name, age) {
this.school = '南京大学'
// 改变this的指向
Person.call(this, name, age)
}
// 关键
Student.prototype = new Person()
// 重写constructor,指向自己的构造函数
Student.prototype.constructor = Student
const student = new Student('张三', 24)
console.log(student.name) // 输出:张三
console.log(student.age) // 输出:24
student.say() // 输出:你好!
// 也能用Student的属性和方法
console.log(student.school) // 输出:南京大学
缺点:
- 无论什么情况下都会调用两次超类型,即父类构造函数被调用两次