原型:JavaScript 中,万物皆对象!但对象也是有区别的。分为普通对象和函数对象
原型的概念:每一个javascript对象创建的时候,就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型中“继承”属性。
构造函数
function Star(age,name) {
this.age = age
this.name = name
this.sing = function(){
console.log("我可以唱歌")
}
}
let ldh = new Star(35,"刘德华")
console.log(ldh)//35,刘德华
console.log(Star.sing)//undefind|实例成员不能通过构造函数访问
Star.sex='男';
console.log(Star.sex)//男|静态成员只能通过构造函数访问
console.log(ldh.sex)//undefind|静态成员只能通过构造函数访问
构造函数里面的内容,构造函数里面是有静态成员和实例成员
实例成员就是构造函数内部通过this添加的成员:age,name,sing都是实例成员,不能通过构造函数访问
静态成员,就是直接在函数本身上面添加的成员,只能通过构造函数访问
内存浪费问题:当我们使用构造函数创建多个实例的时候,如果这个函数里面有多个实例成员的函数,那么我们每一次创建实例对象,构造函数下的方法都会重新开辟一个内存空间去存放这些方法。但是明明这些方法都是一个构造函数里面的,干嘛要这么麻烦,还要占用内存,所以出现了原型。
一:prototype
在JavaScript中,每一个构造函数都有一个 prototype 属性,该属性指向函数的原型对象。
我们可以把那些不变的方法,直接定义在prototype对象上这样所有的实例都可以共享这个方法
function Star(age,name) {
this.age = age
this.name = name
}
Star.prototype.sing = function(){
console.log("我会唱歌")
}
let ldh = new Star(35,"刘德华")
ldh.sing//我会唱歌
因为prototype是一个对象,所以我们称构造函数的prototype为原型对象
function Person(){}
Person.prototype.name = "protoName"
var person = new Person()
console.log(person.name) // protoName
解析:person对象没有name属性,他将去他的构造函数(Person)里面去找,构造函数里面没有,就去构造函数的原型(Person.prototype)里面找,如果没有找到返回值为undefined。
二:proto[注解:是两个_,而不是一个]
每一个对象都有一个proto属性,指向构造函数的prototype原型对象,他指向该对象的原型。
function Person(){}
var person = new Person()
console.log(person.__proto__ === Person.prototype) // true
三:constructor(构造函数)
每个原型都有一个constructor属性(也就是说,constructor是存在于prototype上面的属性),指向该关联的构造函数。
function Star(){}
var ldh = new Star()
console.log(Star.prototype.constructor === Star) // true
console.log(Star.prototype.constructor === ldh.constructor) // true
console.log(ldh.__proto__.constructor === Star.prototype.constructor) // true
解析:ldh没有constructor属性 -> 查找Star的constructor属性,Star没有constructor属性 -> 查找Star.prototype的constructor属性
function Star(age, name) {
this.age = age
this.name = name
}
Star.prototype = {
constructor:Star,//
sing:function(){
console.log("我会唱歌")
},
movie:function(){
console.log("我会演电影")
}
}
let ldh = new Star(35, "刘德华")
console.log(ldh.__proto__.constructor)//function Object() { [native code] }
解析:为何不是Star,Star.prototype本身就是一个对象,你给Star.prototype赋值一个对象,那就会把原来prototype上面的对象给覆盖了,所以这个时候就可以使用constructor了
整合:
function Animal(name) {
this.name= "animal";
this.age= 18;
this.setName= function (name) {
this.name = name
}
}
Animal.prototype.setAge =function(age){
this.age = age
}
var dog = new Animal("dog")
var cat = new Animal("cat")
Animal是构造函数,dog、cat是构造函数创建的对象
继承:JavaScript 只有一种结构:对象。每个实例对象(object)都有一个私有属性(称之为 proto )指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(proto),层层向上直到一个对象的原型对象为 null。null 没有原型,并作为这个原型链中的最后一个环节。
给dosomething的原型加上属性
function doSomething(){}
doSomething.prototype.foo = "bar";
console.log( doSomething.prototype );
运行结果
{
constructor: ƒ doSomething(),
foo: "bar",
__proto__: {
constructor: ƒ Object(),
hasOwnProperty: ƒ hasOwnProperty(),
isPrototypeOf: ƒ isPrototypeOf(),
propertyIsEnumerable: ƒ propertyIsEnumerable(),
toLocaleString: ƒ toLocaleString(),
toString: ƒ toString(),
valueOf: ƒ valueOf()
}
}
原型链
1:首先说好了的每一个实例都有proto,而这个就指向的是构造函数的对象原型,所以ldh.proto和Star.prototype是等价的,即ldh.proto`能够只会Star这个构造函数呢,本质上就是通过Star.prototype指回去的。
2:那么我们就会想到Star.prototype的proto指向的会是谁呢,一打印就能看的出指向的是Object.prototype,所以不用想也知道他的构造函数肯定是Object了
3:那Object.prototype还有没有proto,打印一看就知道是为null,所以我们知道原型的终点就是null
js的成员查找机制
当访问一个对象的属性或者方法的时候,首先查找这个对象自身有没有这个属性,如果没有就去找他的原型,也就是proto指向的prototype原型对象,如果还没有就去查找原型对象的原型,即Object的原型对象以此类推一直找到null为止
javascript是面向对象编程思想,面向对象思想有三大特性:封装、继承、多态。
封装
es5没有class概念,类就是一个函数有自己的方法和属性,将方法和属性组成一个类的过程就是封装。
封装有3种:构造函数、prototype、.
1:构造函数
·首字母大写
·内部使用this
·使用new生成实例
function Cat(name,color){
this.name = name;
this.color = color;
this.eat = function(){alert("吃老鼠")};
}
缺点:通过this定义的属性和方法,我们实例化对象的时候都会内存中重新复制一份,造成内存的浪费。
2:prototype
function Cat(name,color){
this.name = name;
this.color = color;
}
Cat.prototype.type = "猫科动物";
Cat.prototype.eat = function(){alert("吃老鼠")};
优点:所有的实例的type,eat方法指向的同一个内存地址,提高了效率。
缺点:编写麻烦
3:.
function Cat(){}
Cat.type = "猫科动物";
Cat.eat = function(){alert("吃老鼠")};
var blueCat=new Cat();
console.log(Cat.type)//猫科动物
console.log(blueCat.type)//undefined
缺点:只能通过该类访问
javascript也有private public protected
var Book = function (id, name, price) {
//private(在函数内部定义,函数外部访问不到,实例化之后实例化的对象访问不到)
var num = 1;
var id = id;
function checkId() {
console.log('private')
}
//protected(可以访问到函数内部的私有属性和私有方法,在实例化之后就可以对实例化的类进行初始化拿到函数的私有属性)
this.getName = function () {
console.log(id)
}
this.getPrice = function () {
console.log(price)
}
//public(实例化的之后,实例化的对象就可以访问到了~)
this.name = name;
this.copy = function () {
console.log('this is public')
}
}
//在Book的原型上添加的方法实例化之后可以被实例化对象继承
Book.prototype.proFunction = function () {
console.log('this is proFunction')
}
//在函数外部通过.语法创建的属性和方法,只能通过该类访问,实例化对象访问不到
Book.setTime = function () {
console.log('this is new time')
}
var book1 = new Book('111','悲惨世界','$99')
book1.getName(); // 111 getName是protected,可以访问到类的私有属性,所以实例化之后也可以访问到函数的私有属性
book1.checkId(); //报错book1.checkId is not a function
console.log(book1.id) // undefined id是在函数内部通过定义的,是私有属性,所以实例化对象访问不到
console.log(book1.name) //name 是通过this创建的,所以在实例化的时候会在book1中复制一遍name属性,所以可以访问到
book1.copy() //this is public
book1.proFunction(); //this is proFunction
Book.setTime(); //this is new time
book1.setTime(); //报错book1.setTime is not a function
new的实质
var o = new Object();
1:新建一个对象o
2:o. proto = Object.prototype 将新创建的对象的proto属性指向构造函数的prototype
3:将this指向新创建的对象
4:返回新对象,但是这里需要看构造函数有没有返回值,如果构造函数的返回值为基本数据类型string,boolean,number,null,undefined,那么就返回新对象,如果构造函数的返回值为对象类型,那么就返回这个对象类型
继承
js继承有8类:原型链继承、借用构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合式继承、混入方式继承多个对象、ES6类继承extends
1:原型链继承[SubType.prototype = new SuperType(); ]
function SuperType(){
this.property = true;
this.colors = ["red", "blue", "green"];
}
function SubType(){
this.subproperty = false;
}
// 这里是关键,创建SuperType的实例,并将该实例赋值给SubType.prototype
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green,black"
缺点:原型链方案存在的缺点:多个实例对引用类型的操作会被篡改。
2:借用构造函数继承[子类里面SuperType.call(this);]
function SuperType(){
this.color=["red","green","blue"];
}
function SubType(){
//继承自SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.color.push("black");
alert(instance1.color);//"red,green,blue,black"
var instance2 = new SubType();
alert(instance2.color);//"red,green,blue"
核心代码是SuperType.call(this),创建子类实例时调用SuperType构造函数,于是SubType的每个实例都会将SuperType中的属性复制一份。
缺点:
只能继承父类的实例属性和方法,不能继承原型属性/方法,无法实现复用,每个子类都有父类实例函数的副本,影响性能
3:组合继承
// 组合继承: 借用构造函数继承 + 原型链继承 一起使用
// 借用构造函数继承 :可以继承构造函数的属性
// 原型链继承 :可以继承原型链的成员
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
// 继承属性, 构造函数继承,第二次调用SuperType()
SuperType.call(this, name);
this.age = age;
}
// 继承方法,构建原型链继承,第一次调用SuperType()
SubType.prototype = new SuperType();
// 重写SubType.prototype的constructor属性,指向自己的构造函数SubType
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
//
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
缺点:
第一次调用SuperType():给SubType.prototype写入两个属性name,color。
第二次调用SuperType():给instance1写入两个属性name,color。
实例对象instance1上的两个属性就屏蔽了其原型对象SubType.prototype的两个同名属性。所以,组合模式的缺点就是在使用子类创建实例对象时,其原型中会存在两份相同的属性/方法。
4:原型式继承
利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型。object()对传入其中的对象执行了一次浅复制,将构造函数F的原型直接指向传入的对象。
function object(obj){
function F(){}
F.prototype = obj;
return new F();
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
缺点:
原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。
无法传递参数
另外,ES5中存在Object.create()的方法,能够代替上面的object方法。
5:寄生式继承
function object(obj){
function F(){}
F.prototype = obj;
return new F();
}
function createAnother(original){
var clone = object(original); // 通过调用 object() 函数创建一个新对象
//var clone = Object.create(original); es5中和上面等价
clone.sayHi = function(){ // 以某种方式来增强对象
alert("hi");
};
return clone; // 返回这个对象
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"
缺点(同原型式继承):
原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。
无法传递参数
6:寄生组合式继承
结合借用构造函数传递参数和寄生模式实现继承
function inheritPrototype(subType, superType){
var prototype = Object.create(superType.prototype); // 创建对象,创建父类原型的一个副本
prototype.constructor = subType; // 增强对象,弥补因重写原型而失去的默认的constructor 属性
subType.prototype = prototype; // 指定对象,将新创建的对象赋值给子类的原型
}
// 父类初始化实例属性和原型属性
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
// 将父类原型指向子类
inheritPrototype(SubType, SuperType);
// 新增子类原型属性
SubType.prototype.sayAge = function(){
alert(this.age);
}
var instance1 = new SubType("xyc", 23);
var instance2 = new SubType("lxy", 23);
instance1.colors.push("2"); // ["red", "blue", "green", "2"]
instance1.colors.push("3"); // ["red", "blue", "green", "3"]
这个例子的高效率体现在它只调用了一次SuperType 构造函数,并且因此避免了在SubType.prototype 上创建不必要的、多余的属性。于此同时,原型链还能保持不变;因此,还能够正常使用instanceof 和isPrototypeOf()
这是最成熟的方法,也是现在库实现的方法
7:混入方式继承多个对象
function MyClass() {
SuperClass.call(this);
OtherSuperClass.call(this);
}
// 继承一个类
MyClass.prototype = Object.create(SuperClass.prototype);
// 混合其它
Object.assign(MyClass.prototype, OtherSuperClass.prototype);
// 重新指定constructor
MyClass.prototype.constructor = MyClass;
MyClass.prototype.myMethod = function() {
// do something
};
Object.assign会把 OtherSuperClass原型上的函数拷贝到 MyClass原型上,使 MyClass 的所有实例都可用 OtherSuperClass 的方法。
8:ES6类继承extends
extends关键字主要用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类。其中constructor表示构造函数,一个类中只能有一个构造函数,有多个会报出SyntaxError错误,如果没有显式指定构造方法,则会添加默认的 constructor方法,使用例子如下。
class Rectangle {
// constructor
constructor(height, width) {
this.height = height;
this.width = width;
}
// Getter
get area() {
return this.calcArea()
}
// Method
calcArea() {
return this.height * this.width;
}
}
const rectangle = new Rectangle(10, 20);
console.log(rectangle.area);
// 输出 200
-----------------------------------------------------------------
// 继承
class Square extends Rectangle {
constructor(length) {
super(length, length);
// 如果子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
this.name = 'Square';
}
get area() {
return this.height * this.width;
}
}
const square = new Square(10);
console.log(square.area);
// 输出 100
extends继承的核心代码如下,其实现和上述的寄生组合式继承方式一样
function _inherits(subType, superType) {
// 创建对象,创建父类原型的一个副本
// 增强对象,弥补因重写原型而失去的默认的constructor 属性
// 指定对象,将新创建的对象赋值给子类的原型
subType.prototype = Object.create(superType && superType.prototype, {
constructor: {
value: subType,
enumerable: false,
writable: true,
configurable: true
}
});
if (superType) {
Object.setPrototypeOf
? Object.setPrototypeOf(subType, superType)
: subType.__proto__ = superType;
}
}
来源:https://juejin.cn/post/6844903696111763470
https://juejin.cn/post/6844903480868470798