面向对象
js
是一门基于对象的言语。js
中的统统皆对象;
console.log(Object.prototype.toString.call(123)) //[object Number]
console.log(Object.prototype.toString.call('123')) //[object String]
console.log(Object.prototype.toString.call(undefined)) //[object Undefined]
console.log(Object.prototype.toString.call(true)) //[object Boolean]
console.log(Object.prototype.toString.call({})) //[object Object]
console.log(Object.prototype.toString.call([])) //[object Array]
console.log(Object.prototype.toString.call(function(){})) //[object Function]
new function
var a = function () {};
console.log(typeof a);//function
var b = new function () {};
console.log(typeof b);//object
var c = new Function ();
console.log(typeof c);//function
new function 是一个JavaScript中用户自定义的对象
var obj = function (name) {
this.name = name;
};
var b = new obj('aaa')=o == Object.create(obj.prototype);;
console.log(b.name);
// 建立一个以另一个空对象为原型,且具有一个属性p的对象
o = Object.create({}, { p: { value: 42 } })
// 省略了的属性特征默以为false,所以属性p是不可写,不可罗列,不可设置的:
o.p = 24
o.p
//42
私有变量和函数
在函数内部定义的变量和函数,叫部分(内部)变量和函数,假如不对外供应接口,外部是没法接见到的。
function Box(){
var color = "blue"; //私有变量
var fn = function(){} //私有函数
}
var obj = new Box();
alert(obj.color); //弹出 undefined,接见不到私有变量
alert(obj.fn); //同上
静态变量和函数
定义一个函数后加"."
来增加的属性和函数,该函数能够接见到,但实例接见不到。
function Obj(){};
Obj.num = 72; //静态变量
Obj.fn = function() { } //静态函数
alert(Obj.num); //72
alert(typeof Obj.fn) //function
var t = new Obj();
alert(t.name); //undefined
alert(typeof t.fn); //undefined
实例变量和函数
function Box(){
this.a=[]; //实例变量
this.fn=function(){} //实例要领
}
var box1=new Box();
box1.a.push(1);
box1.fn={};
console.log(box1.a); //[1]
console.log(typeof box1.fn); //object
var box2=new Box();
console.log(box2.a); //[]
console.log(typeof box2.fn); //function
在box1
中修正了a
和fn
,而在box2
中没有转变,由于数组和函数都是对象,是援用范例,这就申明box1
中的属性和要领与box2
中的属性与要领虽然同名但却不是一个援用,而是对Box
对象定义的属性和要领的一个复制。
ES5
组织函数
组织函数(constructor)
,实在就是一个一般函数,然则内部运用了this
变量,对组织函数运用new
运算符,就可以天生实例,而且this
变量会绑定在实例对象上。
function Cat(name,color){
this.name=name;
this.color=color;
}
Cat.prototype.type=function(){};
var cat1=new Cat()
这时刻cat1
会自动含有一个constructor
属性,指向它们的组织函数。
alert(cat1.constructor==Cat);//true
alert(cat instanceof Cat);//true
js
供应了一个instanceof
运算符,用来磨练cat1
是不是是Cat
的实例对象。
原型对象与实例对象之间的关联。
组织函数(constructor):每new
天生一个实例,就相当于在内存上又复制了一次
原型对象(prototype):而portotype
,一切的实例都只指向一个内存地点,用于稳定的属性和要领
不论是组织函数内部照样原型对象,内里的this
在没有new
之前都指向该组织函数Cat
Cat.prototype.constructor===Cat //true
alert(Cat.prototype.isPrototypeof(cat1)) //true
isPrototypeOf()
用来推断某个prototype
对象和某个实例之间的关联
alert(cat1.hasOwnProperty("name")); // true
alert(cat1.hasOwnProperty("type")); // false
hasOwnProperty()
用来推断某个属性究竟是当地属性,照样继续自prototype
对象的属性。当地为true
in
运算符用来推断,某个实例是不是含有某个属性,不论是不是是当地属性。还能够用来遍历某个对象的一切属性。
alert("name" in cat1);//true
for(var i in cat1){alert("cat1["+i+"]="+cat1[i])}
继续
组织函数绑定
function Cat(name,color){
Animal.apply(this, arguments);//即是是把父类的实例属性复制了一份给子类实例装上了,占内存
this.name = name;
this.color = color;
}
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
应用prototype
Cat.prototype = new Animal(); //由于prototype 援用范例指向同一个地点会影响别的实例,而且不能向父传参
Cat.prototype.constructor = Cat; //把prototype指向本来的组织函数
组合继续
function Cat(){
Animal.call(this);
}
Cat.prototype = new Animal(); //比较经常使用,占内存
经由过程空对象
function extend(Child, Parent) {
var F = function(){}; //应用一个空对象去转接prototype,而且空对象险些不占内存,不会影响父对象
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child; //改正返来它的组织函数指向
Child.uber = Parent.prototype; //辅佐属性,能够直接挪用父的要领
}
function Animal(){ }
Animal.prototype.species = "动物";
function Cat(name,color){
this.name=name
this.color=color
}
extend(Cat,Animal);
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
拷贝继续
function extend2(Child, Parent) {
var p = Parent.prototype;
var c = Child.prototype;
for (var i in p) { //轮回父的原型要领给到子
c[i] = p[i];
}
c.uber = p;
}
extend2(Cat, Animal);
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
原型和原型链
私有变量和函数
在函数内部定义的变量和函数,叫部分(内部)变量和函数,假如不对外供应接口,外部是没法接见到的。
function Box(){
var color = "blue"; //私有变量
var fn = function(){} //私有函数
}
var obj = new Box();
alert(obj.color); //弹出 undefined,接见不到私有变量
alert(obj.fn); //同上
静态变量和函数
定义一个函数后加"."
来增加的属性和函数,该函数能够接见到,但实例接见不到。
function Obj(){};
Obj.num = 72; //静态变量
Obj.fn = function() { } //静态函数
alert(Obj.num); //72
alert(typeof Obj.fn) //function
var t = new Obj();
alert(t.name); //undefined
alert(typeof t.fn); //undefined
实例变量和函数
function Box(){
this.a=[]; //实例变量
this.fn=function(){} //实例要领
}
var box1=new Box();
box1.a.push(1);
box1.fn={};
console.log(box1.a); //[1]
console.log(typeof box1.fn); //object
var box2=new Box();
console.log(box2.a); //[]
console.log(typeof box2.fn); //function
在box1
中修正了a
和fn
,而在box2
中没有转变,由于数组和函数都是对象,是援用范例,这就申明box1
中的属性和要领与box2
中的属性与要领虽然同名但却不是一个援用,而是对Box
对象定义的属性和要领的一个复制。
我们建立的每一个函数都有一个prototype
属性,这个属性是一个指针,指向一个对象,这个对象的用处是包括能够由特定范例的一切实例同享的属性和要领。那末,prototype
就是经由过程挪用组织函数而建立的谁人对象实例的原型对象。
运用原型的优点是能够让对象实例同享它所包括的属性和要领。也就是说,没必要在组织函数中增加定义对象信息,而是能够直接将这些信息增加到原型中。运用组织函数的重要题目就是每一个要领都要在每一个实例中建立一遍。
在JavaScript
中,一共有两种范例的值,原始值和对象值。每一个对象都有一个内部属性 prototype
,我们一般称之为原型。原型的值能够是一个对象,也能够是null
。假如它的值是一个对象,则这个对象也一定有本身的原型。如许就形成了一条线性的链,我们称之为原型链。
函数能够用来作为组织函数来运用。别的只要函数才有prototype
属性而且能够接见到,然则对象实例不具有该属性,只要一个内部的不可接见的__proto__
属性。__proto__
是对象中一个指向相干原型的神奇链接。根据规范,__proto__
是不对外公然的
当挪用组织函数建立一个实例的时刻,实例内部将包括一个内部指针(__proto__
)指向组织函数的prototype
,这个衔接存在于实例和组织函数的prototype
之间,而不是实例与组织函数之间。
function Person(name){ //组织函数
this.name=name;
}
Person.prototype.printName=function() {//原型对象
alert(this.name);
}
var person1=new Person('Byron'); //实例化对象
console.log(person1.__proto__); //Person
console.log(person1.constructor); //Person
console.log(Person.prototype); //指向原型对象Person
var person2=new Person('Frank');
Person
的实例person1
中包括了name
属性,同时自动天生一个__proto__
属性,该属性指向Person
的prototype
,能够接见到prototype
内定义的printName
要领
实例就是经由过程组织函数建立的。实例一制造出来就具有constructor
属性(指向组织函数)和__proto__
属性(指向原型对象),
组织函数中有一个prototype
属性,这个属性是一个指针,指向它的原型对象。
原型对象内部也有一个指针(constructor
属性)指向组织函数:Person.prototype.constructor = Person;
实例能够接见原型对象上定义的属性和要领。
在这里person1
和person2
就是实例,prototype
是他们的原型对象。
原型链的示意图能够用下图来示意:
ES6
类
–
基本上,ES6
的class
能够看做只是一个语法糖,它的绝大部分功用,ES5
都能够做到,新的class
写法只是让对象原型的写法越发清楚、更像面向对象编程的语法罢了。
class Point { //类名
constructor(x, y) { //组织函数,constructor要领默许返回实例对象(即this)
this.x = x; //this关键字代表实例对象
this.y = y;
}
toString() { //prototype原型对象
return '(' + this.x + ', ' + this.y + ')';
}
}
typeof Point // "function"
Point === Point.prototype.constructor // true
上面代码表明,类的数据范例就是函数,类本身就指向组织函数。
let point = new Point(1,2); //也是直接运用new敕令,传给constructor的值
point.toString(); //(1,2)
point.hasOwnProperty('x') //true,本身的属性(由于定义在this变量上),
point.hasOwnProperty('toString') //false/此属性是定义在原型上
point.__proto__.hasOwnProperty('toString') //true
point.constructor === Point.prototype.constructor //true
在类的实例上面挪用要领,实在就是挪用原型上的要领。
由于类的要领都定义在prototype
对象上面,所以类的新要领能够增加在prototype
对象上面。Object.assign
要领能够很轻易地一次向类增加多个要领。
class Point {
constructor(){ //能够疏忽不写,会自动增加
// ...
}
}
Object.assign(Point.prototype, {
toString(){},
toValue(){}
});
prototype
对象的constructor
属性,直接指向“类”的本身,这与ES5
的行动是一致的。
Point.prototype.constructor === Point //true,类内部的要领不能罗列,和es5不一样
继续
Class
之间能够经由过程extends
关键字完成继续,这比ES5
的经由过程修正原型链完成继续,要清楚和轻易许多。
class ColorPoint extends Point {}//相当于var ColorPoint=new Point也就是ColorPoint继续了Point
上面代码定义了一个ColorPoint
类,该类经由过程extends
关键字,继续了Point
类的一切属性和要领。然则由于没有布置任何代码,所以这两个类完整一样,即是复制了一个Point
类。es6
的继续是先新建父类的实例,再在子类中继续修正this
的指向
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); //挪用父类的constructor(x, y)给父类传值,用来新建父类的this对象。
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // ES6 划定,经由过程super挪用父类的要领时,super会绑定子类的this。
}
}
let cp = new ColorPoint(25, 8, 'green');
cp instanceof ColorPoint // true
cp instanceof Point // true
ES5的继续,本质是先制造子类的实例对象this,然后再将父类的要领增加到this上面Parent.apply(this)。
ES6的继续机制完整差别,本质是先制造父类的实例对象this(所以必须先挪用super要领),然后再用子类的组织函数修正this。
继续链
大多数浏览器的ES5
完成当中,每一个对象都有__proto__
属性,指向对应的组织函数的prototype
属性。ES6
同时有prototype
属性和__proto__
属性,因而同时存在两条继续链。
(1)子类的__proto__
属性,示意组织函数的继续,老是指向父类。
(2)子类prototype
属性的__proto__
属性,示意要领的继续,老是指向父类的prototype
属性。
class A {}
class B extends A {}
B.__proto__ === A //true,es5中,是constructor,实例
B.prototype.__proto__ === A.prototype //true,es5中,是B._proto_===A.prototype,要领
如许的效果是由于,类的继续是根据下面的形式完成的。
// B的实例继续A的实例
Object.setPrototypeOf(B.prototype, A.prototype) = B.prototype.__proto__ = A.prototype;
const b = new B();
// B的实例继续A的静态属性
Object.setPrototypeOf(B, A) = B.__proto__ = A;
const b = new B();
《对象的扩大》一章给出过Object.setPrototypeOf要领的完成。
Object.setPrototypeOf = function (obj, proto) {
obj.__proto__ = proto;
return obj;
}
这两条继续链,能够如许明白:作为一个对象,子类(B)
的原型(__proto__
属性)是父类(A)
;作为一个组织函数,子类(B)
的原型(prototype
属性)是父类的实例。
Object.create(A.prototype);
// 等同于
B.prototype.__proto__ = A.prototype;
实例的__proto__属性
子类实例的__proto__
属性的__proto__
属性,指向父类实例的__proto__
属性。也就是说,子类的原型的原型,是父类的原型。
var p1 = new Point(2, 3);
var p2 = new ColorPoint(2, 3, 'red');
p2.__proto__ === p1.__proto__ // false
p2.__proto__.__proto__ === p1.__proto__ // true
三种特殊状况。
第一种特殊状况,子类继续Object
类。
class A extends Object {}
A.__proto__ === Object //true
A.prototype.__proto__ === Object.prototype //true
这类状况下,A
实在就是组织函数Object
的复制,A
的实例就是Object
的实例。
第二种特殊状况,不存在任何继续。
class A {}
//由于A就是一个函数,所以它继续的天然就是函数。就相当于new Function,但它的prototype是一个对象,所以继续自对象
A.__proto__ === Function.prototype // true
A.prototype.__proto__ === Object.prototype // true
这类状况下,A
作为一个基类(即不存在任何继续),就是一个一般函数,所以直接继续Funciton.prototype
。然则,A
挪用后返回一个空对象(即Object
实例),所以A.prototype.__proto__
指向组织函数(Object
)的prototype
属性。
第三种特殊状况,子类继续null
。
class A extends null {}
A.__proto__ === Function.prototype // true,表明new出来的都是函数,A是函数
A.prototype.__proto__ === undefined // true,由于继续自null,所以它的_proto_找不着,就是undefined
这类状况与第二种状况异常像。A
也是一个一般函数,所以直接继续Funciton.prototype
。然则,A
挪用后返回的对象不继续任何要领,所以它的__proto__
指向Function.prototype
,即本质上执行了下面的代码。
class C extends null {
constructor() { return Object.create(null);
}
原生组织函数的继续
原生组织函数是指言语内置的组织函数,通经常使用来天生数据结构。ECMAScript
的原生组织函数大抵有下面这些。
Boolean()
Number()
String()
Array()
Date()
Function()
RegExp()
Error()
Object()
之前,这些原生组织函数是没法继续的。
class MyDate extends Date{
getTest(){
console.log('我是MyDate的扩大要领',this===date,new Date(),new MyDate(),)
// this向的是它的实例对象,this===date
}
}
let date=new MyDate();
console.log(date.getTime());//当地时候
date.getTest() //我是MyDate的扩大要领 true