JavaScript构造函数
普通对象的创建
var obj ={
name: 'aa';
age:18,
set:function(){
console.log(this.name)
}
}
基于object()构造对象
var person =new Object ();
person.name =' aaaa';
person.age = '18' ;
person.getName = function(){
return this.name
}
person.address={
name: '上海',
code: '122'
}
基于对象字面量
// 键值对,不适用批量创建
var person ={
name =' aaaa';
age = '18' ;
getName = function(){
return this.name
}
address;{
name: '上海',
code: '122'
}
}
基于工厂方法模式
工厂方法模式是一种比较重要的设计模式,用于创建对象,旨在抽象出创建对象和属性赋值的过程,值对外暴露出需要设置的属性值。使用工厂方法可以减少很多重复的代码,但是创建的所有实例都是Object类型,无法更近异步区分具体类型。
function createPerson(name, age, adress){
var o =name Object();
o.name = name;
o.age = age;
o.getName = function(){
return this.name;
}
o.address = address
return o;
}
var person = createPerson('aaa',18,{
name:'上海';
code:'1000'
})
var person2 = createPerson('aaa',18,{
name:'上海';
code:'1000'
})
基于构造函数模式
构造函数是通过this为对象添加属性的,属性值类型可以为基本类型,对象或者函数,然后通过new操作符创建对象的实例。
function Person(name, age, adress){
this.name = name;
this.age = age;
this.address = adderss;
this.getName = function(){
return this.name;
}
}
var person =new Person('aaa',18 'sasd'),
var person2 =new Person('aaa',18 'sasd'),
这就意味着每个实例的函数都会占据一定的内存空间,其实这是没必要的,会造成资源的浪费,另外函数也没必要在代码执行前就绑定到对象上。
基于原型对象的模式
基于原型对象的模式是将所有的函数和属性都封装在对象的prototype属性上。
function Person(){
Person.prototype.name = 'aaa',
Person.prototype.age = 18,
Person.prototype.adress = {
name: 'Bejing'
code 20000
};
Person.prototype.getName=function(){
return this.name
};
}
var person = new Person();
var person2 = new Person();
构造函数通过上面的代码可以发现,使用基于原型对象的模式创建的实例,其属性和函数都是相等的不同的实例会共享原型上的属性和函数,解决了方法4存在的问题。但是方法2也存在一个问题,因为所有的实例会共享相同的属性,那么改变其中一个实例的属性值,便会引起其他实例的属性值得变化,这并不是我们期望的。和原型混合的模式
function Person(name,age,address){
this.name = name;
this.age = age;
this.address = adress;
}
Person.prototype.getName = function(){
return this.name
}
var preson =new Person('aaa',18,{
name:'asd',
code:1000
})
var preson2 =new Person('aaa',18,{
name:'asd',
code:1000
})
基于动态原型模式
动态原型模式是将原型对象放在构造函数的内部,通过变量进行控制,只在第一次生成实例的时候进行原型的设置。
动态原型的模式相当于懒汉模式,只在生成实例时设置原型对象,但是功能与构造函数和原型混合模式是相同的。
function Person(name,age,address){
this.name = name;
this.age = age;
this.address = adress;
nIs.auures-auuress
//如果对象中_initialized为undefined,则表明还没有为Person的原型对象添加函数
if(typeof Person._initialized === "undefined"){
Person.prototype.getName = function(){
return this.name
}
Person._initialized =true;
}
//生成实例
var preson =new Person('aaa',18,{
name:'asd',
code:1000
})
var preson2 =new Person('aaa',18,{
name:'asd',
code:1000
})
对象的克隆
对象的克隆
对象克隆是指通过一定的程序将某个变量的值复制到另一个变量的过程,根据复制后的变量与原始变量值得影响情况,克隆可以分为浅克隆和深克隆两种方式。
针对不同的数据类型,浅克隆和深克隆会有不同的表现,主要表现于基本数据类型和引用数据类型在内存中存储的值同。
对应基本数据类型的值,变量存储的是值本身,存放在栈内存的简单数据段中,可以直接进行访问。
对于引用数据类型的值,变量存储的是值在内存中的地址,地址指向内存中的某个位置,如果有多个变量同时指向同一个内存地址,则其中一个变量对值进行修改时,会影响到其他的变量。
var arr1 =[1,2,3];var arr2 = arr1;
arr2[1] = 4;
console. log(arr1);
console. log(arr2);
正是由于数据类型的差异性导致了基本数据类型不管是浅克隆还是深刻隆都是对值本身的克隆,对克隆后值的修改不会影响到值本身。
引用数据类型如果执行的是浅克隆,对克隆后值得修改会影响到原始值,如果执行的深克隆。则克隆对象和原始对象相互独立,不会彼此影响。
对象浅克隆
浅克隆由于只克隆对象最外层的属性,如果对象存在更深层的属性,则不进行处理,这就会导致克隆对象和原始对象的深层属性仍然执行同一块内存。
简单的引用复制
简单的引用复制,即遍历对象最外层的所有属性,直接将属性值复制到另一个变量中。
//Javascript实现对象浅克
function sha11owc1one(origin){
var result = {};
//遍历最外层属性
for(var key in origin){
//判断是否是对象自身的属性
if(origin.hasownProperty(key)){
result[key] = origin[key];
}
}
return result;
}
// 定义一个具有复合属性的对象,测试
//原始对象
var origin = {
a : 1,
b:[2,3,4],
c:{
d: ' name '
}
};
//克隆后的对象
var result = sha71owclone(origin);
console.log(origin);
console.log(result);
object.assign()函数
在ES6中,Object对象新增了一个assign()函数,用于将原对象的可枚举属性赋值到目标对象中。
var origin = {
a : 1,
b:[2,3,4],
c:{
d: ' name '
}
};
//通过object.assign()函数克隆对象
var result = object.assign({}, origin);
console.log(origin);
console.log(resu1t);
//修改克隆对象的内部属性
result.c.d = 'city ';
console.log(origin);
console.1og(result);
浅克隆实现方案都会存在一个相同的问题,即如果原始对象时引用数据类型的值,则对克隆对象的值的修改会影响到原始对象的值。
对象的深克隆
JSON序列化和反序列化
如果一个对象中的全部属性都是可以序列化的,那么我们可以先使用]SON.stringify()函数将原始对象序列化为字符串,在使用JSON.parse()函数将字符串反序列化为一个对象。这样得到的对象就是深克隆后的对象。
var origin = {
a : 1,
b:[2,3,4],
c:{
d: ' name '
}
};
var result = JSON.stringify(origin);
var result2 =JSON. parse(result);
console. log(origin);
console. log(result2);
这种方法能解决大部分]SON类型对象的深克隆问题,但是对于以下几个问题不能很好的解决。
- ·无法实现对函数,RegExp等特殊对象的。
- ·对象的constructor会被抛弃,所有的构造函数会指向Object,原型链关系会破裂。
- ·对象中如果存在循环引用,会抛出异常。
定义一个原始对象,其中一个属性为函数,一个属性为正则表达式,一个属性为某个对象的实例。
function Animl (name){
this.name = name;
}
var animal = new Animal ( 'tom' );
//原始对象
var origin={
//属性为函数
a :function({
return 'a ';
},
//属性为正则表达式
b :new RegExp( '\d','g '),
//属性为某个对象的实例
c:animal
};
var result =JSON.parse(JSON. stringify(origin));
console.log(origin);
console.log(result);
console.log(origin.c.constructor);
conso1e.log(result.c.constructor);
自定义实现深克隆
在自定义实现深克隆时,需要针对不同的数据类型做针对性的处理,因此我们会先实现判断数据类型的函数,并将所有函数封装在一个辅助类对象中,这里用’_'表示,(类似于underscore类库对外暴露的对象)。
(function(_){
//列举可能出现的数据类型
var types='Array object string Date RegExp Function Boolean Number Null undefined' .split(' ');
function type({
//通过调用tostring()函数,从索引值为8时截取字符串,得到数据类型的值
return object.prototype.tostring.ca11(this).s1ice(8,-1);
}
for(var i= types. length;i--; ){
_['is '+types[i]] = (function(){
return function (e1em){
return type.ca11(elem) === self;
};
})(types[i]);
}
return _;
})(_={});
. c l o n e ( ) 函 数 和 .clone()函数和 .clone()函数和.extend()函数
在jQuery中提供了一个 . c l o n e ( ) 函 数 , 但 是 它 是 用 来 复 制 D O M 对 象 的 。 真 正 用 于 实 现 克 隆 的 函 数 是 .clone()函数,但是它是用来复制DOM对象的。真正用于实现克隆的函数是 .clone()函数,但是它是用来复制DOM对象的。真正用于实现克隆的函数是.extend()
jQuery.extend = jQuery.fn.extend = function( {
//options是一个缓存变量,用来缓存arguments[i]
//name是用来接收将要被扩展对象的key,src改变之前target对象上每个key对应的valuel
//copy传入对象上每个key对象的value,copyIsArray判断是否为一个数组
//clone深克隆中用来临时存对象或数组src
var src,copyIsArray,copy,name,options,clone,
target = arguments[ 0 ]||{}.
i = 1,
length = arguments.length,
deep = false;
原型对象
每一个函数在创建时都会被赋予一个prototype属性,它指向函数的原型对象,这个对象可以包含所有实例共享的属性和函数,因此在使用prototype属性后,就可以将实例共享的属性和函数抽离出构造函数将它添加在prototype属性中。
使用prototype属性就很好的解决了单纯通过构造函数创建实例会导致函数在不同实例中重复创建的问题
通过前面的讲解我们知道,构造函数的prototype属性会指向它的原型对象,而通过构造函数可以生成具体的实例,这里就会涉及3个概念,分别是构造函数,原型对象和实例。
针对这3个概念我们会有以下3个问题,如果能将这3个问题解决了,也就理解了原型对象:
1.原型对象,构造函数和实例之前的关系是什么样的?
⒉.使用原型对象创建了对象的实例后,实例的属性读取顺序时什么样的?
3.假如重写了原型对象,会带来什么样的问题?
每一个函数在创建时都会被赋予一个prototype属性,它指向函数的原型对象,在默认情况下,所有的原型对象都会增加一个constructor属性,指向prototype属性所在的函数,即构造函数。
当我们通过new操作符调用构造函数创建一个实例时,实例具有一个_proto__属性,指向构造函数的原型对象,因此_proto_属性可以看做是一个连接实例与构造函数的原型对象的桥梁。
我们通过下面这段代码为构造函数的原型对象添加了4个属性,同时生成两个实例。
原型链
对象的每个实例都具有一个_proto___属性,指向的是构造函数的原型对象,而原型对象同样存在-一-个_proto__属性指向上一级构造函数的原型对象,就这样层层往上,直到最上层某个原型对象为nul
在JavaScript中几乎所有的对象都具有__proto_属性,由__proto__属性链接而成的链路构成了JavaScript的原型链,原型链的顶端Object.prototype,他的_proto___属性为null。
我们通过一个实例来看看一个简单的原型链过程,首先定义一个构造函数,并生成一个实例。
原型链的特点
原型链的特点主要有以下两个:
1.由于原型链的存在,属性查找的过程不在是只查找自身的原型对象,而是会沿着整个原型链一直向上,知道追溯到Object.prototype,如果Object.prototype上也找不到该属性,则返回’undefined’,如果期间在实例本身或者某个原型对象上找到了该属性,则会直接返回结果,因此会存在属性覆盖的问题,
由于特点1的存在,我们在生成在定义对象的实例时,也可以调用到某些未在在定义构造函数上的函数,例如toString()函数。