专业编程基础技术教程

网站首页 > 基础教程 正文

前端开发基础-JavaScript(二) 前端开发教程

ccvgpt 2024-12-17 11:55:16 基础教程 1 ℃

Object

JavaScript的所有对象都衍生于Object对象,所有对象都继承了Object.prototype上的方法和属性,虽然它们可能会被覆盖,熟悉它对于编程能起到很大的作用,也能比较深刻的了解JavaScript这门语言。

前端开发基础-JavaScript(二) 前端开发教程

Object

创建一个对象可以使用new,也可以使用快速创建的方式:

var _object = {};

_object对象中就可以使用Object.prototype中所有的方法和属性,虽然看起来它是空的。说到这里在编程中常常有一个非常有用的需求,如何判断一个对象是空对象。

这是zepto中的判断一个对象是否是空对象,常常使用:

$.isEmptyObject = function(obj) { var name for (name in obj) return false

return true}

也顺便看了下jQuery原理是一模一样的:

isEmptyObject: function( obj ) { var name; for ( name in obj ) { return false;

} return true;

}

使用in操作符来实现,它不会遍历到父原型链。

constructor返回一个指向创建了该对象的函数引用,这个东西主要是可以用来识别(类)到底是指向哪里的。

defineProperty直接在一个对象上定义一个新属性,非常适合用于动态构建,传入三个参数[动态添加对象的目标对象,需要定义或被修改的属性名,需要定义的对象],在第三个参数中可以有些属性来表示是否继承(proto),要不要定义get,set方法,enumerable是否可枚举。

defineProperties跟上述defineProperty一样,但是它可以添加多个。

getOwnPropertyNames返回一个由指定对象的所有属性组成的数组

keys返回一个数组包括对象所有的属性(可枚举)

keys是经常会用到的一个属性,它只能包可枚举的,如果想获取一个对象的所有属性包括不枚举的,那么使用getOwnPropertyNames。

hasOwnProperty用于判断某个对象是否包含有自身的属性,这个方法常常用于检测对象中的属性是否存在,它只检测自身,对于继承过来的都是false,这一点是非常重要的理解。

isPrototypeOf 用于检测一个对象是否在另一个对象的原型链上,比如有两个对象是互相交互的,常常会使用它来进行检测。

propertyIsEnumerable这个方法也比较重要,返回一个布尔值,检测一个对象的自身属性是否可以枚举

可枚举的理解,也就是对象的属性可枚举,它的属性值不可以修改,但是在Js中它有自己的定义,引擎内部看不见的该属性的[[Enumerable]]特性为true,那么就是可枚举的。基本上把一个普通对象可以看做是一个枚举类型,比如var color = {'red':1},red是可以修改的,但是red是可枚举的,但是如果是继承过来的属性,propertyIsEnumerable是返回false的,它还有一个特点,就是自身。

如果要定义不可枚举的属性,那就要使用defineProperty方法了,目前不能用对象直接量或者构造函数定义出来。

var obj = {name: 'jack', age:23}Object.defineProperty(obj, 'id', {value : '123', enumerable : false });

深拷贝与浅拷贝

关于拷贝的问题,主要分为深拷贝和浅拷贝,但是如果从空间分配上来说JavaScript的拷贝不应该算是深拷贝,比如:

var d = {};for(k in a){

d[k] = a[k];

}return d;

今天突然想到了这么一个问题,在C语言中,所谓的拷贝,就是分两种情况,一种是把指针地址拷贝给另外一个变量,虽然也开辟的了一个内存空间,在栈上也存在着一个地址,我对这个变量进行修改,同一个指针是会改变其值的,这种拷贝叫浅拷贝。另外一种情况,直接开辟一个新空间,把需要复制的值都复制在这个新的空间中,这种拷贝叫中深拷贝。

如果看到上述的一段Js代码,很多人说它是浅拷贝,假设传入一个a对象,拷贝完成之后返回一个d,当我修改返回对象的值时并不能同时修改a对象,于是,在这里我有一个很大的疑问,在Js中到底什么是浅拷贝,什么是深拷贝的问题?

这一点上感觉Js真的很奇葩,如果在开发iOS中,不可变对象copy一下,依然是不可变,所以是浅拷贝,拷贝了指针变量中存储的地址值。如果是可变对象copy一下,到不可变,空间变化了,包括不可变mutableCopy到不可变,空间依然变化了,所以是深拷贝。但是JavaScript中对于这一点要考虑一种情况,值类型,和引用类型,这个基础知识,我相信大家都非常清楚。数字,字符串等都是值类型,object,array等都是引用类型。

var a = [1,2,3];var b = a;b.push(4);console.log(a); //[1,2,3,4]var numb = 123;var _numb = numb;

_numb = 567;console.log(numb); //123

从这个例子中可以看的出来,它们使用的都是=符号,而数组a发生了变化,numb数字却没有发生变化。那么从这里,可以有一个总结,所谓了深拷贝,浅拷贝的问题,应该针对的是有多个嵌套发生的情况。不然假设是这样的情况,还能叫浅拷贝么?

var object = {"de":123};var o = copy(object);

o.de = 456;console.log(object) //{"de":123}

明显对象o中的de属性修改并没有影响到原始对象,一个对象中的属性是一个字符串,如果从内存空间的角度上来说,这里明显是开辟了新的空间,还能说是浅拷贝么?那么针对另外一种情况。

var object = { "de":{ "d":123

}

}var o = deepCopy(object);o.de.d = "asd";

如果一个对象中的第一层属性,不是值类型,只单层循环,这样来看的话确实是一个浅拷贝,因为在Js中引用类型用=赋值,实际上是引用,这样说的通。所以,深拷贝,还需要做一些处理,把object,array等引用类型识别出来,深层递归到最后一层,一个一个的拷贝。

var deepCopy = function(o){ var target = {}; if(typeof o !== 'object' && !Array.isArray(o)){ return o;

} for(var k in o){

target[k] = deepCopy(o[k]);

} return target;

}

思路是如此,这个例子只考虑了两种情况,对象和数组,为了验证这样的思路,最后的结果与预期是一样的。

var _copy = { 'object':{ 'name':'wen'

}, 'array':[1,2]

}var h = deepCopy(_copy);h.object.name = 'lcepy';h.array[1] = 8;console.log(h);console.log(_copy);

面向对象

面向对象的语言有一个非常明显的标志:类,通过类来创建任意多个具有相同属性和方法的对象,可惜的是Js里没有这样的概念。

但是Js有一个特性:一切皆是对象。

聪明的开发者通过这些特性进行摸索,于是迂回发明了一些程序设计,以便更好的组织代码结构。

工厂模式

主要是用来解决有多个相同属性和方法的对象的问题,可以用函数来封装特定的接口来实现

var computer = function(name,version){ return { 'name':name, 'version':version, 'showMessage':function(){ alert(this.name);

}

}

}var test = computer('apple','11.1');test.showMessage();

构造函数模式

我们知道像原生的构造函数,比如Object,Array等,它们是在运行时自动出现在执行环境中的。因此,为了模仿它,这里也可以通过一个普通的函数,并且new出一个对象,这样就成为了自定义的构造函数,也可以为他们添加自定义的属性和方法。

但是这样的构造函数有一个缺陷,就是每个方法都会在每个实例上创建一次,因为每次创建都需要分配内存空间,但是有时候这样的特性还是有用的,主要是要控制它们,在不使用的时候释放内存。

var Computer = function(name,version){ this.name = name; this.version = version; this.showMessage = function(){ alert(this.name);

}

}var apple = new Computer('apple',2014);var dell = new Computer('dell',2010);apple.showMessage();dell.showMessage();

像apple,dell是通过Computer实例化出来的不同的对象,但是它们的constructor都是指向Computer的。这里也可以使用instanceof来对(对象)进行检测。

在书写上构造函数跟其他函数是没有什么区别的,主要的区别还是在使用上,构造函数需要使用new操作符。

其实这样的书写,已经跟类没有什么区别了,表面上来看,而构造函数我个人更倾向于一个类的某个静态方法。

原型模式

说到原型模式就不得不提一提关于指针的问题,因为每一个函数都有一个prototype属性,而这个属性是一个指针,指向一个对象。

C语言描述指针,这个在iOS开发中非常重要

比如我先定义一个int类型的指针变量和一个普通的int类型数据,然后给指针变量赋值。

int *p; int pp = 123;

p = &pp;

*p = 999; printf('%d',pp);

*是一个特殊符号用于标明它是一个指针变量。

&是地址符

分析这个就要说到栈内存和堆内存了,比如*p在栈内存中分配了一个地址假设是ff22x0,它还没有空间。而pp存在一个地址ff23x0,并且分配了一个空间存储着123,这个地址是指向这个空间的。

p = &pp 这样的赋值操作,也就是把ff23x0取出来,并且给p分配一个空间把ff23x0存储进去,并且ff22x0指向这个空间。

*p = 999 从这里就可以看出来p操作的是地址,而这个地址不就是ff23x0么,于是pp成了999。

所谓的指针也就是存储着地址的变量。

回到原型上,如果每一个函数中的 prototype属性都是一个指针,实际上它只是一个地址引用着一个空间,而这个空间正是我们写的xxx.prototype.xxx = function(){}这样的代码在运行时分配的空间。那么可见,使用原型的好处是空间只分配一次,大家都是共享的,因为它是指针。

先看一个例子

var Computer = function(name){ this.name = name;

}Computer.prototype.showMessage = function(name){ alert(name);

}var apple = new Computer('apple');var dell = new Computer('dell');Computer.prototype.isPrototypeOf(apple);

在解释这个原型链之前,还要明白Js的一个特性,就是如果自身不存在,它会沿着原型往上查找。它的原理稍微有些绕,Computer自身的prototype是指向它自身的原型对象的,而每一个函数又有一个constructor指向它自身,prototype.constructor又指向它自身。于是Computer的两个实例apple,dell内部有一个proto属性是指向Computer.prototype的,最后的结果是它们可以使用showMessage方法。

当然它们还有一个搜索原则,比如在调用showMessage的时候,引擎先问apple自身有showMessage吗?“没有”,继续搜索,apple的原型有吗,“有”,调用。所以从这里可以看出,this.showMessage是会覆盖prototype.showMessage的。

另外还可以使用isPrototypeOf来检测一个对象是否在另一个对象的原型链上,上述的代码返回的是true。

apple.hasOwnProperty('name')apple.hasOwnProperty('showMessage')

使用hasOwnProperty来检测到底是对象属性还是原型属性,使用this创建的属性是一个对象属性。

从上面可以看出来原型链的好处,但是它也不是万能的,正因为指针的存在,对于某些引用类型来说这个就非常不好了,我需要保持原对象属性值是每一个对象特有的,而不是共享的,于是把之前的构造函数与原型组合起来,也就解决了这样的问题。

var Computer = function(name){ this.name = name;

}Computer.prototype.showMessage = function(){ alert(this.name);

}var apple = new Computer('apple');apple.showMessage();

这样的结果是在对象中都会创建一份属于自己的属性,而方法则是共享的。

动态原型模式

有时候遇到某些问题需要动态添加原型,但是实例中是不能添加的,所以绕来一下,在初始化构造函数中添加。

var Computer = function(){ if(typeof this.showMessage !== 'function'){ Computer.prototype.showMessage = function(){

}

}

}

只要初始化了一次,以后就不用修改了。

寄生构造函数模式

这种模式的原理就是在一个函数中封装需要创建对象的代码,然后返回它。

var test = function(name){ return { 'name':name

}

}var g = new test('apple');var f = de('dell');

看起来它跟工厂模式还是很像的,

稳妥模式

这种模式主要是在解决需要安全的环境中使用,一般来说一个类如果不提供getter,setter方法,是不允许直接访问和修改的。

var computer = function(name){ var _name = name; return { 'getter':function(){ return _name;

}, 'setter':function(name){

_name = name;

}

}

}

这样的方式可以保证属性或者说是数据的安全性,不允许直接随便修改,如果不提供setter方法的话,压根就不允许。

早读课提醒

言归正传我们在微信群中推出了《早读课》,每日分享一篇我们认真精选的文章(不限于前端开发类),其目的是帮助开发者来学习有价值的东西。想加微信群的朋友,直接添加我的微信号:icepy_1988,审核之后会邀请你入群。想加QQ群的朋友,可以直接添加:418898836,答对问题即可入群。

关注我们

更多精彩内容可关注微信公众号:搜索 fed-talk ,来关注我们吧,也欢迎你将它分享给自己的朋友。

Tags:

最近发表
标签列表