Javascript王者归来

本文深入探讨了JavaScript的特性和安全问题。JavaScript基于对象,无类但有构造器实现类的功能,采用基于原型的继承。它广泛应用于浏览器,提供防御性编码以适应不同浏览器的兼容性。JavaScript通过DOM和浏览器对象接口控制页面,利用闭包处理变量。同时,文章提醒避免过多全局变量,提倡把脚本指令放在运行阶段执行。JavaScript的安全隐患包括表单篡改、数据伪造和数据采集,提醒开发者注意客户端验证的不安全性。此外,介绍了JavaScript的生命周期、脚本位置、数据交互方式(如Ajax)以及编码安全实践。通过对闭包、依赖管理和冲突的讨论,强调了良好的代码组织和面向接口设计的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基于对象。JavaScript拥有对象,可以包含数据和处理数据的方法。对象可以包含其他对象。它没有类,但却有构造器可以做类能做的事,包括扮演类变量和方法的容器的角色。它没有基于类的继承,但它有基于原型的继承。

目前绝大多数浏览器中都嵌入了某个版本的JavaScript解释器。这样它就允许可执行的内容以JS的形式在用户客户端浏览器中运行。

<noscript>您的浏览器不支持JavaScript,请检查浏览器版本或者安全设置,谢谢!</noscript>

<noscript></noscript>是一种防御性编码,如果用户浏览器不支持JS或者设置了过高的安全级别,那么就会显示出相应的提示信息,避免在用户不知情的情况下停止运行或者得到错误结果。

JS通过浏览器对象接口访问和控制浏览器元素,通过DOM接口访问和控制HTML文档,通过给文档定义“事件处理器”的方式响应由用户触发的交互行为。Document是JS访问页面文档对象的主要方式。Window提供了对浏览器、窗口、框架、对话框以及状态栏的访问方法。

要避免定义过多的全局变量。在JS中,利用闭包是一种代替临时变量的好习惯。
在某些特定情况下,定义在函数体内部的匿名函数在执行的过程中形成“闭包”。

void是JS的一个特殊的运算符,它的作用是舍弃任何参数表达式的值,这意味着要求解析器并计算参数表达式内容,但是却忽略其结果。如果刻意去检查void运算的返回值,会发现它返回一个undefined标记(事实上任何一个不带return指令的函数运算的默认返回值都是undefined)在浏览器的缺省行为中,undefined阻止了页面的跳转。

undefined在一定情况下可以取代void(0);

undefined代表“无”;null代表“空”。空依然是一种存在,而无则是存在的对立面。由于JS没有强制检验对象存在的机制,所以它承认无的概念。任何一个未经定义和使用的标识,均可以用“无”来表示。当你直接引用一个未声明的标识,或者声明了一个变量却未对其进行赋值,那么typeof操作返回的结果将是undefined。
在浏览器地址栏输入:

JavaScript:alert(typeof(x));   >>> undefined 未声明
JavaScript:var x;alert(typeof(x));  >>> undefined 未赋值
JavaScript:var x=null;alert(typeof(x));   >>>object

客户端JS的生命周期起始于浏览器开始装载某个请求的特定数据,结束于浏览器发起一个新的请求(通常意味着页面的跳转或者刷新)。

进一步细分:客户端生命周期可以划分为从页面数据被装载到页面数据装载完毕的初始化阶段以及页面数据装载完毕一直到新的请求被发起之前的运行阶段。在前一个阶段里,JS代码被浏览器解析,运行环境被初始化,函数和闭包被建立,而那些可以被立即执行的指令被执行并实时地得到结果。在后一个阶段里,完成初始化的程序化环境进入了一个缺省的等待消息的循环,捕获用户操作引发的事件并做出正确响应。

将JS代码放<head> 头部是在生命周期的初始化阶段执行(更安全,指令不会对装载期的文档内容产生影响,脚本指令被注册到body的onload事件中执行<body onload="PageLoad()"> 元素的onload事件将在元素被完全加载后由浏览器发起);放在<body> 是在运行期内执行。(可能会导致文档数据不能加载完全)

一个比较好的习惯就是把除声明之外的所有的脚本指令都放到运行阶段来执行,这样避免了因为初始化期间的DOM元素加载失败或者低级的次序问题而导致脚本失效。

提交表单操作是浏览器提供的一种数据交互的默认方式。现在更多的应用系统采用XML HTTP作为主要的数据交互方式。XML HTTP逐渐取代传统的提交表单,标志着Ajax技术的日趋成熟。

学会合理利用禁忌所带来的安全性,只有在必要的时候才去破解它们,是成为一个优秀程序员所必需掌握的技能。

利用JavaScript的常见攻击有以下几种:
1.伪造表单提交目的地,从而窃取数据。
HTML表单的提交有form的action属性决定,而JS具备从客户端修改form的action的能力。

<body>
    <form name="myForm" action="a.action">
        <input name="data" type="text" value="a"/>
        <input type="submit"/>
    </form>
</body>

以上代码定义了一个将数据提交到a.action的表单,在浏览器中访问该页面,点击提交表单按钮,input框中的内容将被提交到a.action处理域。但是在提交表单之前,先清除浏览器中原地址栏中的内容,并输入JavaScript:document.myForm.action="b.action;void(0);" 之后回车,那么数据实际将被提交到b.action处理域中。

2.伪造数据,绕过合法性验证

<form name="myForm" action="a.html" onsubmit="return !/\s+/.test(document.myForm.textData.value) && /\d+/.test(document.myForm.numData.value) || alert('请输入正确的格式!') || false;">
    <input name="textData" type="text"/>
    <input name="numData" type="text"/>
    <input type="submit"/>
</form>

以上代码对表单的合法输入进行校验,要求用户输入的textData域非空,并且numData域为数字串,如果输入非法的话则弹出提示信息阻止表单的提交。
在浏览器 输入JavaScript:document.myForm.onsubmit=function(){return true;};void(0); 轻而易举地便让表单校验失效。
所以,依赖客户端的校验是不安全的。

3.采集数据,窃取网页内容
将一些合法网站上的数据内容通过HTTP请求等方式获取并发布到自己的网页上。
最原始的数据采集源于浏览器对框架的支持。由于主流浏览器上支持任意个数的嵌套框架,因此,一些人将他人制作的网页嵌入自己页面的框架中,作为自己网站内容的一部分。网络上的“盗链”形式的资源窃取由来已久,防御手段是在页面上加入如下代码:

<script>
    if(self != top){
        top.location = self.location;
    }
</script>

利用JS的特性阻止页面文档出现在框架内。当然也有对应手段同样用JS使得上述代码失效(利用XML HTTP请求页面之后将其中的这一段JS代码屏蔽掉)

安全:采用编码后的数据作为内容,再通过特定的模板生成静态的HTML/XML页面文档,最后利用客户端JS将文档动态输出。
这里写图片描述

绝招:JavaScript:document.body.outerHTML

这里写图片描述

按照独立功能的划分将一组相互依赖的或者功能相近的模块写在一个<script>块内,将功能相对独立彼此孤立的代码分开写入多个<script>块。至于一组通用的功能,更好的办法是写入一个独立的文件,甚至,如果必要的话,将他们封装入一个对象中。
推荐一个比较好的规范:
对于页面上的JavaScript代码来说,将全局变量(如果有的话)和全局变量的初始化放在一个单独的<script> 标签里,置于<head><body> 之间,并且可以的话,尽量采用额外的属性来标记它们,例如<script region="global"> 。将闭包和孤立函数放在<head> 标签的末尾(急结束标记</head>之前)。
将页面装载期间执行的指令、DOM对象初始化以及DOM相关的全局引用赋值操作放在<body> 标签的末尾。
事实上应尽量避免全局DOM引用,因为浏览器通常很难释放它们消耗的内存,换句话说,尽量不要让你的脚本出现在结束标记</body>之前。
将需要引用到的外部JavaScript文件按照相互依赖的先后次序放在<head>标签中的<meta>标记之后,<title>标记之前。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <script type="text/javascript">引用外部CSS</script>
    <title>Document</title>
    <script>
        //放置函数和闭包
        function add(x,y){
            return x+y;
        }
    </script>
</head>
<script>
    //这里放置全局变量
    var a = 100;
    var b = 200;
</script>
<body>
    <h1></h1>
    <script>
        //这里放置程序实际执行的代码
        document.getElementByTagName("h1")[0].innerHTML = add(a,b);
    </script>
</body>
</html>

<script>标签提供了一个不太常用的属性defer,可以显示生命脚本在页面装载完成之后才被执行。defer最大的好处是当你嵌入一段脚本时,不必考虑它所依赖的dom对象是否被成功装载完成。不过不主张采用defer,因为当代码中有alert或者document.write类似的输出时,defer的表现有些奇怪。

闭包经典

function A(){
            alert(1);
        }
        A();
        function A(){
            alert(2);
        }
        A();
        A = function(){
            alert(3);
        } 
        A();                          >>>>>2,2,3

依赖和冲突:
假如a.js中需要一个名叫x的对象,而这个x又在b.js中被定义,就产生了a.js对b.js的依赖;
假如c.js和d.js中都定义了一个叫做y的对象,而碰巧它们对y的解释是不同的,就产生了c.js和d.js的冲突。

面向接口就是一种很好的避免冲突的模式。
对于管理依赖的问题,在文件头部进行充分的描述和预防性地检测是一个比较妥当的方案。

常见的标准字符集有ASCII、ISO Latin-1、GBK、Unicode。其中ASCII是7位编码的字符集,它基本上只适用于英语,8位的ISO-Latin-1则支持大部分拉丁语系的语种,而16位编码的GBK和Unicode则充分支持了汉语系的东方语种。

有经验的程序员仅仅通过ONLINE、Online、online就能区别出它们分别代表一个常量、一个类和一个局部变量、属性或者方法。

HTML标签名和属性并不区分大小写。但是JavaScript访问DOM元素时却区分大小写。这意味着HTML的不规范书写方式可能直接影响到JavaScript的正确执行。

<button onclick="alert('a')">a</button>
<BUTTON onClick="alert('b')">b</BUTTON>

document.getElementsByTagName("button");//只能取到a,取不到b,因为JS对元素标签的大小写敏感。
而DOM属性b.onClick为空,b.onclick才有正确的值,这是因为JS对于DOM事件类型名采用小写。

行匹配原则:如果一行代码能够成为完整的句子,不管它是否以分号结束,都将被作为缺省了分号的完整句子来对待,这样容易造成问题。

JavaScript是一种“弱类型”的语言,具体表现为JavaScript的变量可以存储任何类型的值。即,数据类型和变量是不绑定的,变量的类型通常要到运行时才能决定。
建议对所有变量都遵循先声明再赋值的原则。在一些特殊情况下,如某些面向对象和面向接口方法以及闭包的某些特殊用法,允许不声明直接使用全局变量。闭包和函数是独立的域。

对于未声明也未赋初值的变量,如果直接使用,会抛出一个系统级别的Error ,唯一的例外是typeof操作,对于typeof操作,任何一个未赋初值的标识符,不论是否已经被声明,都将返回一个字符串 undefined作为结果。

//a未声明
alert(typeof(a));   //undefined
alert(a);   //Error

typeof这个特性可以用来判断和处理脚本程序的某些特性之间的依赖。如判断某个对象或者某个域是否已经被声明过。

if(typeof(System) == "undefined"){
    System = new Object();
}
//判断System.Core域是否已经被声明过
if(typeof(System.Core) != "undefined"){
    do sth .. //如果已经声明过了,做一些处理,以防止意外地覆盖了原始域
}

变量的作用域
对变量的查找总是从当前域开始,递归向上查找各级嵌套的父域,最后到达全局。

闭包

function step(a){
    return function(x){
        return x+a;//内层的闭包可以访问外层的局部参数a
    }
}
var b = step(10);
alert(b(20));//得到30
var c = step(20);
var c = step(20);
alert(c(20)); //得到40

算数运算符
“+”总是把对象转换成可以进行加法运算或者进行连接操作的数值或字符串,这一转换是通过调用对象方法valueOf()或toString()来执行。
“+”可以较方便地将一个数值转换成字符串,具体操作是将这个数值加上一个空串。

var a = 2.96;
var b = 1.0;
alert(a + b);//得到数值3.96
alert(a + "" + b);//得到字符串"2.961"

“-”总是对两个运算符进行代数减法操作,如果运算数是非数值的,会试图将它们转换成数值,这一转换和调用parseFloat方法的数值转换类似,但是通常效率更高。如果转换失败,将会得到特殊的值NaN.
“-“通常用来将字符串快速转换成数值,具体操作是将这个字符串减去一个数值0.

var a = "2.96";
alert(a + 1);//得到字符串"2.961"
alert(a - 0 + a);//得到数值3.96

运算符的隐式数值转换和parseFloat并不完全相同。对于字符串来说,前者是一种完全匹配,而后者是一种解析过程;对应布尔常量来说,前者总是将true转换为数值1,将false转换为数值0,后者则得到NaN;对于对象来说,前者总是先试图调用对象的valueOf()方法进行转换,如果失败再调用 toString()方法,而后者则直接尝试调用对象的toString()方法。

JavaScript:parseFloat("123abc");//得到数值123
JavaScript:"123abc"-0;//得到NaN

“/”JavaScript不区分数值的精度类型。因此当你用一个整数来除另外一个整数时,得到的结果可能是浮点数,如果你希望得到整数商,你就可能需要调用Math.floor()方法来进行精度处理。当除法的除数为0时,运算结果通常为Infinity,而如果是0/0,则结果将得到NaN。

“%”取模运算也适用于浮点数,运算结果的精度和两个运算数中精度最高的那个相同,-4.3%2.11得到-0.08

关系运算符
两个运算符中只要有一个是NaN或undefined,或者被转换成NaN或undefined,那么运算符“>””<”“>=””<=” “==”都返回false.
JS对象的深度比较 132

null == undefined  >>>true
null === undefined  >>>false
null >= null      null <= null  >>>>true

逻辑与&&操作符时  null\NaN\0\undefined都会被转为false
对任何表达式a连续使用两次”!”运算符(即!!a)都可以将它转换成一个布尔值

逗号运算符
 先计算左边的运算数,再计算右边的运算数,并将右边运算数的计算结果作为表达式的值返回。

x = (i=0,j=1,k=2)
等价于
i=0;
j=1;
x=k=2;

对象运算符:(in、instanceof、new、delete、. 、[])

var point = { x:1,y:1 };//定义一个对象
var has_x_coord = "x" in point;//值为true
var has_y_coord = "y" in point;//值为true
var has_z_coord = "z" in point;//值为false
var ts = "toString" in point;//继承属性:值为true

instanceof要求其左边的运算数是一个对象,右边的运算数是对象类的名字。如果该运算符左边的对象是右边类或者它的派生类的一个实例,它返回false,否则返回true。

alert("" instanceof Object);
alert({} instanceof Object);
alert([] instanceof Array);

如果instanceof运算符的坐运算数不是对象,或者右运算数是一个对象,而不是一个类,它将返回false。另外如果它的右运算数根本不是一个对象,它将抛出一个系统级别的异常。

delete 用var语句声明的变量也不能被删除。如果delete使用的运算数是一个不存在的属性,它将返回true.

typeof对对象、数组和null会返回object

void运算符
可以出现在任何类型的操作数之前,作用是舍弃运算数的值,返回undefined作为表达式的值。考虑到向后兼容性,用表达式void(0)比使用undefined属性更好。

while(true)会创建一个无限循环
缺省函数名的函数被作为一个匿名函数或者闭包使用。
with语句用来暂时修改默认的作用域。在实际应用中,with语句的作用是减少程序代码的输入,特别是当代码作用于一些深层次的对象时,例如

frames[1].document.forms[0].name.value = "";
frames[1].document.forms[0].address.value = "";
frames[1].document.forms[0].email.value = "";
可以写成:
with(frames[1].document.form[0]){
    name.value = "";
    address.value = "";
    email.value = "";
}

数据类型
在JS中,要将变量中的整数类型用十六进制方式表示,可以通过调用number的toString(16)方法来完成。
toString()方法,它接受一个从2到36的整数作为参数,作用是把数值表示成参数所指定进制的数。
var x = 33;
var y = x.toString(2); //y的值为字符串"100001"

也可以用数值常量直接调用toString方法,但是必须采用括号将数值常量括起来作为表达式使用。
var y = (255).toString(16); //y是“FF”
当一个浮点数值大于所能表示的最大值时,其结果是一个特殊的无穷大,JS将它输出为Infinity.负无穷大为-Infinity。
当JS表达式计算中出现无法用数值表示的“数值型”结果时,它总是返回一个NaN.NaN可以理解为一个无法用数值表示的特殊“数值”。NaN这个符号同任何数值包括它自己在内都不相等,所以只能用一个特殊的isNaN()方法来检测这个值。
内置函数isFinite()当数值为+-Infinity或NaN时返回false,否则总是返回true,可以用它来判定常规数值。

字符串
charAt(index)方法可以获取index位置上的字符;
charCodeAt(index)方法则可以获取index位置上字符的Unicode编码;
substring和slice方法可以抽取子串;
indexOf(char)方法可以查找指定字符在字符串中第一次出现的位置;
search方法可以查找和匹配子串;
replace方法可以完成子串的替换;

var str = "www.51js.com";
        //indexOf得到字符串中字符的索引
        var pos = str.indexOf(".");
        //substring获取从指定索引位置开始的子串
        document.write(str.substring(pos+1) + "<br/>");//51js.com
        //split分割字符串
        var parts = str.split(".");
        document.write(parts[1] + "<br/>");//51js
        //search匹配字符串,返回匹配处的索引
        pos = str.search("51js");
        document.write(pos + "<br/>");//4
        //replace替换字符串
        str = str.replace("51js","无忧脚本");
        document.write(str);//www.无忧脚本.com

数值
1.可以使用构造函数Array()来创建一个数组 var a = new Array();
2.采用数组常量创建数组 var table = [base,base+1,0.9];
由于JS是一种动态类型语言,因此数组元素不必具有相同的类型。

var a = new Array(1,2,3); 这里初始化一个新数组,a[4][0]==1;a[4][1]==2;a[4][2]==3;

JS并不直接从语法上支持多维数组,但是由于它的数组元素还可以是数组,利用这一特性可以轻松实现多维数组这样的结构。
var a = new Array(10); 如果只给构造函数传递一个参数,并且这个参数是数值的话,该参数指定的是数组的长度。 创建的是具有10个未初始化的元素的新数组。
length属性可读可写,如果设置数组的length为0,可以清楚数组中的所有元素(不包括那些下标不为整数的数组元素)
数组常量中还可以直接存放未初始化的元素,只要在逗号之间省去该元素的值就可以了。var sparseArray = [1,,,,,5];

对象
访问对象的属性可以通过对象运算符”.”来访问,也可以通过集合运算符”[]”来访问;
point.x和point[“x”]的两种访问方式是等价的,前者将point当作一个对象,x是它的属性;而后者将point当作一个哈希表,”x”是它的索引。

JSON
JSON是由JavaScript发展而来的一种简单的数据交换协议,它的数据格式就是一个合法的JS对象常量。{a:100,b:" abc",c:true,d:[1,2,3]}一个简单的用JSON形式表示的数据对象

函数
由于函数具有被调用时创建封闭环境的特性,因此在某些情况下它又被称为“闭包”。
JS允许在函数体中定义并返回另一个函数,这个嵌套定义在函数体内的函数在调用时创建“闭包” 由于JS拥有闭包,因此它具有明显的functional(函数式)的特征
匿名函数:
function关键字后的标识符表示的是喊出常量的名字,它可以缺省。缺省名称的函数是匿名函数,它可以被直接调用、赋值给某个变量或者出现在某些表达式中。
匿名函数最直接的用法是将它作为常量直接赋给对象属性,作为对象的方法。

null:
null常常被看作对象类型的一个特殊值,代表对象为”空”或者变量没有引用任何对象。但是注意typeof(null)的值是object null本身不是对象,不要把它看作特殊的对象,它是JS的关键字
undefined:
表示”无值”,typeof(undefined)的值是undefined 一个未定义的变量,或者已经声明但还未赋值的变量,又或者一个并不存在的对象属性,它们的值都是undefined.
undefined == null >>>true 用处:my.prop == null 如果属性my.prop并不存在或者它存在但是值为null,那么这个表达式的值为true
要明确区分null和undefined,可以使用===或者typeof
只声明这个变量,并不初始化它,就可以确保它的值为undefined。另外,void运算提供另一种获取undefined值的方法。

值与引用的相互转换:装箱和拆箱
JS为基本数据类型提供了对应的引用类型对象,使得这些基本数据类型可以进行值和引用类型的转换。把基本数据类型转换为对应的引用类型的操作被称为装箱boxing,把引用类型转换为对应的值类型,被称为拆箱unboxing。
JS中为数值提供的类型是Number,为字符串提供的类型是String,为布尔型提供的是Boolean。
把一个值装箱,就是用这个值来构造一个相应的包装对象。

var a = 10,b = "JavaScript",c = true;
var o_a = new Number(a);
var o_b = new String(b);
var o_c = new Boolean(c);

o_a、o_b、o_c分别是a、b、c对应的包装对象,他们的值就是a、b、c的原始值。o_a、o_b、o_c是引用类型,对它们进行比较时,是对引用进行比较,而不是对内容进行比较。typeof作用于它们的结果都是object.
装箱最大的作用就是将值作为对象来处理。

var b = (255).toString(16);  //隐含了一个类型转换,将数值255转换为对象Number(255)

对包装对象拆箱非常简单,只要调用它们的valueOf()方法,就能得到原始值。事实上,Number、String、Boolean的另一个用处是直接作为普通函数调用它们,可以返回相应的值类型,如Number(10)返回数值10.
程序操作值类型数据通常比操作引用类型数据快得多(不要额外寻址)

JS的弱类型,不是指JS的数据没有类型区别,而是指JS的变量不关心它们存储的内容的数据类型。
运行时类型的识别:typeof、instanceof运算符、constructor属性、甚至自己实现的机制
强制类型转换:parseInt() parseFloat() toString()、通过构造函数强制转换var num = 100;var str = String(num);//强制转换为String

浮点数精度问题
JS存在严重的浮点数精度缺陷alert(86217.8+45.6)结果不是86263.4 而是86263.40000000001
在进行浮点运算前,一个合理的做法是事先确定好问题的精度范围,JS提供了几个取整的函数,来限定解的精度。分别是Math.floor() 取比当前数值小的最大整数、Math.round() 四舍五入、Math.ceil() 取比当前数值大的最小整数 、Number对象提供的toFixed() 保留几位小数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值