js
是一门基于对象的语言。js
中的一切皆对象;es6
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
对象定义的属性和方法的一个复制。app
构造函数(constructor)
,其实就是一个普通函数,可是内部使用了this
变量,对构造函数使用new
运算符,就能生成实例,而且this
变量会绑定在实例对象上。函数
function Cat(name,color){ this.name=name; this.color=color; } Cat.prototype.type=function(){}; var cat1=new Cat()
这时cat1
会自动含有一个constructor
属性,指向它们的构造函数。this
alert(cat1.constructor==Cat);//true alert(cat instanceof Cat);//true
js
提供了一个instanceof
运算符,用来检验cat1
是不是Cat
的实例对象。es5
构造函数(constructor):每new
生成一个实例,就至关于在内存上又复制了一次
原型对象(prototype):而portotype
,全部的实例都只指向一个内存地址,用于不变的属性和方法spa
不论是构造函数内部仍是原型对象,里面的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
的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