1两种编程分格
1.1)面向过程我们习惯用传统的方式编写一个一个函数来解决需求的方式,这样不利于整个团队的开发,无端的增加了全局变量,也会出现函数覆盖
1 function a(){} 2 function b(){}
1.2)面向对象编程就是将你的需求抽象一个对象,然后针对这个对象分析其特征(属性)与动作(方法),这个对象我们称之为类。面向对象中有一个特点就是封装,
就是说把你的需求功能放在一个对象里。比如你的行李箱存放的物品,包括很多件,而不是一件一件的存放在其他处。
2封装
2.1)创建一个类
在js 中创建一个类很容易,首先声明一个函数保存在一个变量里。按照编程习惯一般将这个代表类的变量首先字母大写。然后这个函数内部通过this添加熟悉及方法
如:
1 var Book = function(id, bookname, price){ 2 this.id = id; 3 this.bookname = bookname; 4 this.price = price; 5 }
也可以通过在类的原型(类也是一个对象,所以也具有原型prototype)上添加属性和方法,有两种方式,一种是 一一为原型对象属性赋值,另一种则是将一个对象的 赋值给类的原型对象。这两种方式不能混用。例如
1 Book.prototyp.display = function(){ 2 //code 3 } 4 或者 5 6 Book.prototyp = { 7 display : function(){ 8 //code 9 } 10 }
我们将所有的方法和属性都封装在我们抽象的Book类里面了,当使用功能方式的时候不能直接使用这个 Book类,需要用new 关键字来实例化(创建)新的对象使用实例化对象的属性和方法是,可以通过点语法:
1 var book = new Book(10,'javascrpt',23); 2 console.log(book.bookname); // javascript
通过this添加的属性和方法同在prototype中添加的属性和方法有什么区别哪?
“通过this添加的属性、方法都是在当前对象添加的,然而javascript是一种基于原型prototype的语言,所有每次创建对象时(javascript中函数也是一种对象),它都有一个原型prototype用于指向其继承的属性、方法。这样通过prototype继承的方法并不是对象自身的,所以在使用这些方法时,需要通过prototype一级一级来查找来得到。这样你会发现通过this定义的属性或者方法是该对象自身拥有的,所以每次通过类创建一个新对象时,this指向的属性和方法都会得到创建,然而通过prototype继承的属性或者方法时每个对象通过prototype访问到的,所以我们每次通过类创建一个新对象时这些属性和方法不会在创建。”
2.2属性与方法的封装
理解面向对象是对一些属性方法的隐藏与暴漏,比如私有的属性、私有的方法、共有的属性、共有的方法、保护方法,javascript 有哪些哪?
在javascript 中没有显性的存在,可以通过灵活的技巧来实现
由于javascript的函数级作用域,声明在函数内部的变量以及方法在外界时访问不到的,通过此特性即可创建类的私有变量及私有属性方法。然而在函数内部通过this创建的属性和方法,在类创建对象时,没个对象自身都拥有一份并且可以在外部访问到。
1 var Book = function(id, name, price){ 2 //私有属性 3 var num = 1; 4 //私有方法 5 function checkdi(){ 6 7 } 8 //特权方法 9 this.getName = function(){}; 10 //对象共有属性 11 this.id = id; 12 //对象共有方法 13 this.copy = function(){} 14 }
2.3闭包实现
“有时候我们经常将类的静态变量通过闭包来实现”
1 //利用闭包实现 2 var Book = (function(id, name, price){ 3 //私有静态变量 4 var book_num = 1; 5 //私有方法 6 function checkid (){ 7 8 }; 9 //返回构造函数 10 return function(newid,namename,newprice){ 11 //私有变量 12 var name, price; 13 //私有方法 14 function checkid(id){}; 15 //特权方法 16 this.getName = function(){}; 17 //共有属性 18 this.id = newid; 19 //共有方法 20 this.copy = function(){}; 21 book_num++; 22 if(book_num > 100){ 23 throw new Error('only 100'); 24 } 25 //构造器 26 this.setName(name); 27 } 28 29 })(); 30 31 Book.prototyp = { 32 //静态公有属性 33 isJSBook : false, 34 //静态方法 35 display : function(){} 36 }
在闭包外部添加原型属性和方法看上去像脱离了闭包这个类,所以有时候在闭包内部实现一个完整的类后将其返回
简单理解也可以这样写:
1 var book = (function(){ 2 var booID = 1; 3 function checkbook(){} 4 //create class 5 function _book(id){ 6 this.id = id; 7 8 } 9 _book.prototype = { 10 display : function(){ 11 alert("display"); 12 } 13 } 14 15 return _book; 16 })(); 17 18 var a = new book(1); 19 20 a.display();
也可以这个写:
1 (function(win){ 2 var booID = 1; 3 function checkbook(){} 4 //create class 5 function _book(id){ 6 this.id = id; 7 8 } 9 _book.prototype = { 10 display : function(){ 11 alert("display"); 12 } 13 } 14 15 //return _book; 16 //声明一个全局类 17 win._book = _book; 18 })(window); 19 20 //类使用时,先new 才可以使用 21 var a = new _book(); 22 23 a.display();
2.4 创建对象的安全模式
“对与初学者,在创建对象上很不适合这样写法,所以经常会忘记使用new而犯下错误”
比如:
1 var Book = function(id, name, price){ 2 this.id = id; 3 this.name = name; 4 this.price = price; 5 } 6 7 //实例化一本书 8 var book = Book(10,‘javascript’,30);
测试代码:
console.log(book.name); // undefined
这是为什么那?(undefined),仔细观察后 原来忘记用new关键字实例化,为了避免这个情况,就用到安全模式:
1 //图书信息 2 var Book = function(id, name, price){ 3 //判断执行过程中this是否是当前这个对象(如果是说明使用new创建的) 4 if(this instanceof Book){ 5 this.id = id; 6 this.name = name; 7 this.price = price; 8 }else{ 9 return new Book(id,name,price); 10 } 11 12 } 13 14 //实例化一本书 15 var book = Book(10,‘javascript’,30);
3 继承
javascript 并没有继承这一现有的机制,又是如何实现的那?
3.1子类的原型对象-类式继承
1 //类式继承 2 //声明父类 3 function SuperClass(){ 4 this.superValue = true; 5 } 6 7 //为父类添加共有的方法 8 SuperClass.prototype.getSuperValue = function(){ 9 return this.superValue; 10 } 11 12 //声明子类 13 function SuberClass(){ 14 this.subValue = false; 15 } 16 17 //继承父类 18 SuberClass.prototype = new SuperClass(); 19 //为子类添加共有方法 20 SuberClass.prototype.getSubValue = function(){ 21 return this.subValue; 22 }
这里声明了两个类,第二个类的原型prototype被赋予了第一个类的实例
类的原型对象的作用就是为类的原型添加共有方法,但类不能直接访问这些属性和方法,必须通过原型prototype来访问。
调用:
var instance = new SubClass();
console.log(instance.getSuperValue); //true
console.log(instance.getSubValue); //false
另外,我们还可以通过instanceof 来检测某个对象是否是某个类的实例,或者说某个类是否继承了某个类。
instanceof 它如何就知道对象与类之间的继承关系哪?
instanceof 是通过判断对象的prototype链来确定这个对象是否是某个类的实例,而不是关系对象与类的自身结构。
//检测
console.log(instance instanceof SuperClass); // true
console.log(instance instanceof SubClass); //true
console.log(subClass instanceof SuperClass); //false
我们说subClass继承superClass, 可以为什么SubClass instanceof SuperClass 得到结果是 false?
instanceof是判断前面的对象是否是后面类(对象)的实例,但不能表示两者的继承
我们在实现subClass继承superClass时通过将supClass的实例赋值给supClass的原型prototype,
所以说SubClass.prototype继承了SuperClass
1 console.log(SuberClass.prototype instanceof SuperClass); //true
这是类继承的一个特点
Object是所有类的祖先:
console.log(instance instanceof Object); //true
这种类的继承还有两个缺点:
其一:由于子类通过其原型prototype对父类实例化,继承了父类。所有说父类中的共有属性要是引用类型,就会在子类中被所有实例共用,因此一个子类的实例更改子类原型从父类构造函数中继承来的共有属性就会直接影响到其它子类
1 function superClass(){ 2 this.books = ['javascript','html','less']; 3 } 4 5 function subClass(){ 6 subClass.prototype = new superClass(); 7 var instance1 = new subClass(); 8 var instance2 = new subClass(); 9 console.log(instance2.books); // ['javascript','html','less'] 10 instance1.books.push('java'); 11 console.log(instance2.books); // ['javascript','html','less','java'] 12 13 }
instance1 的一个无意的修改久会无情的伤害了instance2的book属性,这在编程中很容易埋藏陷阱。
其二:由于子类实现的继承是靠其原型prototype对父类的实例化实现的,因此在创建父类的时候,是无法向父类传递参数的,因而在实例化父类的时候也无法对父类构造函数内的属性进行初始化。
接下来我们会用,继承解决这个问题。
3.2创建即继承-构造函数继承
比如我们常见的构造函数继承
1 //构造函数式继承 2 //声明父类 3 function superClass(id){ 4 //应用类型共有属性 5 this.books = ['javascript','html','less']; 6 //值类型共有属性 7 this.id = id 8 } 9 10 //父类声明原型方法 11 superClass.prototype.showbooks = function(){ 12 console.log(this.books); 13 } 14 //声明子类 15 function subClass(id){ 16 //继承父类 17 superClass.call(this,id); 18 } 19 20 //创建一个子类实例 21 var instance1 = new subClass(10); 22 //创建第二个类的实例 23 var instance2 = new subClass(11); 24 25 instance1.books.push('java'); 26 27 console.log(instance1.books); // ['javascript','html','less'] 28 console.log(instance1.id); // 10 29 30 31 console.log(instance2.books); //['javascript','html','less'] 32 console.log(instance2.id); //11
superclass.call(this,id);这条语句是构造函数式继承的精华,由于call 可以更改函数的作用环境,由于父类中时给this 绑定属性,子类也就继承类父类的共有属性,由于这种继承没有涉及到原型prototype,所有父类中的原型不会子类所继承,如果想被子类继承久必选要放在构造函数中,这样创建出来的每个实例都会单独拥有一份而不能共用,这样就违背了代码复用的原则。为了综合这两种模式的优点,后来有了组合式继承。
3.3组合继承
1 //构造函数式继承 2 //声明父类 3 function superClass(id){ 4 //应用类型共有属性 5 this.books = ['javascript','html','less']; 6 //值类型共有属性 7 this.id = id 8 } 9 10 //父类声明原型方法 11 superClass.prototype.showbooks = function(){ 12 console.log(this.books); 13 } 14 //声明子类 15 function subClass(id){ 16 //继承父类 17 superClass.call(this,id); 18 } 19 20 21 22 instance1.books.push('java'); 23 24 25 subClass.prototype = new superClass(); 26 27 SuberClass.prototype = { 28 console.log(this.id); 29 } 30 31 var instance1 = new subClass(10); 32 instance1.books.push("mysql"); 33 console.log(books); // ['javascript','html','less','mysql'] 34 var instance2 = new subClass(11); 35 console.log(books); // ['javascript','html','less']
子类中的实例更改父类继承下来的引用类型属性,根本不会收到影响。
问题:在使用构造函数继承时执行了一遍父类的构造函数,而在实现子类原型的类式继承时有调用了一遍父类的构造函数。因此父类构造函数调用了两遍。
loading......