原型与原型链

原型

每一个对象都有一个proto,每一个函数都有一个prototype,函数也是对象,所以也有proto。

尝试:

let test = {
	"aa": 100,
	"bb": 200,
	"cc": function(){ 
		console.log("cc is function") 
	}
};

打印结果:

test对象中,只存在__prop__

原型1.png

test对象中的cc()函数中,存在prototype__prop__

原型2.png

每个函数都有一个prototype原型属性,称之为【显式原型属性】或显式原型

每个对象都有一个__proto__原型属性,称之为【隐式原型属性】或隐式原型

prototype和__proto__的作用是什么

  • __proto__属性的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(父对象)里找,一直找,直到__proto__属性的终点null,再往上找就相当于在null上取值,会报错。通过__proto__属性将对象连接起来的这条链路即我们所谓的原型链。
  • prototype仅在函数中存在,当你创建函数时,JS会为这个函数自动添加prototype属性,值是一个有 constructor 属性的对象,而一旦你把这个函数当作构造函数(constructor)调用(即通过new关键字调用),那么JS就会帮你创建该构造函数的实例,实例继承构造函数prototype的所有属性和方法(实例通过设置自己的__proto__指向承构造函数的prototype来实现这种继承)。即f1.__proto__ === Foo.prototype

原型链

var user = {
    name: "NPC",
    sex: "男",
    age: 1,
    readMe: function(){
        console.log("我叫" + this.name + ", 现在" + this.age + " 岁了!")
    }
}

var students = {
    name: "NPC_students",
    readMe: function(){
        console.log("我叫" + this.name + ", 是一名学生,现在" + this.age + " 岁了!")
    }
}
students.readMe();  // this.age 输出 undefined

students.__proto__ = user;  // 将students的原型指向user

students.readMe();  // this.age 输出 1

// 打印结果
> 我叫NPC_students, 是一名学生,现在undefined 岁了!
> 我叫NPC_students, 是一名学生,现在1 岁了!

从这看出,students中不存在age属性时,他会通过__proto__往上冒泡查找。

再加一层看看

var mealCard = {
    money: 0,
    info: function(){
        console.log(`您好: ${this.name}(${this.sex}), 您的 就餐卡 剩余金额为:${this.money}元。`)
    }
}
mealCard.__proto__ = students;

// console.log(mealCard);
mealCard.info();

mealCard中并不存在sex属性,但它会通过__proto__往上冒泡查找;students中也是不存在,说明它还会继续通过__proto__往上冒泡。

原型链

访问一个对象的属性时(obj.name),先从自身属性中找,如果没有,就从隐式原型(obj.__proto__)找,一直找,直到找到null,结束,找的过程形成的链条称为原型链。

涉及到原型、原型链的知识还有:构造函数,ES6的class等

prototype与__proto__总结

  • prototype只在函数中存在,__prop__在对象(除null)和函数中都存在。
  • __proto__一般情况指向的是当前对象的构造函数的prototype(注意有非一般情况)。f1.__proto__ === Foo.prototype
  • prototype保存着实例共享的方法,有一个指针constructor指回构造函数。

一张经典图:(耐心看完,可以从 function Foo()入手,据说看懂它就看懂了世界√)

image

非一般情况
非一般情况就是指最顶端时(FunctionObject)的情况。

从上图中可以看到Object函数中prototype__proto__null, 意味着在原型链顶端为null,我们验证一下

> function foo(){}
> let f1 = new foo()

> f1.__proto__ === foo.prototype
  true
> foo.__proto__ === Function.prototype
  true
> Function.__proto__ === Object.prototype
  false
  // 大意了,我们看看这个Function.__proto__是什么?
> typeof Function.__proto__
  'function'
> Function.__proto__ === Function.prototype
  true

这里看到Function__proto__指向了自己的prototype。可以理解为这是顶层了,没人可以构建Function了,Function就是Function自己。

我们知道函数中是存在__proto__prototype,那么此时的Function.prototype是什么情况呢

> typeof Function.prototype
  'function'
  
> Function.prototype.prototype
  undefined
> Function.prototype.__proto__
  {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
  constructor: ƒ Object()
  hasOwnProperty: ƒ hasOwnProperty()
  isPrototypeOf: ƒ isPrototypeOf()
  propertyIsEnumerable: ƒ propertyIsEnumerable()
  toLocaleString: ƒ toLocaleString()
  toString: ƒ toString()
  valueOf: ƒ valueOf()
  __defineGetter__: ƒ __defineGetter__()
  __defineSetter__: ƒ __defineSetter__()
  __lookupGetter__: ƒ __lookupGetter__()
  __lookupSetter__: ƒ __lookupSetter__()
  __proto__: null
  get __proto__: ƒ __proto__()
  set __proto__: ƒ __proto__()

// 好家伙,是个对象,typeof验证一下    
> typeof Function.prototype.__proto__
  'object'

这说明

> Function.prototype.__proto__ === Object.prototype
  true
  
> typeof Object
  'function'
  
> typeof Object.prototype
  'object'

嘶~,这个Object竟然是个函数,这样就形成了一个循环了。冒泡的最顶层Object.prototype.__proto__===null

探索下来,也就知道了非一般情况指

  • Function.prototype.prototype === undefind
  • Object.prototype.__proto__===null

构造函数与ES6的Class

ES6的class

class user{
    constructor(name, sex, age){
        this.name = name ? name : "NPC";
        this.sex = sex ? sex : "男";
        this.age = age ? age : 1;
    }

    userInfo(){
        console.log("我叫" + this.name + ", 现在" + this.age + " 岁了!")
    }
}

class students extends user{
    constructor(name, grade){
        super(name, "男", "8"); // super关键字用于访问和调用一个对象的父对象上的函数
        this.grade = grade;
    }

    studentsInfo(){
        console.log("我叫" + this.name + ", 是一名学生,现在" + this.age + " 岁了,读" + this.grade + "年级。")
    }
}

var LiHua = new students("LiHua", "二");
LiHua.studentsInfo();
LiHua.userInfo();

// 输出
> 我叫LiHua, 是一名学生,现在8 岁了,读二年级。
> 我叫LiHua, 现在8 岁了!

将上面的例子用 构造函数 复现:

function user(name, sex, age){
    this.name = name ? name : "NPC";
    this.sex = sex ? sex : "男";
    this.age = age ? age : 1;
}
user.prototype.userInfo = function(){
    console.log("我叫" + this.name + ", 现在" + this.age + " 岁了!")
}

function students(name, grade){
    user.call(this, name, "男", "8");  
    this.name = name ? name : "NPC_students";
    this.grade = grade ? grade : "二";
}
students.prototype = Object.create(user.prototype);
students.prototype.constructor = students;
students.prototype.studentsInfo = function(){
    console.log("我叫" + this.name + ", 是一名学生,现在" + this.age + " 岁了,读" + this.grade + "年级。")
}

var LiHua = new students("LiHua", "二");
LiHua.studentsInfo();
LiHua.userInfo();

// 输出
> 我叫LiHua, 是一名学生,现在8 岁了,读二年级。
> 我叫LiHua, 现在8 岁了!

再说说this

在 JavaScript 中 this 不是固定不变的,它会随着执行环境的改变而改变。

this指向情况:

  • 在对象方法中,this 指向该方法 所属的 对象
  • 如果单独使用,this 表示全局对象。
  • 在函数中,this 指向函数的所属者。
  • 在严格模式下,函数是没有绑定到 this 上,这时this 是未定义的(undefined)
  • 在事件中,this 表示接收事件的元素。
  • 类似 call() 和 apply() 方法可以切换函数执行的上下文环境,将 this 引用到任何对象。

在对象方法中,this 指向该方法 所属的 对象

var person = {
    id: "100",
    myId : function() {
        console.log(this.id)
        console.log(this)
    },

    personChild: {
        childId: "1",
        myId : function() {
            console.log(this.id)
            console.log(this.childId)
            console.log(this)
        }
    }
};
person.myId();
// 输出
> 100
> {id: "100", personChild: {…}, myId: ƒ}

person.personChild.myId();
> undefined
> 1
> {childId: "1", myId: ƒ}

单独使用 和 函数中使用 this 和 启用严格模式情况

console.log(this);

function myFunction() {
    console.log(this);
    return this;
}

console.log( myFunction() === this )

// 输出
> Window
> Window
> true

/* 在开头加上严格模式 */
"use strict";

// 再次运行
// 结果:
> Window
> undefined
> false

切换函数执行的上下文环境,即 this 绑定的对象

bind()call()apply()

例:

var user = {
    name: "A",
    myName: function(){
        console.log(this.name)
    }
}

var user_liHua = {
    name: "LiHua"
}

user.myName();  // 输出 A
user.myName.bind(user_liHua)();     // 输出 LiHua
user.myName.call(user_liHua);       // 输出 LiHua
user.myName.apply(user_liHua);      // 输出 LiHua