likes
comments
collection
share

javascript 构造函数和原型

作者站长头像
站长
· 阅读数 69

##在 ES6 之前我们都是通过下面这种构造函数的方法来创造对象的

1.利用构造函数来创建一个明星类

function Star(uname, age){
    this.name = uname
    this.age = age
    this.sing = sing(){
        console.log('唱歌')
    }
}
let star = new Star('刘德华', 38)

结果:javascript 构造函数和原型 构造函数是一种特殊的函数主要用来初始化对象,即为对象成员变量赋初始值,我们可以把对象中一些公共的属性和方法抽取出来然后封装到这个函数里面

注意:

  • 构造函数用于创建某一类对象,其首字母大写。
  • 构造函数要和 new 关键字一起使用才有意义。

构造函数执行的四件事情:

  1. 首先在内存中创建一个新的空对象。
  2. 让 this 指向这个新对象。
  3. 执行构造函数里面的代码,给这个新对象添加属性和方法。
  4. 返回这个新对象(所以构造函数里面是不需要写 return 的)

构造函数

javascript 的构造函数中可以添加一些成员,可以在构造函数本身上添加,也可以在构造函数内部的 this 上添加。通过这两种方式添加的成员,就分别称为静态成员实例成员

  • 静态成员:在构造函数本身上添加的成员称为静态成员,只能由构造函数本身访问。

  • 实例成员:在构造函数内部创建的对象成员称为实例成员,只能由实例化的对象来访问。

    function Star(uname, age){
        this.name = uname
        this.age = age
        this.sing = function (){
            console.log('唱歌')
        }
    }
    //实例化成员就是构造函数内部通过 this 添加的成员 name age sing就是实例成员
    //实例成员只能通过实例化的方式来访问。
    let ldh = new Star('刘德华', 18)
    console.log(ldh.name);
    console.log(ldh.age);
    ldh.sing()
    //不可以通过构造函数来访问实例成员
    //console.log(Star.name)
    
    //静态成员:在构造函数中本身上添加的成员。
    //此时 sex 就是静态成员
    Star.sex = '男'
    //静态成员只能通过构造函数来访问
    console.log(Star.sex)
    //不能通过对象来访问
    console.log(ldh.sex)
    

构造函数的问题

function Star(uname, age){
    this.name = uname
    this.age = age
    this.sing = function (){
        console.log('唱歌')
    }
}
let ldh = new Star('刘德华', 39)
let zxy = new Star('张学友', 34)
//因为函数属于复杂数据类型,内存会单独给这个函数开辟一个空间。上面的例子实例化了两个对象,就会在内存空开辟出两个存相同函数的空间,而且存放的地址是不一样的,所以说构造函数存在一个浪费资源的问题。
console.log(ldh.sing === zxy.sing);
  • 存在内存浪费问题

构造函数原型 prototype

function Star(uname, age){
    this.name = uname
    this.age = age
}
//我们把函数存储到 prototype 原型上,就可以调用,并且只会用这一个共享的函数,得以解节省内存资源。
Star.prototype.sing = function (){
    console.log('唱歌')
}
let ldh = new Star('刘德华', 39)
let zxy = new Star('张学友', 34)
//因为函数属于复杂数据类型,内存会单独给这个函数开辟一个空间。上面的例子实例化了两个对象,就会在内存空开辟出两个存相同函数的空间,而且存放的地址是不一样的,所以说构造函数存在一个浪费资源的问题。
console.log(ldh.sing === zxy.sing);

构造函数通过原型分配的函数是对象所共享的。 javascript 规定,每一个构造函数都已一个prototype 属性,指向另一个对象,注意这个 prototype 就是一个对象,这个对象的所有属性和方法,都会被构造杉树所拥有。

_proto_ 对象原型

javascript 构造函数和原型 注意:这里 sing 方法可以通过 ldh.sing() 这样调用是因为,在对象身上也有个对象原型 proto 它指向我们构造函数的原型对象 prototype,所以我们可以直接用 javascript 构造函数和原型 javascript 构造函数和原型

方法的查找规则:

  • 首先先看 ldh 对象身上是否有 sing 方法,如果有就执行这个对象上的 sing 方法。
  • 如果没有 sing 这个方法,因为有 __proto__这个对象的存在,就去构造函数原型对象 prototype 去查找 sing 这个方法

总结:原型就是构造函数中的一个对象,我们也称 prototype 或 原型对象。,它是每一个构造函数里面都会存在的一个对象。它的主要作用就是共享方法,只要我们把方法定义到原型对象上我们的所有实例对象就都可以调用这个方法,并且是共享的,不会开辟多余的空间来存放这个函数。

constructor 构造函数

对象原型( proto )和构造函数( prototype )原型对象里面都有一个 constructor 我们称为构造函数,因为它指回构造函数本身。

function Star(uname, age){
    this.name = uname
    this.age = age
}
//我们把函数存储到 prototype 原型上,就可以调用,并且只会用这一个共享的函数,得以解节省内存资源。
Star.prototype.sing = function (){
    console.log('唱歌')
}
let ldh = new Star('刘德华', 39)
/*console.log('Star.prototype:', Star.prototype);
console.log('ldh.__proto__:', ldh.__proto__);*/
console.log('Star.prototype:', Star.prototype.constructor);
console.log('ldh.__proto__:', ldh.__proto__.constructor);

打印结果(可以看到返回的是构造函数本身):

javascript 构造函数和原型 这里的 constructor 主要用于记录该对象引用那个构造函数,它可以让原型对象重新指向原来的构造函数。

一个小细节: 假设我们定义多个共享方法:

function Star(uname, age){
    this.name = uname
    this.age = age
}
//我们把函数存储到 prototype 原型上,就可以调用,并且只会用这一个共享的函数,得以解节省内存资源。
Star.prototype.sing = function (){
    console.log('唱歌')
}
Star.prototype.movie = function (){
    console.log('电影')
}
let ldh = new Star('刘德华', 39)
ldh.sing()
ldh.movie()
console.log('Star.prototype:', Star.prototype.constructor);
console.log('ldh.__proto__:', ldh.__proto__.constructor);

上面这个写法是没问题的。打印结果 javascript 构造函数和原型 但下面的做法就有问题

Star.prototype = {
    sing: function () {
        console.log('唱歌')
    },
    movie: function () {
        console.log('电影')
    }
}
let ldh = new Star('刘德华', 39)
ldh.sing()
ldh.movie()
console.log('Star.prototype:', Star.prototype.constructor);
console.log('ldh.__proto__:', ldh.__proto__.constructor);

虽然可以打印方法,但是我们可以看到 constructor 指向的原来的构造函数,变成了一个对象。 这理由语法问题,如果我们已 Star.prototype.constructor = sing() 的方法是在这个属性上修改操作, 而 Star.prototype = {sing()} 这里是相当于将对象赋值给了 Star.prototype。覆盖了原来的 constructor 所以就没有办法指回原来的 constructor 构造函数了 当然这里的正确解决办法就是我们手动加一个 constructor 让它指回原来的 constructor 构造函数。

    function Star(uname, age){
    this.name = uname
    this.age = age
}
//我们把函数存储到 prototype 原型上,就可以调用,并且只会用这一个共享的函数,得以解节省内存资源。
/*    Star.prototype.sing = function (){
        console.log('唱歌')
    }
    Star.prototype.movie = function (){
        console.log('电影')
    }*/
    Star.prototype = {
        constructor: Star,
        sing: function () {
            console.log('唱歌')
        },
        movie: function () {
            console.log('电影')
        }
    }
    let ldh = new Star('刘德华', 39)
    ldh.sing()
    ldh.movie()
    console.log('Star.prototype:', Star.prototype.constructor);
    console.log('ldh.__proto__:', ldh.__proto__.constructor);

打印结果: javascript 构造函数和原型 总结:如果我们修改了原来的原型对象,给原型对象赋值,则必须通过手动的方式利用 constructor 指回原来的构造函数。

构造函数,实例,原型对象三者之间的关系

首先说构造函数和原型之间关系: javascript 构造函数和原型

构造函数的 prototype 属性指向了原型对象,原型对象的 constructor 属性又指回了原本的构造函数。

javascript 构造函数和原型

我们又可以通过构造函数 new 一个实例对象,所以我们的构造函数又可以指向了实例对象,而我们又知道这个 new 出来的实例对象中也有一个 _proto_ 原型对象他又指向了 原型对象 ( prototype )。 小细节:我们的实例对象里面也有一个 _proto_.constructor 也可以指回 constructor 但其实就是等价于用了 原型对象 prototype 指回去的。

2.原型链

现在我们知道了,只要有对象,就必然会有对象原型 _proto_ 。 打印示例:

function Star(uname, age){
    this.name = uname
    this.age = age
}
Star.prototype.sing = function (){
    console.log('唱歌')
}

let ldh = new Star('刘德华', 49)
ldh.sing()
console.log(Star.prototype)

javascript 构造函数和原型 可以看到这个 prototype 原型对象里面也有一个 _proto_

function Star(uname, age){
    this.name = uname
    this.age = age
}
Star.prototype.sing = function (){
    console.log('唱歌')
}

let ldh = new Star('刘德华', 49)
ldh.sing()
console.log(Star.prototype)
console.log(Star.prototype.__proto__ === Object.prototype)

打印结果: javascript 构造函数和原型 下面是一张原型链的图: javascript 构造函数和原型 大意就是,比如我们创建了一个 ldh 的实例,我们要用里面的一个成员属性,首先会去 Star 的原型对象去找,假如没找到的话,就会通过 dlh 对象实例的_proto_ 去Star 下面的 prototype 去找,如果找到了就是通过 constructor 指向 Star 构造函数,如果没找到就会继续向上通过_proto_ 的 Object 下面的 prototype 原型对象,如果找到了,就由 constructor 指向原来的构造函数,加入还没找到,就返回空,因为 Object 已经是最上层了。

javescript 的成员查找机制(规则)

  • 当访问一个对象属性(或者方法)时,首先查找这和对象自身有没有该属性。
  • 如果没有就去查找它的原型也就是 _proto_ 指向的 prototype 原型对象。
  • 如果还没有找到,就去找原型对象的原型(也就是 Object 的原型对象)。
  • 以此类推一直找到 Object 为止(如果还找不到就会返回 undefain )。

例子:给 Star 对象一个 sex 成员属性

function Star(uname, age){
    this.name = uname
    this.age = age
}
Star.prototype.sing = function (){
    console.log('唱歌')
}

let ldh = new Star('刘德华', 49)
//这里我吗给 ldh 对象一个 sex 成员属性。
ldh.sex = '男'
//这里拿到的就是 Star 对象的成员属性
console.log(ldh.sex)

javascript 构造函数和原型 例子 2:给 Star 原型对象一个 sex 成员属性

function Star(uname, age){
    this.name = uname
    this.age = age
}
Star.prototype.sing = function (){
    console.log('唱歌')
}

let ldh = new Star('刘德华', 49)

//这里给 Star 对象一个成员属性。
Star.prototype.sex = '男'

//这里拿到的就是 Star 原型对象的成员属性
console.log(ldh)
console.log('Star 原型对象:' + ldh.sex)

javascript 构造函数和原型 例子 3:给 Object 对象一个 sex 成员属性

function Star(uname, age){
    this.name = uname
    this.age = age
}
Star.prototype.sing = function (){
    console.log('唱歌')
}

let ldh = new Star('刘德华', 49)

//这里给 Object 对象一个成员属性。
Object.prototype.sex = '男'

//这里拿到的就是 Star 原型对象的成员属性
console.log(ldh)
console.log('Object 原型对象:' + ldh.sex)

javascript 构造函数和原型 例子 3:给 ldh ,Star 的原型对象, Object 的原型对象都加一个 sex 成员属性。

function Star(uname, age){
    this.name = uname
    this.age = age
}
Star.prototype.sing = function (){
    console.log('唱歌')
}

let ldh = new Star('刘德华', 49)
//这里我吗给 Star 对象一个 sex 成员属性。
ldh.sex = '男'
//这里给 Star 对象一个成员属性。
Star.prototype.sex = '女'
//这里给 Object 对象一个成员属性。
Object.prototype.sex = '未知'
console.log(ldh)
console.log(ldh.sex)	

javascript 构造函数和原型

总结:就是一层一层通过原型对象网上找,一直到 Object , 而且假如有多个相同的成员,会取就近原则。

原型对象 this 指向

var that
function Star(uname, age){
    this.name = uname
    this.age = age
    that = this
}

Star.prototype.sing = function (){
    console.log('唱歌')
}

let ldh = new Star('刘德华', 49)
// 1. 在构造函数中,里面的 this 指向的是对象实例。  (谁调用了就指向谁)
// 因为即使我们把 sing 方法加到了 prototype 原型对象里面,调用者实际还是 ldh ,所以 this 指向还是 ldh 对象实例 
ldh.sing()
console.log(that === ldh)
// 2.原型对象里面的 this 指向的是实例对象 ldh

javascript 构造函数和原型

扩展内置对象(原型对象的一个典型应用,可以扩展内置方法)

可以通过原型对象来扩展内置对象里面的方法。比如给数组增加自定义求偶数和的功能。

首先打印看看数组对象里面有什么内置方法和属性。

console.log(Array.prototype)

javascript 构造函数和原型 我随意看了下框出几个我们经常见到的一些内置方法。就是应为这些内置方法,我们才能很方便的操作数组,现在让我来自定义一个内置方法。 例子:

Array.prototype.sum = function (){
    let sum = 0
    for (let i = 0; i < this.length ;i++){
        sum += this[i]
    }
    return sum
}
console.log(Array.prototype)

打印结果可以看到我们自定义的 sum 方法已经放入了 Array 里面。 javascript 构造函数和原型 让我们来使用一下:

Array.prototype.sum = function (){
    let sum = 0
    for (let i = 0; i < this.length ;i++){
        sum += this[i]
    }
    return sum
}
//这里定义一个数组,这个 arr 其实就是一个实例对象,这个 arr 就是用 Array 构造函数创建出来的
let arr = [1, 10, 100]
console.log(arr.sum())

打印结果: javascript 构造函数和原型

小细节:为什么这里的 arr 就是一个实例对象呢? 现在我们来验证一下

let arr1 = []
//这里的 arr2 是实例对象的写法吧
let arr2 = new Array()
console.log(arr1)
console.log(arr2)
console.log(typeof arr1 === typeof arr2)

打印结果: javascript 构造函数和原型 可以看到只是定义的方式语法不同,但其实是一样的。

转载自:https://juejin.cn/post/6880449679432089608
评论
请登录