JavaScript随笔

53 阅读6分钟

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、构造函数

构造函数和工厂函数一样,都是专门用于创建对象的

构造函数的本质是工厂函数的简写

构造函数和工厂函数的区别在于:

  1. 构造函数的函数名称首字母必须大写
  2. 构造函数的只能通过new关键字来调用
  3. 构造函数使用了this
  4. 构造函数不需要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

特点

  1. 存储在prototype中的方法可以被对应构造函数创建出来的所有对象共享
  2. prototype中除了可以存储方法以外,还可以存储属性
  3. 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、对象三角恋关系

  1. 每个构造函数中都有一个默认的属性,叫做prototype,prototype属性保存着一个对象,这个对象我们称之为原型对象
  2. 每个原型对象中都有一个默认的属性,叫做constructor,constructor指向当前原型对象对应的那个构造函数
  3. 通过构造函数创建出来的对象我们称之为实例对象,每个实例对象中都有一个默认的属性,叫做__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]
  1. 多个原型指向同一个实例,当有多个实例化子对象时,修改一个会影响其他对象
  2. 无法对父类进行传参,即无法对Person进行传参

20、bind、call、apply

  1. 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对象
  1. 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)
  1. 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() // 报错
  1. 只能继承父类的实例属性和方法,不能继承原型属性/方法
  2. 无法实现复用,每个子类都有父类实例函数的副本,影响性能

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) // 输出:南京大学

缺点:

  1. 无论什么情况下都会调用两次超类型,即父类构造函数被调用两次