原型
1、定义:原型是 function 对象的一个属性,它定义了构造函数制造出的对象的公共祖先。通过该构造函数产生的对象,可以继承该原型的属性和方法,原型也是对象。
2、利用原型特点和概念,可以提取公有属性
将同一个构造函数构造的多个对象的值相同的属性放到对象的原型的属性中,如下面例子中的 Lastname ,Person 产生的对象都姓 ‘zhang’,为了减少代码的冗余,可以将其提取至 Person 的原型上。首先注意,原型也是对象,意味着它能进行对象该有的一切操作。
3、对象如何查看原型 ——>隐式属性 proto
4、对象如何查看对象的构造函数 ——>constructor
//Person.prototype = {}
// 公有祖先
Person.prototype.Lastname = "zhang";
//如果公有属性比较多时,因为原型本身就是对象,可以如下表达:
//Person.prototype = {
// ...//属性:属性名
//}
function Person(name,sex){
//this.Lastname = 'zhang'
this.name = name;
this.sex = sex;
}
var person1 = new Person('小明','male');
var person2 = new Person('小嫣','female')
当对象自身含有某个属性时,则该对象访问该属性时,返回的是该属性值,当访问该对象不存在的属性时,会依次遍历整条原型链。如person1 和 person2 没有 Lastname 属性,于是访问它们的构造函数的原型的属性 Lastname.
原型的增删改查
对于一个对象,在自身的属性上进行增删改查,不会对改构造函数产生的其它对象产生影响,如person1属性的增加、修改和删除对 person2完全没影响。
一个对象不能操作(增、删、改) 构造该对象的构造函数的原型上的属性
要想操作对象的原型的属性,通过对象修改原型是不可能的,必须直接在其原型上进行操作,可以操作无论是显式的属性还是隐式的属性(proto 和 constructor )对原型的属性进行操作,对象的相关属性也会做出相应变化。
注意,person1 有属性,是因为在上一步的时候,自己添加了属性 Lastname = ’ li '。
另外,对象删除属性的时候,返回值都是 true ,自己有的属性,当然可以删除,自己没有的属性,当然也能删除
proto 属性
xxxx :前两下划线,后两下划线,隐式属性命名规则(人为的,javascript没有绝对的隐式属性命名规则)
proto 属性的属性值存放着该对象的原型,构造函数原理中三步第一步
Person.prototype.name = 'abc';
function Person(){
// var this = {
// __proto__ : Person.prototype
//}
}
所以对象访问其自身不存在的属性时,会沿着__proto__去原型找,也就是说,proto__属性才是对象遍历原型链的根本原因,对象可以自己改变自己的__proto 属性值。
例子:
Person.prototype.name = 'abc';
function Person(){
}
var person1 = new Person();//写哪里没区别
//改的是原型的属性值
Person.prototype.name = 'edf';
换种写法
Person.prototype.name = 'abc';
function Person(){
//this = {
// __proto__ : Person.prototype
//}
}
var person1 = new Person();
//在 new 的时候,它的 __proto__ 就已经定了。
Person.prototype = {
name : 'edf'
}
//这种写法是直接把原型改了,换成了另一个新对象,Person.prototype指向另一个空间了,
//但是__proto__没变,还是指向原来的空间
结果变了,为什么?
原来那种写法,只是在原有的基础之上将 name属性值改了,后面那种写法,是直接把原型换成了另一个对象。
就像下面的一样理解
var obj = {
name : "a"
};
var obj1 = obj;
obj = {
name:'b'
}
换个位置
Person.prototype.name = 'abc';
function Person() {
}
Person.prototype = {
name : 'edf'
}
var person1 = new Person();
//此时的__proto__就是后面那个 Person.prototype
原型链
原型还有原型
当对象自身含有某个属性时,则该对象访问该属性时,返回的是该属性值,当访问该对象不存在的属性时,会沿着 proto 依次遍历整条原型链。原型链的联系点是 proto
原型链的终端
绝大多数的对象最终都会继承自 Object.prototype ,注意是绝大多数,不是所有,比如 null,null是没有原型的
对象自变量的原型就是原型终端
方法的重写
function Person(){
}
var person = new Person()
person对象的原型是 Person.prototype,也就是 Object
注意原型的表示方式,person.proto === Person.prototype ,person是具体的实例对象,Person是构造实例对象的构造函数
在原型链的终端中,也就是Object.prototype中有方法 toString,也就是说,在一般情况下,所有的对象都能通过原型链找到并调用这个toString方法,但如果我们在原型链的某个对象构造函数中(非终端)自己写了一个名字也叫 toString 的方法,功能与原型链的有差别,(没差别也不用写了)这就叫函数的 重写。如上面例子,调用 toString方法,此时调用的是原型链终端的 toString方法,
如果我们直接在person的原型中自己再写个 toString方法, person再调用,就是直接调用自己的原型的了
Person.prototype = {
toString : function(){
return 'hello';
}
}
function Person(){
}
var person = new Person()
方法的重写:在某个对象中定义一个函数,这个函数的名字与它的原型链中对象的某个函数名字一样,但具体功能不同,这就叫函数的重写。自己写的这个方法功能覆盖了它原型链上的同名方法。
再举个例子:
num 是一个原始值数字,通过包装类,可以调用原型 Number.prototype 也就是 num.proto 的方法 toString 而不是调用的原型链终端的 toString 方法,也就是说 num 的原型对 toString 方法进行了重写。
this 指向
Person.prototype = {
name : 'a',
sayName : function (){
console.log(this.name)
}
};
function Person() {
this.name = 'b'
}
var person1 = new Person();
person1 对象没有 sayName 方法,上原型链找到了方法,但是里面的 this指向的还是 person1本身,所以打印出 b ,谁调用该方法,this就指向谁
Person.prototype = {
height : 100
};
function Person() {
this.eat = function (){
this.height ++
}
}
var person1 = new Person();
person1 继承了原型的属性及其值 height = 100,然后调用 eat 方法,该方法的 this 指向 person1,所以 person1.height ++ ,输出101,但是它原型的 height 是没变的
改变 this 指向
call、 apply
作用:改变 this 指向
正常一个函数,如
function test(){
}
test() //真实过程是 test.call()
执行 test() test.call()是没有区别的,为什么?因为括号里没有对象,因此就是默认的this
如何改变 this 指向?将一个对象作为参数,传入括号函数执行括号中,如果函数有参数,则参数依次放到括号传入对象的后面,this 指向的就是 call 的第一个参数, 如
function Person(name,age){
this.name = name;
this.age = age;
}
var person = new Person()
var obj = {
}
Person.call(obj,'zhang',21);
本来 Person() 或者 Person.call()正常执行,里面的 this指的是 window ,现在执行 Person.call(obj,‘zhang’,21),则等于把 this全部换成了 obj ,obj 代替了 里面的 this
function Person(name,age){
//this = obj
this.name = name;
this.age = age;
}
call 功能
有如下构造函数
function Person(name,age){
this.name = name;
this.age = age;
}
该构造函数可以生产出具有 名字,年龄的对象,如果此时我们需要再写一个构造函数,这个构造函数可以生产出更加全面一点的对象,并且这个对象功能需要涵盖上面构造函数构造出对象的功能,如何做?
再写一个构造函数:
//A写的
function Person(name,age){
this.name = name;
this.age = age;
}
//B写的
function Student(name,age,sex,grade){
this.name = name;
this.age = age;
this.sex = sex;
this.grade = grade;
}
显然代码冗余,B如何借用用A写的方法实现自己的需求?
function Person(name,age){
this.name = name;
this.age = age;
}
function Student(name,age,sex,grade){
//原来Person里的 this指向:谁调用Person,就指向谁
//Student 里的this 指向:谁调用 Student,就指向谁
//现在我要调用 Person,但是希望原来Person里的this 变成Student里的this
//所以将Student 的默认 this 传入 call的第一个参数
Person.call(this,name,age);
this.sex = sex;
this.grade = grade;
}
var student = new Student('zhang',21,'male',3);
借用用别人的方法,实现自己的功能,注意一旦使用 call ,Person里的功能 Student 都会有,不会少,所以使用于 B的功能完全涵盖 A 的功能时,用 call 比较合适。注意 call的第一个参数必须是改变 this指向 的对象。
形象的例子
造车工厂下有若干部门,分别是造 车轮(wheel )、车座(sit)、车骨架(model)然后还有一个部门组装成车,即造车 (car)
function Wheel(wheelSize,style){
this.wheelSzie = wheelSize;
this.style = style;
}
function Sit(color){
this.color = color;
}
function Model(width,height){
this.width = width;
this.height = height;
}
function Car(wheelSize,style,color,width,height){
Wheel.call(this,wheelSize,style);
Sit.call(this,color);
Model.call(this,width,height);
}
var car = new Car(100,'随便','white', 400, 150);
Car全是调用其它的方法,实现自己的功能。
call 、apply 区别:传参列表不同
call 和 apply的参数第一个都是改变 this 指向的对象,但是call 后面的实参是一个一个传,而apply是只有一个实参,并且这个实参是一个数组,也就是一个实参列表,如上面的例子
Wheel.call(this,wheelSize,style);
//换成 apply :
Wheel.apply(this, [wheelSize,style] );