对比犀牛书那本厚厚的“字典”,《javascript高级程序设计》可以算是一本由浅入深的好读物。。这段时间翻了前几章的内容。顺便今天mark做个总结。
Javascript基础知识速查表
写在JavaScript语法之前
javaScript应该放在哪?
JS文件应该放在后面
其实这是一个很笼统的说法。一般来我们都知道CSS和JS的文件放置顺序都是。CSS在前,JS在后。这里涉及到一个网页的渲染机制:
网页渲染机制
- 1.浏览器解析代码:
这里主要分为①浏览器解析HTML代码生成DOM树,②浏览器解析CSS文件生成CSS树,③浏览器解析JS文件。生成JS树(不过我更愿意成为渲染树)。 - 2.布局:
在屏幕上绘制渲染树中的所有节点的几何属性,比如: 位置,宽高,大小等等 - 3.绘制元素:
绘制所有节点的可视属性,比如:颜色,背景,边框等.(这里可能会出现多个图层。和PS有点像) - 4.将重叠的图层合并。
这时候把CSS文件放在前面就很好理解了,因为加载文件是一步步来的,如果CSS文件放在后面,很有可能出现白屏现象或者,样式很丑等很久之后突然变了一下。所有CSS文件一般放在前面。
JS文件放在后面,主要是因为涉及到一个JS的加载阻塞,如果这个JS文件特别大,会使得页面加载速度很慢。影响用户体验。
白屏和无样式内容闪烁
之前看浏览器网页的渲染机制的时候正好看到这个。用自己话试着说一说:
白屏主要是针对于IE浏览器。
因为IE浏览器的渲染机制是等样式表全部加载完之后才才呈现整个页面,不是和FF浏览器一样采用逐步呈现,这样就导致如果把样式表放在底部。中间就会有一个等待的时间,会出现白屏。
就好比IE觉得在所有的样式还没加载完之前,就“刷墙”,那万一后面要的不是这个颜色,岂不是还要再刷一道墙?索性我就等后面具体怎么弄的总方案下来之后,再做。这样就只用做一次。不过我觉得白屏让我更难受- -,如果一个网站白屏时间长了,我就退了。无样式内容闪烁主要是针对于firefox。
出现无样式内容闪烁,这点主要是与firefox的加载机制有关,firefox主要是采用逐步呈现的方式加载,就是得到什么指令我就做什么指令,这样就使得,如何某些元素的效果和我们最终想要效果一致的时候就会出现一直闪动。解决方法:用link标签将样式表放置在head标签中。
JavaScirpt的加载机制
之前说到JavaScript的加载阻塞这个特性,JavaScript具有加载阻塞这个特性,所以一般都讲JS文件放置在页面底部
那有没有什么能改善这种问题的方法呢?看下面:
async与defer
由于才接触JS不久,这里就直接用例子说明吧。
<script src="script.js"></script>
没有 defer 或async,浏览器会立即加载并执行指定的脚本,“立即”指的是在渲染该 script 标签之下的文档元素之前,也就是说不等待后续载入的文档元素,读到就加载并执行。
<script async src="script.js"></script>
有 async,加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)。
<script defer src="myscript.js"></script>
有 defer,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded事件触发之前完成。然后从实用角度来说呢,首先把所有脚本都丢到 </body> 之前是最佳实践,因为对于旧浏览器来说这是唯一的优化选择,此法可保证非脚本的其他一切元素能够以最快的速度得到加载和解析。
这就很好理解defer和async的特点了:
- 两者作用对于下载来说,都是异步。
- 而defer是等所有文件都下载完全后,再进行加载,async则是,下载异步,但是我下载完之后就立马执行,等不了。
JavaScript语言介绍
标识符
什么是标识符?
var new;//不是标识符
var _new;//标识符 _new
var $new; //标识符$new
标识符的三个特征:
- 区分大小写
- 首字符必须为字母,$,和_和下划线
- 关键字和保留字不能当标识符
弱类型语言
对于JAVA C语言之类的逻辑性比较强的计算机语言而言,JS可谓是有点弱。弱在哪?主要是以下几点:
- 变量声明很松散:
var arr=1;
var arr="1";
var arr=[1,"23",3];
var arr=function(){};
相比于JAVA需要声明变量的时候带上变量的类型,JS相当于 先占坑,谁来谁上车一样,先声明一个变量在这,谁第一个来,就是什么类型的变量。而不用先声明是什么样类型的变量,再选择变量的值。
JS的数据类型:
JS的数据类型主要分为两类,一类是基本数据类型,一类是引用数据类型:
基本数据类型
- Undefined类型
var z_one=undefined;
var z_one1;
//两者都是undefined;
- Null 类型
var z_none=null;
这里说一点null和undefined的区别:
null表明这个变量是空。就是什么都没有,而undefined表明这个元素没主啊,快来个“东西”把这个坑填了。
*Boolean类型:
var z-one=true;
*Number类型:
var z_one=1;
number类型数据的转换
涉及到number类型数据的转换有三个函数:
- Number();
- parseInt();
- parseFloat();
对于Number();我还是举个例子:
var num1=Number("123"); //123;
var num2=Number(true);//1;
var num3=Number(" ");//0;
var num4=Number("hello world") //NaN;
这里我总结的很简单。我感觉主要就围绕下面这几点:
字符串类型:
"123"这样的直接转换为123。
"0123"这样的转换为123(可以理解为忽略了首位为0不转换?)
" "这样的转换为0;
"x12"或"0012x2" 这种的转换为NaN(not a number)还是挺形象的。
布尔值类型:
true 为1
false 为0(这点貌似所有计算机语言都通用,1为真,0为假)
null 传入0
undefined 传入NaN(我理解是因为undefined是未定义的,什么都有可能,就用NaN 作为传入,保险一点。)
引用类型:先用valueOf();调用按上面方法传入,如果是NaN,再用tostring()方法调用,当然规律和上面一样。
parseInt(),和parseFloat();
看int和float理解也知道一个是传入整数,一个是传入小数。使用方法是:
var a="12x3'
var b="12.3'
var num1=parseInt(a);//12
var num2=parseInt(b);//12
var num3=parseFloat(a);//12
var num4=parseFloat(b);//12.3
Number()和parseInt()的区别
1.Number() 和parseInt()如果是遇到"x123"都转换为NaN,
但是对于"12x3"Number()转换为NaN,而parseInt()则转换为12。
*String类型:
var z_one="12";
引用数据类型
var a=[1,2,3]
var b=function obj(){};
引用数据类型如果需要转换为number数据类型。可以用valueOf(),或者tostring();转换为字符串,数值,布尔类型,然后再根据规则转换为number数据类型。
如何知道这个变量是什么类型变量?
这里就需要用到:
- typeof();
- instanceof();
typeof() 主要是针对于:Number,String,Boolean,Undefined,和Null类型的函数;
var a=typeof("123") //a="string" *这里要注意虽然是String类型,但返回值却为string*
var a=typeof(true) //a ="boolean"
var a=typeof([1,2,3])//a="object"
如果出现引用类型函数,则只会显示"object"这里算typeof的一个弊端把。这时候
我们就需要用到下面这个函数:instanceof
var a= ([1,2,3] instanceof Array); //a=true;
这里就用instanceof 来判断引用类型的函数 是属于哪一个类型的函数。依据返回值
的结果true or false
操作符
操作运算这里弄几个比较有代表性的总结
“+”运算符
1.在两个操作数都是数字的时候,会做加法运算
2.两个参数都是字符串或在有一个参数是字符串的情况下会把另外一个参数转换为字符串做字符串拼接
3.在参数有对象的情况下会调用其valueOf或toString
4.在只有一个字符串参数的时候会尝试将其转换为数字
5.在只有一个数字参数的时候返回其正数值
变量与作用域
变量
- 基本类型变量——存放在栈内存
- 引用类型变量——存放在堆内存
这里基本类型变量和引用类型变量的区别主要在于:
** 复制变量值的时候**
对于基本类型函数
var a=10;
var b=a;
var a=11;
这时候a=11,但是a还是等于10;
对于基本函数的复制,我感觉更加倾向于是“分裂”,把a的值赋值给b的时候,就相当于一个人一分为二,以后我再发生什么,都和你没关系了 0.0 两者互不影响的
对于引用类型变量
var a=[1,2,3];
var b=a;
a.push(1);
a=[1,2,3,1]
b=[1,2,3,1]
这时候如何a发生变化 比也会随之发生变化。
**对于引用类型的复制,官方的解释是以为引用型变量存储的并不是变量本身,而是一个指针,这个指针指向储存在堆内存中的一个对象,我的理解就是,相当于基本类型变量复制的时候,就是给了一个“指南针”,两个“指南针”给的都是一个“宝藏”的图,如果A发现变化,就相当于"宝藏“发生变化了,B的值理所当然也会发生变化。
** 传递参数的时候**
不多说,直接看例子;
- 对于基本类型变量传递
function exa(num){
//num=a=10;
num+=10;
return num;
}
var a=10;
var b= exa(a);
console.log(a);//10
console.log(b);//20
对于基本类型函数 ,在传递参数的时候,我感觉还是用到了复制,如果给上面一个例子加上num=a=10
感觉就很好理解了,我把这个值复制给你,你要遵守我们基本函数复制的"规矩",——以后出了什么事都和我没关系,所有就很好理解为什么a的值还是10了。
- 对于引用类型变量传递
function exa(arr){
//arr=a={name:"zero"};
arr.name="one";
return arr;
}
var a={name:"zero"};
var b=exa(a);
console.log(a);//Object {name: "one"}
var a={name:"two"};
console.log(a);//Object {name: "two"}
console.log(b);//Object {name: "one"}
**对于引用型的变量传递,相当于开始的时候会有一个arr=a={name:"zero"}这么一个过程,然后在进行操作。规则和引用型变量复制一致,上面例子中,前后A变量不一样,但第二次A变量发生改变后,B的值却没发生改版,主要原因是,第二次A发生改变的时候,我感觉还是一个赋值操作,而不是在原本基础上的一个操作。就相当于从新给了你一个“指南针”,让你去找另外一个地方的“宝藏”。所以B的值不会发生变化.
作用域
函数的作用域相当于一个函数的执行环境。这里提一个作用域链的概念,我感觉作用域链的作用就是一个铁链,连着相关变量和函数,让他们有序的进行。
没有块级作用域
JavaSript 是没有块级作用域的,只有函数作用域。怎么说呢。就好比:只有函数中才存在局部变量,而for,if 条件语句中不存在局部变量。
举个例子:
var a=1;
console.log(a);//1
function arr(a){
a++;
return a;
}
arr(a);
console.log(a);//1
for(var i=0;i<10;i++){
a++;
}
console.log(a);//11
function arr1(a){
var a;
for(var i=0;i<10;i++){
a++;
}
console.log(a);//21
return a;
}
arr1(a);
console.log(a);//11
可以看到在进行arr2函数运算的时候,虽然也有for循环最后得到21并且返回了,但是因为JS存在函数作用域,相当于是一个局部变量,函数内的变量可以访问函数上一级的变量,但函数上一级的并不能访问到他,我的理解就是这里可以使用上一级a=11值,但是外面的全局变量并不能访问到函数内,所以a的值还是11。
声明提升
变量声明前置主要分为:
- 函数的声明提升。
- 变量的声明提升。
函数的声明前置:
函数的声明前置 主要是......先看例子吧。。
一个很经典的例子:
var a = 1;
foo();
function foo() {
if (!a)
{
var a = 2;
}
alert(a);
};
这里开始我的理解就是,直接报错啊。函数都没定义,就运行了。后面自己试着运行下,果不其然运行成功了。还输出2!
如果学了声明前置,这例子就很好理解了。
我们把例子转换下:
var a = 1;
function foo() {
var a;
if (!a)
{
//变量a的声明前置并非在这,而是在最顶部。
a = 2;
}
alert(a);
};
foo();
这样就很好理解了,因为函数foo声明前置,和变量a声明前置,函数变成这幅德行。当运行到foo()的时候,这时候有var a;就不会使用上一级的a元素,而是直接使用这里定义的a元素(有现成的为什么不拿?还要跑上一级去要?的感觉。。。)这里涉及到一个标识符的搜索,这个下面一会说;这时候a的值是undefined;if判断为假,进行赋值操作,a=2.警告框输出a=2;
这里有点要说明的是:
1.声明前置,只针对于变量和函数定义式,而非函数表达式。
2.声明前置,是提升到这个作用域的最顶层。
标识符
先画个草图.....
搜索标识符a
if(局部环境找到了a){
好的,就用这个啦
} else{
再在全局找找,if(全局找到了a){
总算找到了,用这个吧,这时候因为是全局变量a,所以使用后
全局变量a也会同步发生变化,(需要考虑作用域。)
}else{
实在是找不到就只能报错了。
}
}
例子:
var a=1;
function arr(a){
var a=3;
a++;
console.log(a);//4
}
arr(a);
console.log(a);//1
if(a>0){
var a=3;
a++;
console.log(a);//4
}
console.log(a);//4
if(a>0){
a++;
console.log(a);//5
}
console.log(a);//5
function arr1(a){
a++;
console.log(a);//6
return a;
}
arr1(a);
console.log(a);//5