构造函数 原型对象prototype 实例对象原型__proto__ 原型链 继承

构造函数

构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总是和new一起使用,可以把对象中一些公共的部分抽取出来,然后封装到这个函数里面;

new 在执行时做的事情

  • 在内存中创建一个新的空对象;
  • 让 this 指向这个空对象;
  • 执行构造函数里面的代码,给这个新对象添加属性和方法;
  • 返回这个新对象(所以构造函数里面不需要 return)

静态成员和实例成员

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

  • 静态成员:在构造函数本身上添加的成员称为静态成员,只能由构造函数本身访问;
  • 实例成员:在构造函数内部创建(this)的对象成员称为实例成员,只能由实例化对象访问;
function Person(uname, age) {
    // 实例成员定义
    this.uname = uname;
    this.age = age;
    this.sing = function () {};
}
var zs = new Person('zs', 23);
console.log(zs.uname); // 实例成员访问
Person.sex = 'woman'; // 静态成员定义
console.log(Person.sex); // 静态成员访问

构造函数原型 prototype

构造函数中的复杂数据类型(function)会重新开辟内存空间进行存储,随着实例的增加,会产生性能问题;

构造函数通过原型分配的函数是所有对象共享的;JavaScript 规定,每一个构造函数都有一个 prototype 属性;指向另一个对象,注意: 这个 prototype 就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有;我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有的对象的实例就可以共享这些方法

function Person() {
    this.sing = function () {}
}
Person.prototype.song = function () {console.log('增加共享方法')}
var zs = new Person();
var ls = new Person();
console.log(zs.sing === ls.sing); // false 因为每个实例的对象中的复杂数据类型,都会存放在不同的内存空间
zs.song(); // prototype 方法调用
console.log(zs.song === ls.song); // true 通过原型对象 prototype 共享了 song 方法

对象原型 __proto__

对象都会有一个属性 __proto__ 指向构造函数的 prototype 原型对象,之所以对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 __proto__ 原型的存在

  • __proto__对象原型和原型对象 prototype 是等价的

    • console.log(zs.__proto__ === Person.prototype); // true
      
  • __proto__对象原型的意义就在于为对象查找机制提供一个方向,或者说一条链路,但是它是一个非标准属性,实际开发中不可以使用,它只是内部指向原型对象的 prototype

constructor 构造函数

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

constructor 主要用于记录该对象引用了哪一个构造函数,它可以让原型对象重新指回原来的构造函数

function Person() {}
// 如果以赋值的形式给原型对象(prototype)添加方法,此时原型对象(prototype)与对象原型(__proto__)中存在 constructor 构造函数这个构造函数指向 Person 对象
Person.prototype.song = function () {};
Person.prototype.move = function () {};
// 2. 如果原型以对象的形式赋值,那么此时原型对象(prototype)和对象的原型(__proto__)就不存在 constructor 构造函数,也不指向Person对象本身
Person.prototype = {
    // 可以通过键值对的方式,手动指向Person对象
    constructor: Person,
    sing: function () {},
    movie: function () {}
}
var zs = new Person();
console.log(Person.prototype); 
console.log(zs.__proto__);

对象,实例,原型对象之间的关系

每一个构造函数都会有一个原型对象 prototype;

构造函数的对象实例的原型 __proto__ 也会指向这个原型对象 prototype;同时他的constructor属性也会指向这个原型对象;

这个原型对象 prototype 的 constructor属性指回这个构造函数;

原型链

JavaScript 成员查找机制

  • 当访问一个对象的属性(包括方法)时,首先查找对象自身有没有该属性或者方法
  • 如果没有,就查找原型,也就是(__proto__指向的原型prototype对象)
  • 如果还没有,就查找原型对象的原型(Object原型对象)
  • 依次类推一直找到Object()为止(null)

继承

ES6之前并没有提供 extends 继承,只能通过构造函数 + 原型对象模拟实现继承,这种方法被称为组合继承

call()

call() 可以调用函数,也可以改变函数中this的指向

// call(thisAarg, arg1, arg2);
function fn() {
    console.log('hello');
    console.log(this);
}
var o = {};
fn.call(); // 此时的this指向全局对象 window/global
fn.call(o); // 此时的this就指向了 o这个对象

借用构造函数实现继承父类型的属性

通过 call() 方法把父类型的this指向子类型的this

function Father(uname, age) {
    this.uname = uname;
    this.age = age;
}
function Son(uname, age, score) {
    // 这里面的this是Son改变了this的指向
    Father.call(this, uname, age);
    this.score = score;
}
var zs = new Son('zs', 23, 90);
console.log(zs); // 只能继承父类的属性和实例方法;不能继承使用原型对象prototype声明的方法

借用原型对象实现继承父类的方法

function Father(uname) {
    this.uname = uname;
}
Father.prototype.sing = function () {
    console.log(this.uname);
}
function Son(uname) {
    Father.call(this, uname); // 继承属性
}
// 把 Father的实例对象给Son的原型对象prototype
// Father的实例对象可以通过__proto__原型访问Father实例对象的prototype原型对象的sing()方法
// 同理 Son.prototype 也是可以访问这个sing()方法的;
// 此时 Son.prototype 等同于被一个新对象赋值了; 新对象的__proto__原型指向构造函数Father的原型对象prototype也存在自己的constructor属性; 所以Son.prototype.constructor此时等价于Father.prototype.constructor; 因此需要手动再次指派回来
Son.prototype = new Father(); // 此时会改变Son.prototype.constructor的指向 需要重新指定回来
Son.prototype.constructor = Son; // 手动指向

Son.prototype.song = function () {
    console.log(this.uname);
}
var zs = new Son('hello world');
zs.sing();
posted @ 2020-04-09 00:27  计算机相关人员  阅读(687)  评论(0)    收藏  举报