文章目录
一、基本介绍
js 有两种开发模式:1.函数式(过程化),2.面向对象(OOP)。
面向对象的语言有一个标志,那就是类的概念。但是,js没有类的概念,因此它的对象也与基于类的语言中的对象有所不同。
二、声明对象
声明对象的几种方式
- 字面式
- new Object() 构造函数。
- 构造模式
- 工厂方式
- 原型模式声明对象
- 混合模式声明(构造模式+原型模式,平时开发最常用)
象格式为:先声明函数、函数内部使用new Object创建对象,并定义方法属性
2、原型模式创建对象格式为:创建一个空函数,函数外部使用prototype对象定义属性及方法
3、混合模式创建对象格式为:构造+原型
2.1 字面式(json模式)
字面式也是我们平时最常用的。
var person = {
name: 'poorpenguin',
age: 25,
speak: function(say){
alert(say);
},
}
2.2 new Object() 构造函数
Object是所有对象的基类、根,所有的javascript对象都是由Object延伸的。
var person = new Object();
person.name = 'poorpenguin';
person.age = 28;
person.speak = function(){
return '我是'+this.name+'!';
}
2.3 构造方法
先声明函数,函数内使用this指向属性与方法。最后实例化函数。
function Person(name,age){
this.name = name;
this.age = age;
this.speakHi = function(){
console.log('你好,我是'+this.name);
};
}
var me = new Person('poorpenguin',25);
me.speakHi();
var you = new Person('aXiBa',23);
you.speakHi();
2.4 工厂方式
工厂方式声明对象实际让就是对new Object()
声明对象一系列方法的封装。
挺常用的,工厂模式就是为了解决不断的new,重复实例化的问题。
function createObject(object){
var obj = new Object();
obj.name = object.name;
obj.age = object.age;
obj.addr = object.addr;
obj.speak = function(){
console.log('我是'+this.name+',今年'+this.age);
};
return obj;
}
var poorpenguin = createObject({name: 'poorpenguin',age: 25,addr: '中国'});
poorpenguin.speak();
var penguin = createObject({name: '不良企鹅', age: 25, addr: '利比亚'});
penguin.speak();
2.5 原型模式声明对象
js中任何的方法都自带prototype
这个对象。prototype
是object
的子对象。
原型模式的根本就是函数本身声明空内容,利用prototype
可以在函数外面定义一些属性和方法。
可以让所有实例化的对象都有prototype
定义的属性和方法。
第一种写法
function Person(){
}
Person.prototype.name = '默认';
Person.prototype.age = 20;
Person.prototype.init = function(name){ //初始化
this.name = name;
return this;
}
Person.prototype.speak = function(){
console.log('我是'+this.name+',年龄'+this.age);
return this;
};
var poorpenguin = new Person().init('poorpenguin');
poorpenguin.speak();
第二种写法
function Person(){
}
Person.prototype = {
name: 'poorpengion',
age: 25,
speak: function(){
console.log(this.name);
}
}
var po = new Person();
po.speak();
2.6 混合模式
混合模式是也是平时开发中最常用的。
混合模式就是原型+构造,属性使用构造模式定义在函数中,方法使用prototype
定义在函数外。
function Person(obj){
this.name = obj.name || '未知';
this.age = obj.age || 18;
this.addr = obj.addr || '中国';
}
Person.prototype.speak = function(){
console.log('我是'+this.name);
}
var man = new Person({name: 'poorpenguin',age: 25});
man.speak();
三、对象遍历
对象有些时候可以当做数组去处理使用for in
来遍历。
for in 可以遍历上面6种方法声明出来的对象
function Person(obj){
this.name = obj.name || '未知';
this.age = obj.age || 18;
this.addr = obj.addr || '中国';
}
Person.prototype.speak = function(){
console.log('我是'+this.name);
}
var man = new Person({name: 'poorpenguin',age: 25});
for(var p in man){ 使用for in遍历
console.log(man[p]);
}
四、闭包和封装
其实这一段如果不是写底层的话一般可以不用太深入。
4.1 闭包
什么是闭包
全局域中无法直接操作局部作用域中的变量
n是在fn的函数作用域中,无法再全局作用域中操作n
function fn(){
var n = 1;
}
fn();
n = 2; 这样是不行的,在全局域中无法直接操作局部作用域中的变量
但是在局部作用域中的可以操作局部作用域的变量
function fn(){
var n = 1;
setN(); 这样是可以的
function setN(){
n++;
}
}
所以我们将 局部作用域 中的函数 作为返回值 在全局作用域中使用不就可以了吗
function fn(){
var n = 1;
function setN(){
n++;
console.log(n);
}
return setN;
}
fn()(); 这样就实现了在全局作用域下,让局部作用域中的n自增
闭包的概念?
闭包是有权访问另一个函数作用域中的变量的函数,常见的方式就是在一个函数内部
再创建一个函数。
闭包的用途?
1.防止过多的全局变量,改成局部变量来定义。
2.外部函数始终在内存里不会被回收只有内部函数(闭包)被销毁后才被销毁。
闭包的优缺点?
1.优点:有利于封装代码,访问局部变量。
2.缺点:内存占用时间长,占用过多影响网页性能,IE中可能要内存泄漏。
注:内存泄露是指你用不到(访问不到)的变量,依然占居着内存空间,不能被再次利用起来。
闭包与变量
闭包只能取得包含函数中任何变量的最后一个值,所以在循环中的i只能保存最后一个。
解决方案:for循环里面函数想取到对应的i可以再加一个自执行的匿名函数把i当参数传入即可。
4.2 封装
封装就是把对象内部数据和数据操作细节进行隐藏,这是一种思想。
大多数面向对象的语言都支持封装的特性,提供了private
关键字来隐藏某些属性,用来限制被封装的数据或者内容的访问。
js中没有专门的关键字,所以可以通过 闭包 来实现封装。因为函数内部声明的变量外部是访问不到的。
实现封装的思想的步骤:
- 创建一个构造函数(上面提到的那6种都行)
- 变量私有化:将需要私有化的变量,使用
var
在函数作用域中定义(ps:给对象添加的属性和方法无论如何都是不可能私有化的,我们要利用 函数作用域 进行私有化) - 函数私有化:将需要私有化的函数,使用定义在函数作用域中。
- 为对象添加属性
- 为对象添加方法,通过对象的方法来操作私有化的变量和函数。
举个例子
function Student(){
var privateStore = {}; 全局作用域无法访问
function _get(){ 全局作用域无法访问
return privateStore;
}
function _set(stu){ 全局作用域无法访问
if()
privateStore[stu.name] = stu;
}
this.get = function(){ 提供调用操作的接口,可以通过实例化出的对象调用
return _get;
};
this.set = function(){
return _set;
};
}
var student = new Student();
student.set()({name:'poorpenguin',age: 18, sex: '男'});
console.log(student.get()());
student.set()({name:'mengfeng',age: 18, sex: '男'});
console.log(student.get()());
五、继承
- 原型继承
- 构造继承(对象冒充)
- call继承
- apply继承
5.1 原型继承
原型继承涉及到prototype
和_proto_
5.1.1 prototype 和 __ proto __ 的区别
prototype 原型: 只有函数才有的 属性,这个属性是一个指针,指向一个原型对象。原型对象是在定义函数的时候,浏览器默认创建的。
__ proto __ 原型链: 每个js对象都有的属性,指向它的构造函数的protoptype
属性(也就是原型对象)。
5.1.2 __ proto __ 的指向
对象中的__ proto __
属性指向该对象构造函数的prototype
属性
function Person(){ 构造方法
}
Person.prototype.name = '不良企鹅';
Person.prototype.speak = function(){
console.log('h1,guys');
}
console.log(Person.prototype);
var people = new Person();
console.log(people.__proto__);
console.log(Person.prototype === people.__proto__);
5.1.3 原型链
由于__proto__
是任何对象都有的属性,包括prototype
这个属性指向的对象也有__proto__
这个属性,所以会形成一条__proto__
连接起来的链条,链条最终指向null
function Person(){}
var people = new Person();
通过上面的图我们可以最终得出下面的原型链条
console.log(people.__proto__ === Person.prototye); true
console.log(people.__proto__.__proto__ === Object.prototye); true
console.log(people.__proto__.__proto__.__proto__); null
5.1.4 原型继承
js的继承是发生在对象与对象之间。
每个构造函数都有一个属性 prototype 指向一个原型对象,该原型对象定义函数的时候,浏览器自动帮我们创建的。
将父类的实例作为子类的原型便是原型继承的核心。
function Person(){
this.type = '人类';
}
Person.prototype.run = function(){
console.log('会跑');
}
Person.prototype.speak = function(){
console.log('会说话');
}
function Chinese(){
this.comeFrom = '中国';
}
Chinese.prototype = new Person(); 继承了Person中的属性和方法
Chinese.prototype.speak = function(){
console.log('你好,世界!');
}
Chinese.prototype.sayType = function(){
console.log('我是'+this.type);
}
Chinese.prototype.sayComeFrom = function(){
console.log('我来自'+this.comeFrom);
}
var man = new Chinese();
man.run();
man.speak();
man.sayType();
man.sayComeFrom();
5.2 构造继承(对象冒充)
父对象被子对象继承,父对象的所有的 特权属性 和 特权方法 都将传递到子对象中。
特权属性和特权方法就是在函数中定义的属性和方法
- 使用
this
定义属性和方法。 - 在构造函数使用
function
定义的方法和var
定义的变量。
ps:在原型中定义的属性和方法,使用对象冒充(构造继承),子对象是无法继承。1
这才是构造继承,为什么很多人会把call、apply
继承认作构造继承。。。
案例1:使用构造继承父类的特权属性和特权方法
function Person(name){
this.name = name;
this.sayName = function(){
console.log('我是'+this.name);
}
}
function Student(name,str){
this.pObj = Person; 冒充person对象,传递特权属性和特权方法
this.pObj(name);
this.str = str;
this.sayHi = function(){
console.log(this.str);
}
}
var stu = new Student('学生','你好世界');
stu.sayName();
stu.sayHi();
案例2:使用构造继承(对象冒充)无法继承原型对象中定义的属性和方法
function Person(name){
this.name = name;
this.sayName = function(){
console.log(this.name);
}
this.sayHi = function(){
return hi;
}
function hi(){
console.log('函数作用域下定义的函数');
}
}
Person.prototype.test = function(){
console.log('这是在原型中定义的方法');
}
function Student(name){
this.pObj = Person;
this.pObj(name);
}
var stu = new Student('poorpenguin');
stu.sayName();
stu.sayHi()();
stu.test();
5.3 call继承
在子构造函数中 间接调用 想要继承的父构造函数,子构造函数将会获得父构造函数中的所有方法和属性。
function Person(obj){
this.name = obj.name;
this.age = obj.age;
this.speakName = function(){
console.log('我是'+this.name+',年龄'+this.age);
}
}
function Student(obj){
Person.call(this,obj); 这个this是实例化中的stu这个对象,会将父构造函数中的所有this都指向stu。
}
var stu = new Student({name:'不良企鹅',age:25});
stu.speakName();
5.4 apply继承
apply和call的用法几乎一样,就是传参有点不同
function Person(name,age){
this.name = name;
this.age = age;
this.speakName = function(){
console.log('我是'+this.name+',年龄'+this.age);
}
}
function Student(name,age){
Person.apply(this,[name,age]);
}
var stu = new Student('不良企鹅',25);
stu.speakName();
六、面向对象中的一些关键字
1. delete
delete:只能删除对象中的属性,方法、原型链中的属性及变量不能被delete删除。
2. this
在
new
实例化对象的时候,this
代表的是当前对象。
var name = '不良企鹅';
function Person(){
this.name = 'poorpenguin';
}
var people = new Person();
console.log(people.name); 输出poorpenguin
如果只是函数调用的话,
this
表示的就是全局,和在函数外使用var
声明的变量没有区别
var name = '不良企鹅';
function Person(){
this.name = 'poorpenguin';
}
Person();
console.log(name); 输出poorpenguin