Js中容易遗漏的知识点
1.
<script>标签中存在一个defer属性,这个属性是可选的。表示脚本可以延迟到文档完全被解析和显示这后再执行。
2.
<script>标签与<img>标签中相同之处,即他们的Src属性均可以指向当前html页所在域之外的的某个域中的URL.这一点让它功能强大,不过也让它倍受争议。利用jsonp实现js的跨域请求就是通过这个特性来实现的。
3.
浏览器按照<script>元素在页面中出现的顺序对它们依次进行解析。
4.
一般情况下,我们都会把css文件和js文件head中,这就意味着必须等到全部的js代码都被下载、解释、执行完成以后,才能开始呈现页面的内容(浏览器在遇到body标签时才开始呈现内容)。如果有大量的js代码需要被执行,那势必就会造成呈现页面时出现延迟,而延迟期间浏览器将是一片空白。为了避免这个问题,我们可以将js代码放在body体中,放在页面的内容后面。如下例所示:
<html>
<head>
….
</head>
<body>
页面内容
<script></script>
<script></script>
</body>
</html>
5.文档模式,
通过使用文档类型(doctype)来切换实现的。很多初学者并不关注这一概念,但这确实会造成影响。虽然文档模式主要影响CSS内容的呈现,但在某些情况下也会影响到js脚本的解释执行。如果文档开始没有发现文档类型声明,则所有的浏览器都会默认开启混杂模式,但采用混杂模式并不被推荐,因为不同浏览器在这种模式下的行为差异非常大。
浏览器不同、版本不同,其呈现引擎不同,即使相同的文档模式,也可能会产生不同的呈现结果。
6.<noscript>元素
。这个元素基本上很少有人用。包含在<noscript>元素中的内容只会在两种情况下显示出来:
1,浏览器不支持js
2,浏览器支持脚本,但脚本被禁用。
第一种情况现在几乎是不存在的,在特别的情况下才会出现第二种情况。
7.如果在函数中,用Var声明一个变量,则该变量是局部的,即退出函数后,该变量会被销毁。如果没有用Var,则该变量是全局的(不推荐,很难维护)。
8,
ECMAScript不支持任何自定义数据类型的机制。它只包括五种基本数据类弄(Undefined,Null,Boolean,Number和String.还有一种复杂数据类型-Object,Object本质上是一组无序的名值对组成的)。利用typeof 操作符可以查看给定变量的数据类型
9.
在其它编程语言中,任何数值除以0都会产生一个错误,从而停止执行后面的代码,但在ECMAScript中,任何数值除以0都会返回NaN,并不会影响其它代码的执行。
10.
各数据类型之间的转换非常重要。
11.
可以对任何数据类型的值调用Boolean()函数,而且总会返回一个Boolean类型的值。转换规则如下:
12.
浮点数值计算会产生传入误差的问题,如0.1+0.2并不等于0.3,这是使用基于IEEE754数值的浮点计算的通病,ECMAScript并非独此一家,其它使用相同数据格式的语言也存在这个问题。因此,永远不要测试某个特定浮点数值。
13.with语句
With语句的作用是将代码的作用域设置到一个特定的对象中,语法如下:
With(expression) statement
定义with语句的目的主要是为了简化多次编写同一个对象的工作,如:
var qs=location.search.substring(1);
var hostname=location.hostname;
var url=location.href;
with(location)
{
var qs2=search.substring(1);
var hostname2=hostname;
var url2=href;
alert(qs2);
alert(hostname2);
alert(url2);
}
14.函数参数
ECMAScript函数不介意传递进来多少个参数,也不介意传递进来的参数是什么类型。即使你定义函数时只接收两个参数,在调用这个函数时也不需要一定要传递两个参数,你可以传递一个,也可以传递三个,解析器永远不会报错。原因是ECMAScript函数的参数在内部是用一个数组表示的,函数接收到的永远是这个数组,而不关心数组中包含哪些参数。实际上,在函数体内可以使用arguments对象来访问这个参数对象,从而获得传递的参数。
Arguments对象只和数组非常相似,但并不是数组(Array)的实例。
如下:
function sayHello()
{
var name=arguments[0]!=null?arguments[0]:"kang";
console.info("Hello "+name+",Welcome to China ");
}
sayHello(‘huilai‘);
sayHello();
也正因为这个特性,函数没有签名,真正的函数重载是不可能做到的。
function add(num)
{
console.info(num+100);
}
function add(num1,num2)
{
console.info(num1+200);
}
add(100);
add(100,2);
结果会输出2个300.由此可以看出,函数名相同,后定义的函数将前面定义的函数覆盖了。
15.基本类型与引用类型
ECMAScript变量包含两种不同数据类型的值:基本类型值和引用类型值。所谓的基本类型值指的是那些保存在栈内存中的简单数据段,即这种值完全保存在内存中的一个位置。而引用类型值则是指那些保存在堆内存中的对象,意思是变量中保存的实际上是一个指针,这个指针指向内存中的另一个位置,该位置保存对象。
在将一个值赋给变量时,解释器必须确定这个值是基本类型还是引用类型。5种基本类型:Undefined,Null,Number,Boolean,String,这五种类型的值在内存中分别占有固定大小的空间,因此可以把它们保存在栈内存中,而且,这样可以提高变量的查询速度。对于保存基本类型值的变量,我们说它是按值访问的,因为它们直接操作的是它们实际保存的值。
如果赋给变量的值是引用类型,则必须在堆内存中为这个值分配内存,因为这种值的大小不确定。但内存地址的大小是固定的,因此可以将内存地址保存在栈内存中。这样,当查询引用变量时,就可以先在栈中读取内存地址中,然后再到堆内存中找到相应的值。这种访问方式我们称为按引用访问,因为变量操作的是值的引用,而不是实际值。
当从一个变量向另一个变量复制值时,会在栈中创建一个新值,然后把该值复制到为新变量分配的位置上。如下:
Var num1=5;
Var num2=num1;
用num1来初始化num2后,这样num2中保存的也是5,但num1中的5与num2中的5是完全独立的,num2中的值只是num1中值的一个副本。此后这两个变量可以参与任何操作而互不影响。
当复制引用变量时,复制的引用的副本,也就是内存地址的副本。所以,这两个变量指向的仍然是同一个对象。操作任何一个都会影响另一个。
var person={
name:"kang",
age: ‘24‘,
sex: "m"
}
var p=person;
p.age=25;
console.info(person.age); //输出的值为2
ECMAScript中函数的参数传递都是按值传递的(java也是)。也就是说,函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。
16
前面提到我们可以用typeof操作符来检测变量值是什么类型。但是,在检测引用类型值的时候,这个操作符的用处不大。通常,我们并不是想知道某个值是对象,而是想知道它是什么类型的对象。为此,我们可以使用instanceof操作符,语法如下:
Var result= variable instanceof constructor
如果变量是给定引用类型(由构造函数表示)的实例,那么instanceof操作符会返回true.
所有的引用类型的值都Object的实例,所以在检测一个引用类型值和Object构造函数时,instanceof操作符会始终返回True.
17
.数组的length属性很有特点,它不是只读的。因此,通过设置这个属性,可以从数组的未尾移除项或向数组中添加新项。如下:
var array=[‘red‘,‘blue‘,‘yellow‘];
array.length=2;
console.info(array[2]); //undefined
array.length=4;
array[3]=‘black‘;
console.info(array[3]); //black
数组提供了类似于其它数据结构行为的方法,如push()和pop()方法可以使得数组的行为像栈,而shift()和push()方法可以使得数组的行为像队列。
18.Function类型。ECMAScript中的函数是对象,因此函数也有属性和方法。每个函数都包含两个属性:length和prototype.其中length表示函数希望接收的命令参数的个数。而对于prototype属性,则是ECMAScript中最耐人寻味的了。对于引用类型而言,prototype是保存他们实例方法的真正所在。在创建自定义引用以及实现继承时,prototype属性是极为重要的。
每个函数都包含两个非继承来的方法:apply()和call().这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内的this对象的值。首先,apply()方法授受两个参数,一个是其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是Array数组,也可以是arguments对象。如下:
function sum(num1,num2)
{
return num1+num2;
}
function applySum1(num1,num2)
{
//this表明函数运行在applySum2()函数所在的作用域内
return sum.apply(this,arguments);
}
function applySum2(num1,num2)
{
return sum.apply(this,[num1,num2]);
}
console.info(applySum1(10,10));
console.info(applySum2(10,20));
call()与apply()的作用一样,它们的唯一区别就是call()函数传递参数时必须直接传递。如下:
apply()和call()最强大的用武之地是能够扩充函数赖以运行的作用域。
window.color="red";
var o={color:"blue"};
function sayColor()
{
console.info(this.color);
}
sayColor();
sayColor.call(this);
sayColor.call(window);
sayColor.call(o);
18.Global对象
所有在全局作用域内定义的属性或函数,都是Global对象的属性。像isNaN(),parseInt(),parseFloat()等方法都是Gobal对象的方法。
EncodeURL()和encodeURLComponet()方法是另外两个很重要的方法。
对应的,有decodeURL()和decodeURLComponent()方法。
Eval()方法是ECMAScript语言中最强大的一个方法,它就像一个完整的ECMAScript解析器,它只接受一个方法,即要执行的ECMAScript字符串。
但这个函数非常危险,使用时应该特别谨慎,特别是在用户输入数据的时候,否则,会产生代码注入。
19.面向对象编程
我们可以通过以下方法来创建一个Object实例,并且为它添加属性和方法。
var person={
name:‘kang‘,
age:24,
sex:‘m‘,
sayName:function()
{
console.info(this.name);
}
}
但它存在一个很大的问题,即每次创建一个对象时都需要写大量重复性代码,于是出现了工厂模式来创建对象。
function createPerson(name,age,sex)
{
var person=new Object();
person.name=name;
person.age=age;
person.sex=sex;
person.showName=function()
{
console.info(person.name);
}
return person;
}
var person2=createPerson(‘huilai‘,22,‘f‘);
person2.showName();
虽然解决了代码重复问题,但还存在一个问题,即无法知道一个对象的类型。于是,构造函数模式出现了
function Person(name,age,sex)
{
this.name=name;
this.age=age;
this.sex=sex;
this.showName=function()
{
console.info(this.name);
}
}
var person3=new Person(‘lai‘,24,‘f‘);
person3.showName();
console.info(person3 instanceof Person);
任何函数,只要通过new操作符来调用,那它就可以作为构造函数。而任何函数,如果不通过new操作符来调用,那它跟普通函数也不会有什么两样
构造函数模式虽然好用,但也存在问题。它的问题是:每个方法都要在每个实例上重新创建一遍
var person3=new Person(‘lai‘,24,‘f‘);
var person4=new Person(‘hui‘,23,‘m‘);
console.info(person3.showName==person4.showName); //false
在上面这段代码中,person3和person4都有一个showName()方法,但这两个方法不是同一个Function的实例。Why?ECMAScript中的函数是对象,因此每定义一个函数,也就是实例化了一个对象。从逻辑角度讲,此时的构造函数也可以这样定义:
function Person(name,age,sex)
{
this.name=name;
this.age=age;
this.sex=sex;
this.showName= new function("console.info(this.name)")
}
从这个角度看,更容易明白每个Person实例都包含一个不同的Function实例的本质。
创建两个完成同样任务的Function实例实在没有必要,况且有this对象在,根本没有必要在执行代码前就把函数绑定到特定对象上面。
为了解决上述问题,于是原型模式出现了。
原型模式(重中之重)
我们创建的每个函数都有一个prototype属性,这个属性是一个对象,它的用途是包含可以由特定类型的所有实例共享的属性和方法。换句话说,不必在构造函数中定义对象信息,可以直接把这些信息添加到原型对象中。
如下:
function Student(age,sex)
{
this.age=age;
this.sex=sex;
}
Student.prototype={
name:‘kang‘,
showName:function()
{
console.info(this.name);
}
}
var s1=new Student(23,‘f‘);
var s2=new Student(24,‘m‘);
console.info(s1.name);
console.info(s2.name);
console.info(s1.showName==s2.showName); //true
关于对prototype的理解,可以见《JAVAScript高级编程》Page 120.
同时使用hasOwnProperty()方法和in操作符,就可以确定该属性到底是存在于对象中,还是存在于原型中。如下:
function hasPrototypeProperty(object,name)
{
return !object.hasOwnProperty(name)&&(name in object);
}
除此之外,还有寄生构造函数模式和稳妥构造函数模式。可以自行百度。
20.继承
许多oo语言都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。由于在ECMAScript中函数没有签名,所以无法实现接口继承。ECMAScript只支持实现继承,而且其实现继承主要是依靠原型链来实现的。
思想:让一个引用类型继承另一个引用类型的属性和方法。我们让原型对象等于另一个类型的实例,此时的原形对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。
如下:
function SuperType()
{
this.property="this is super";
}
//这样是相当于重写prototype属性,所以要显示说明其构造函数是什么类型。
SuperType.prototype={
//在这可以直接一步到位
constructor:SubType,
getSuperValue:function()
{
return this.property;
}
}
function SubType()
{
this.property="this is sub";
}
//让原型对象等于类型的实例。也就继承了SuperType.本质是重写原型对象,代之以一个新类型的实例。因为这一步的存在,不能使用对象字面量
SubType.prototype=new SuperType();
//给原型添加新方法一定要放在替换原型的语句之后。
SubType.prototype.getSubValue=function()
{
return this.property;
}
//显式说明构造函数类型。
//SubType.prototype.constructor=SubType;
var instance =new SubType();
console.info(instance.getSuperValue()); //"this is sub"
console.info(instance.getSubValue());
//由于原型链的关系,我们可以说instance是Object,SubType,SuperType中任一类型的实例。
console.info(instance instanceof Object);
console.info(instance instanceof SuperType);
console.info(instance instanceof SubType);
console.info(instance.constructor);
//调用的实际上是object.prototype中的toString()
console.info(instance.toString());
//只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型。
console.info(Object.prototype.isPrototypeOf(instance));
console.info(SuperType.prototype.isPrototypeOf(instance));
console.info(SubType.prototype.isPrototypeOf(instance));关系图如下:
我们知道,所有的引用类型都继承了Object,这个继承也是通过原型链来实现的。
原型链虽然强大,但也存在问题,如不能向父类传递参数。可以通过"借用构造函数"的技术(主要是通过apply()或call()方法来实现)来克服这个问题。一般情况下,都是原型链和借用构造函数一起使用,很少会单独使用。
function SuperType(name)
{
this.colors=[‘red‘,‘blue‘,‘yellow‘];
this.name=name;
}
SuperType.prototype={
constructor:SubType,
showName:function()
{
return this.name;
}
}
function SubType(name,age)
{
SuperType.apply(this,[name]);
this.age=age;
}
SubType.prototype=new SuperType();
SubType.prototype.showAge=function()
{
return this.age;
}
var sub=new SubType(‘kang‘,24);
//下面返回的是True,也就是说name和colors成为为SubType实例的属性。
console.info(sub.hasOwnProperty("name")); //true
console.info(sub.hasOwnProperty("colors")); //true
console.info(sub.showName());
var sub2=new SubType(‘huilai‘,23);
console.info(sub2.showName());
这样,两个不同的SubType实例就即可以有自己的属性,也可以共用相同的方法了。
组合继承融合了原型链和借用构造函数的优点,成为JavaScript中最常用的继承模式。而且,instanceof和isPrototypeOf也能够识别基于组合继承创建的对象。
不过,大家注意到没有,在组合继承模式中,一共调用了两次SuperType的构造函数。在就导致了在SubType实例中有name和colors属性,在SubType的原型中也有name和colors属性。接下来,我们会想办法解决这个问题。
function inheritPrototype(SubType,SuperType)
{
function temp(){}; //创建临时对象
temp.prototype=SuperType.prototype;
var t=new temp(); //创建临时实例
t.constructor=SubType;
SubType.prototype=t; //还是将实例赋给原型
}
function SuperType(name)
{
this.colors=[‘red‘,‘blue‘,‘yellow‘];
this.name=name;
}
SuperType.prototype={
showName:function()
{
return this.name;
}
}
function SubType(name,age)
{
SuperType.apply(this,[name]);
this.age=age;
}
inheritPrototype(SubType,SuperType);
//增强原型,添加方法
SubType.prototype.showAge=function()
{
return this.age;
}
var sub=new SubType(‘kang‘,24);
//下面返回的是True,也就是说name和colors成为了SubType实例的属性。
console.info(sub.hasOwnProperty("name")); //true
console.info(sub.hasOwnProperty("colors")); //true
console.info(sub.showName());
var sub2=new SubType(‘huilai‘,23);
console.info(sub2.showName());
上面这种方法叫寄生组合继承模式。它的高效率体现在调用了一次SuperType的构造函数,因此避免了在SupType.prototype上创建不必要的属性。
寄生组合继承模式被认为是引用类型最理想的继承范式。
21.匿名函数
匿名函数即没有名称的函数,有时也称为拉姆达函数(lambda).
Arguments.callee是一个指向正在执行的函数的指针,因此可以用它来实现对函数的递归调用。