1、原型
JavaScript中,万物皆对象。
一般分为Object(普通对象)和Function(函数对象)。其中,通过new Function 产生的对象是函数对象,其他对象都是普通对象。
每次声明函数时,都会产生一个原型属性,即prototype。这个属性是一个指针,指向一个对象,这个对象包含可以由特定对象实例共享的一些属性和方法,即原型对象。原型对象上包含constructor属性,这个属性指向构造函数本身。简单来说就是,每个函数都有一个原型属性(prototype),原型上的指针指向一个对象,对象包含所有实例共享的属性和方法。
所以,原型定义了一些公用的属性和方法,利用原型创建出来的新对象实例会共享原型的所有属性和方法。一般通过new + 构造函数 来创建新对象实例。例如:
var a = new A();
新对象实例a被构造函数A创建出来,新对象a没有prototype属性,只有__proto__属性,prototype属性只有构造函数A才有。 构造函数A中有prototype属性,指向a的__proto__属性指向的地方,即构造函数A的原型对象。所以实例a可以访问构造函数A的原型对象上的所有属性和方法。
所以,原型是一个对象,其他对象可以通过原型实现属性继承。原型的构成:原型的属性、方法和constructor。
2、原型链
JS中万物皆对象。每个被构造出来的新对象实例都有一个__ proto__属性,指向创建该对象的构造函数的prototype属性所指向的地方,即构造函数的原型对象。当访问一个对象的某个属性时,会先查找对象本身,如果本身没有时,就会沿着对象的__proto__去寻找,即在构造函数的原型对象上寻找,找到就返回其值,没找到就继续沿着对象的__proto__寻找,直到寻找到null,返回undefined就结束。这就形成了一条链式结构,即原型链。原型链实现了属性和方法的继承。
当查找对象的属性时,会先检查对象本身是否存在该属性,如果不存在,会经由__ptoto__在原型链上一层层访问,而不会访问自身的ptototype。例如:
<script src="">
function Person(name, sex) {
this.name = name;
this.sex = sex;
}
var person1 = new Person('charon', 23);
</script>
新对象person1被构造函数Person创建出来,即person1.ptoto == Person.prototype,Person函数又是被Object函数创建出来,即Person.proto == Object.prototype,而Object的原型对象上是null,即Object.prototype==null。
所以,person1的原型链是:
person1.proto == Person.prototype
person1.proto.proto == Object.prototype
person1.proto.proto.proto == null
person1的原型链如下图所示:
3、原型链练习题1分析
<script >
// 重点 改变this指向
// 题1 这个是在构造函数创建的新对象里新增talk/mycode属性
function Person(name, age) {//this指向Itgril创建的新对象
// console.log(this);// Itgirl {}
this.name = name;
this.age = age;
this.talk = function () {
console.log(this.name + '的年龄是' + this.age + '岁');
}
// console.log(this);// Itgirl {name: '一个女生', age: 20, talk: ƒ}
}
// Itgirl.prototype = new Person; 不影响
function Itgirl(name, code, age) {
// console.log(this);// Itgirl {}// this指向Itgril创建的新对象
Person.call(this, name, age);//改变的是Person函数里面的this的指向
// console.log(this);// Itgirl {name: '一个女生', age: 20, talk: ƒ} this指向Person创建的新对象
this.code = code;
this.mycode = function () { console.log('我在学习' + this.code); }
// console.log(this);// tgirl {name: '一个女生', age: 20, code: 'H5', talk: ƒ, mycode: ƒ}
}
var girl1 = new Itgirl('一个女生', 'H5', 20);
console.log(girl1);// Itgirl {name: '一个女生', age: 20, code: 'H5', talk: ƒ, mycode: ƒ}
// console.log((girl1.__proto__===Itgirl.prototype));// true
// console.log((girl1.__proto__.__proto__===Person.prototype));// false
// console.log((girl1.__proto__.talk===Itgirl.prototype.talk));// true
girl1.talk();// 一个女生的年龄是20岁
/*
1.girl1对象有talk属性 gril1对象可以直接访问talk属性
2.this.talk = function(){console.log(this.name + '的年龄是' + this.age + '岁');}
3.返回结果 一个女生的年龄是20岁
*/
girl1.mycode();//我在学习H5
/*
1.girl1对象没有mycode属性 沿着原型链去找
2.this.mycode = function(){console.log('我在学习' + this.code);}
3.返回结果 我在学习H5
*/
/*题1 分析
window{
girl1:undefined 0x001
Person:f...
Itgirl:f...
}
Itgirl-AO{
name:undefined '一个女生'
code:undefined 'H5'
age:undefined 20
}
Person-AO{
name:undefined '一个女生'
age:undefined 20
}
虚拟内存
栈 堆
Itgirl-AO 0x001 0x001:{name: '一个女生', age: 20, talk: ƒ}
-->{name: '一个女生', age: 20, talk: ƒ , code:'H5',mycode:'我在学习H5'}
Person-AO 0x001
girl1 0x001
原型链
gril1 Itgirl.prototype Object.prototype null
__proto__
__proto__ __proto__
*/
</script>
4、原型链练习题2分析
<script src>
// 重点 原型链的继承
// 题2 这个是在构造函数的原型对象上新增sayName/sayAge属性
function SuperType(name) {// this指向SubType创建的新对象
// console.log(this);// SuperType {}
// 给新对象SubType-AO1添加 name colrs属性
this.name = name;
this.colors = ["red", "blue", "green"];
console.log(this);// 第一次调用 SubType {name: 'Nicholas', colors: Array(3)} 第二次调用SubType {name: 'Greg', colors: Array(3)}
}
// 在SuperType的原型对象(SuperType.prototype)上新增sayName属性 值为函数体
SuperType.prototype.sayName = function () {
// console.log(this);// SubType {name: 'Nicholas', colors: Array(4), age: 29}
console.log(this.name);
};
// console.log(SuperType.prototype);// {sayName: ƒ, constructor: ƒ}
function SubType(name, age) {
// console.log(this);// SubType {}
SuperType.call(this, name);// SuperType构造函数里面的this指向被改变为SubType构造函数创建的新对象
// 给新对象SubType-AO1添加 age属性
this.age = age;
// console.log(this);// SubType {name: 'Nicholas', colors: Array(3), age: 29}
}
SubType.prototype = new SuperType();
// 在SubType的原型对象(SubType.prototype)上新增sayAge属性 值为函数体
SubType.prototype.sayAge = function () {
// console.log(this);// SubType {name: 'Nicholas', colors: Array(4), age: 29}
console.log(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
// console.log(instance1);// SubType {name: 'Nicholas', colors: Array(4), age: 29}
console.log(instance1.colors);// ["red", "blue", "green","black"]
// 调用sayName() instance1.__proto__.sayName()
console.log(instance1.__proto__.sayName === SuperType.prototype.sayName); // true
instance1.sayName();// 'Nicholas'
/* 1.instance1对象身上有sayName属性 沿着原型链去找
2.在instance1.__proto__上找 找到sayName属性 (instance1.__proto__ === SubType.prototype)
3.function(){console.log(this.name);}
4.打印结果 'Nicholas'
*/
// 调用sayAge() instance1.__proto__.__proro__.sayAge()
instance1.sayAge();// 29
/* 1.instance1对象身上没有sayAge属性 沿着原型链去找
2.在instance1.__proto__上找 没有sayName属性 (instance1.__proto__ === SubType.prototype)
3.在instance1.__proto__.__proto__上找 找到sayName属性 (instance1.__proto__.__proto__ === SuperType.prototype)
4.function(){console.log(this.age);}
5.打印结果 29
*/
var instance2 = new SubType("Greg", 27);
console.log(instance2.colors);// ["red", "blue", "green"]
// 调用sayName() instance2.__proto__.sayName
instance2.sayName();// 'Greg'
/* 1.instance1对象身上没有sayName属性 沿着原型链去找
2.在instance1.__proto__上找 找到sayName属性 (instance1.__proto__ === SubType.prototype)
3.function(){console.log(this.name);}
4.打印结果 'Greg'
*/
// 调用sayAge() instance2.__proto__.__proro__.sayAge
instance2.sayAge();// 27
/* 1.instance1对象有sayAge属性
2.在instance1.__proto__上找 没有sayName属性 (instance1.__proto__ === SubType.prototype)
3.在instance1.__proto__.__proto__上找 找到sayName属性 (instance1.__proto__.__proto__ === SuperType.prototype)
4.function(){console.log(this.age);}
5.打印结果 27
*/
console.log(instance2);// SubType {name: 'Greg', colors: Array(3), age: 27}
/*
window{
instance1:undefined 0x001
instance2:undefined 0x002
SuperType:f...
SubType:f...
}
SuperType-AO1{
name:undefined 'Nicholas'
}
SubType-AO1{
name:undefined 'Nicholas'
age:undefined 29
}
SuperType-AO2{
name:undefined 'Greg'
}
SubType-AO2{
name:undefined 'Greg'
age:undefined 27
}
虚拟内存
栈 堆
SubType-AO1 0x001 0x001:SuperType添加{name: 'Nicholas', colors: Array(3)}
--> SubType添加 {name:'Nicholas',color:["red", "blue", "green","black"],age:29}
SuperType-AO1 0x001
instance1 0x001
SubType-AO2 0x002 0x002:SuperType添加{name:'Greg',color:["red", "blue", "green"]}
--> SubType添加 {name:'Greg',color:["red", "blue", "green",age:27}
SuperType-AO2 0x002
insstance2 0x002
原型链
instance1 SubType.prototype SuperType.prototype null
__proto__
__proto__ __proto__
sayAge(29) sayName('Nicholas')
instance2 SubType.prototype SuperType.prototype null
__proto__
__proto__ __proto__
sayAge(27) sayName('Greg')
*/
</script>
5、原型链练习题3分析
<script>
// 只要有new关键字 函数一定会运行(不需要再加小括号表调用)
// 函数体中的this 指向距离this最近的function的调用者
/*
window:{
getName:undefined --> f(5) --> f(4)
Foo:f..
}
Foo-AO:{
getName:f(2)
}
Foo.prototype-AO:{
getName:f(3)
}
*/
// 第2步 提升Foo 值为函数体
function Foo() {
getName = function () {// 全局的getName属性
console.log(1);
}
return this;
}
// 第4步 给Foo新增属性 值为函数体f(2)
Foo.getName = function () {
console.log(2);
}
// 第5步 给Foo.prototype新增属性 值为函数体f(3)
Foo.prototype.getName = function () {
console.log(3);
}
// 第1步 提升var声明的变量getName 值为undefined
// 第6步 同名变量 getName 覆盖函数体f(5) 重新赋值为函数体f(4)
var getName = function () {
console.log(4);
}
// 第3步 提升getName函数 因为与var声明的变量名为同名变量 所以覆盖值undefined 重新赋值为函数体f(5) 接下来执行非var 非function的部分
function getName() {
console.log(5);
}
Foo.getName();// 2
/*
7.Foo没有调用 直接访问getName属性 是Foo在调用getName
8.调用(Foo.getName) 执行f(){console.log(2);}得到结果 2
*/
getName();// 4
/*9.window.getName() 即访问全局中的getName属性 f(){console.log(4);}// 4 */
Foo().getName();// 1
/*
10.Foo发生调用 函数体执行
11.执行f(){getName=function(){console.log(1);},return this}
12.第一步 同名变量 getName 覆盖值f(4) 重新赋值f(1) 第二步 return this 返回结果 return window
13.Foo().getName()==window.getName(); 执行函数体f(1) // 1
*/
getName();// 1
/*14.window.getName() 访问全局范围内的getName属性*/
new Foo.getName();// 2
/*
15.new Foo.getName()==new (Foo.getName)() 整体成为一个new带参 构造一个新对象
16.{}.__proto__===Foo.getName.prototype
17.{getName:f(){console.log(2)}}
{} Foo.getName.prototype Object.prototype null
__proto__
__proto__ __proto__
*/
new Foo().getName();// 3
/*
18.new Foo().getName()===(new Foo()).getName() 先整体成为new带参 构造一个新对象
19.(new Foo()).getName===(Foo.prototype).getName()
20.{}.__proto__===Foo.prototype
21.{getName:f(){console.log(3)}}新对象身上有getName属性
{} Foo.prototype Object.prototype null
__proto__
__proto__ __proto__
*/
new new Foo().getName();// 3 只要有new关键字 函数一定会运行
/*
22.new new Foo().getName()==new (new Foo()).getName()==new ((new Foo()).getName)() == new Foo.prototype.getName()
23.{}.__proto__===Foo.prototype.getName.prototype
24.{getName:f(){console.log(3)}}
{} Foo.prototype.getName.prototype Object.prototype null
__proto__
__proto__ __proto__
*/
</script>