1.函数的原型链(__ proto__,prototype,constructor )
-
函数的原型(prototype),包含2个默认属性:{constructor : ... , __ proto__:window.Object.prototype}
函数名.prototype.arg1 = "value_arg1" 这样添加后 arg1将与constructor,__proto __平级
函数名.arg2 = "value_arg2" 这样添加后,arg2将被添加到constructor中。 -
函数的实例,包含一个默认属性{__ proto__ : 函数的原型(prototype)}
当访问一个函数的实例的属性或方法的时候,浏览器会先在这个函数的实例本身查找有没有这个属性或方法,没有就在它的__ proto__(函数的原型)中查找,如果还没有就继续往__ proto__的__ proto__中查找直到__ proto__ === window.Object.prototype
注意:访问函数实例的属性,不会去constructor中查找,即如果函数名.arg2,函数实例.arg2返回的是undefined,因为constructor也是函数实例的一个属性,且这个属性等于函数本身。
this 关键字并不是指向当前对象的原型对象,this指向的是当前对象的一个实例。
this.属性
实际上是把属性实际上是把属性或方法拷贝到每个实例中,有多少个实例就有多少份,而prototype则是通过引用指向,所以在内存中只有一份
注意:一般属性是定义在函数体中,用this. 这样子类和实例都能拿到,同样也都能改变,每个实例有自己的一份。而方法则定义在prototype中,每个实例如果想有自己独立的方法,利用原型链优先查找原则"重写该方法"
函数是对象,只有函数对象有原型,而函数的实例是没有原型的,只有个__ proto__是指向函数的原型。
必须重申,原型链中的方法和属性没有被复制到其他对象——它们被访问需要通过前面所说的“原型链”的方式。上游对象的方法不会复制到下游的对象实例中;下游对象本身虽然没有定义这些方法,但浏览器会通过上溯原型链、从上游对象中找到它们
例如:Array的prototype属性定义了一些方法,所有Array对象都能使用。
如果Array.直接定义一些属性,则Array的实例对象不能直接返回到。
函数的原型中的constructor === 函数本身,即:
Person.prototype.constructor===Person
创建对象一般是在构造器(函数体)中定义属性、在 prototype 属性上定义方法
// 构造器及其属性定义
function Test(a,b,c,d) {
// 属性定义
this.a = a
};
// 定义第一个方法
Test.prototype.x = function () { ... }
// 定义第二个方法
Test.prototype.y = function () { ... }
// 等等……
上面代码中的this.a = a 中的this实际上是Test.call(this, "a", "b", "c", "d")方法传进去的。
2.创建对象
- 1> var childObj = Object.create(supObj) 传入一个原型对象,并创建一个基于该原型的新对象,新对象的__ proto__ === supObj
var newObj = Object.create(supObj)实际上执行的是(伪代码):
function create(){
newObj.__ proto__ = supObj;
return newObj
}
childObj重写父类supObj的属性后,该属性的值跟supObj中的该属性值得变化无关。如果没有重写则会随supObj的变化而变化。
- 2> 构造函数的方式:
function Student(name) {
this.name = name;
this.hello = function () {
alert('Hello, ' + this.name + '!');
}
}
var xiaoming = new Student('小明');
xiaoming.name; // '小明'
xiaoming.hello(); // Hello, 小明!
注意:构造函数千万别忘记new。在strict模式下,this.name = name将报错,因为this绑定为undefined,在非strict模式下,this.name = name不报错,因为this绑定为window,于是无意间创建了全局变量name,并且返回undefined,这个结果更糟糕。
new 函数的实现原理:
function trivialNew(constructor, ...args) {
var o = {}; // 创建一个对象
o = Object.create(constructor.prototype); //设置实例的__ proto__
constructor.call(o, ...args);//传this进去复制构造方法中的函数和变量。
return o;
}
需要注意的是create()方法与call()方法的顺序,执行create()方法会将o实例的所有非constructor.prototype属性清空。理由如下Object.create()实现原理:
if (!Object.create) {
Object.create = function (o) {
function F() {} //定义了一个隐式的构造函数
F.prototype = o;
return new F(); //其实还是通过new来实现的
};
}
3> 直接定义创建
var Chinese = {
nation:'中国'
};
Chinese相当与一个实例。
3.继承
- 1>
function inherits(Child, Parent) {
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
}
记得在子类中调用父类构造函数Parent.call(this, props),确保子类能继承父类函数体(构造器函数中)中的属性
inherits上面三行代码跟Object.create()方法一样实际效果都是将Child.prototype的
__ proto__指向Parent.prototype,这样会改变Child构造器指向,使之与Child关联,所以要执行 Child.prototype.constructor = Child;这段代码。
最后来一个ES6的class创建对象。。。。
class Student {
constructor(name) {
this.name = name;
}
hello() {
alert('Hello, ' + this.name + '!');
}
}
var xiaoming = new Student('小明');
xiaoming.hello();
class继承。。。。
class PrimaryStudent extends Student {
constructor(name, grade) {
super(name); // 记得用super调用父类的构造方法!
this.grade = grade;
}
myGrade() {
alert('I am at grade ' + this.grade);
}
}
总结
三种定义对象的属性的方式
1.定义在造器函数中的,this.x = x 在内置的浏览器代码中,它们是可用于对象实例的成员,通常通过使用new关键字调用构造函数来创建
2.那些直接在构造函数上定义、仅在构造函数上可用的。这些通常仅在内置的浏览器对象中可用,并通过被直接链接到构造函数而不是实例来识别。 例如:直接用函数名.属性
3.那些在构造函数原型上定义、由所有实例和对象类继承的。这些包括在构造函数的原型属性上定义的任何成员,如myConstructor.prototype.x()。
网友评论