原型与原型链
原型
每一个对象都有一个proto,每一个函数都有一个prototype,函数也是对象,所以也有proto。
尝试:
let test = {
"aa": 100,
"bb": 200,
"cc": function(){
console.log("cc is function")
}
};
打印结果:
test对象中,只存在__prop__
test对象中的cc()
函数中,存在prototype
与__prop__
每个函数都有一个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()入手,据说看懂它就看懂了世界√)
非一般情况
非一般情况就是指最顶端时(Function
和Object
)的情况。
从上图中可以看到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