最初名称livescript
,真名ecmascript
,目前称作JavaScript
JavaScript
是一种脚本语言,是一种解释型、动态类型、弱类型、基于原型的语言,内置支持类型;
Javascript
的变量没有类型约束(没有类型系统);
它只能通过web浏览器进行解释和执行才能去完成某种操作,而不能独立运行。
javascript
编码规范:ECMAScript
javascript
是面向函数式编程,所有的编程都面向于函数
javaScript
产生的原因:
在JavaScript
出现之前,web浏览器只是一个能够显示超文本文档的软件的基础部分;JavaScript
的出现是为了实现用户与客户端的交互;
用户与客户端的交互:客户端给用户提供功能,用户通过某些操作说明用户的要求,客户端对这些操作做出的反馈;
JS
为了提高用户体验而产生。<body>
<script type="text/javascript">
var x="hellow"; 声明 赋值
console.log(x);
script>
body>
JavaScript
是非纯函数式的编程语言(的原因)
变量必须使用 var
关键字定义。
变量的目的:用于存储东西,赋予的值可以改变。
变量标识符(变量名):必须由字符、下划线、$符、_符为开头,不可以用数字开头,同时不能使用关键字和保留字;
html
不区分大小写,但 Javascript
严格区分;如何生成变量:声明一个变量,并且赋值;
创建变量的过程叫声明;
如何声明:var x;
声明语句
声明分两种:
赋值是指将某一数值赋给某个变量的过程;
赋值语句是指将确定的数值赋给变量的语句。
语法糖:声明与赋值写在一起
变量的声明与定义实际上是两个不同的步骤,但我们可以用语法糖完成声明与定义的一次性操作;
若一个变量只有声明而没有定义,则该变量的值为 undefined
var s = 9;
声明提前
变量(包括函数)具有声明提前的特性,把所有变量的声明部分都被放到该变量所在的作用域的顶端。声明提前的过程是在预解析过程中。
声明提前执行的过程:执行上下文赞,也叫预解析。
预解析:在代码运行之前,会调整一次代码,但不执行代码,在这个过程中,就会把变量声明部分提前,但不提前赋值部分。
console
后,则输出值为 undefined
console.log(any); // undefined
var any = "hello world!";
const
常量常量使用 const
关键字进行定义
const
实现早于规范,在 es6(es2015)
版本当中才被定义为标准,所以依然有很强的兼容性问题,IE11
以下不支持。
声明的同时必须赋值定义 const s = 9;
,即声明与定义是一个整体
常量的标识符建议使用全部大写形式,必须由字符、下划线、$符、_符为开头,不可以用数字开头
常量不可重新赋值:常量的特点是值一旦定义不可改变,常量一旦被改变将会报错并终止代码运行;这也是常量的声明与定义必须同时完成的原因。
常量不具备变量的声明提前的特性
原因:假设常量可以拆分声明与赋值,那么常量在声明的时候未被赋值,实际上它被赋予了特殊的undefined
值,当我们后续赋值时,就必须要去改变 undefined
,这种该行为实际上已经在改变常量的值了,与常量的概念冲突。
常量所存储的地址指向一旦发生变化将会触发类型错误
const one = 45;
one = 34; 改变了地址(不是引用地址,是指向45的地址),报错
const two = [];
two[0] = 45; 数组地址没有改变
数值型 number
字符串 string
布尔值 boolean
,true
,false
,“.toString
”
未定义 undefined
,没有被定义的量都默认定义为undefined
本质是变量,在现阶段浏览器中该值无法更改,不能做任何对象操作(没有任何属性,也没有基本包装转换(无构造函数),不能进行点操作,没有原型对象,null一样)
空指针 null
对象 object,它也是语法糖
注意:在整个 JavaScript
中只有 null
和 undefined
不能进行点操作
基本数据类型分两种:值类型(直接量)、引用类型
JS
一切皆对象,在 Object
类型的基础上又构建了一些其他的引用类型:
Function.prototype
Array.prototype
Date.prototype
Arguments.prototype
是类数组对象(无法构造,自动生成),并不能找到它的构造函数的原型,它的原型非常特殊被系统隐藏,它的构造函数就是 Arguments
而不是对象,它是独立特殊的一类HTMLCollection
节点集合,也是一种构造函数JavaScript
支持三种范式面向对象、面向过程、函数
NaN
:当 number 类型与另一个类型一起操作时,无法进行任何类型转换,所以无法拿到结果值,即想要返回一个 number,但是最终拿不到结果
操作数与操作符
操作符,在 JavaScript 中操作表达式并返回结果的符号或者字符
操作符是对数据进行操作的符号,操作符操作完数据后一定会产生结果
数值操作符 +、-、*、/
比较操作符 >、<、>=、<=、、=、!=、-> 都是布尔值
== 与 === 的区别
不同的数据类型无法进行值的比较
双等操作符在进行数据比较时,会进行隐式类型转换(看不见转换过程,向string / number方向转换)
全等操作符,又称恒等操作符,首先判断类型,只要类型不同就返回false,没有隐式类型转换
()
用来决定操作范围
四则运算符号计算的结果为数值类型
var num = 88 + 1 ;
console.log(num);
>比较操作符,比较运算符计算结果为布尔值
console.log( 88 > 99);
比较操作符
|| 或 &&与 !非
单目操作符,又称逻辑操作符
在 JavaScript 中比较操作符或、与返回值是表达式的值,而不是 boolean;非返回布尔值
function f1(){
console.log("call f1")
return 0;
}
function f2(){
console.log("call f2")
return 1;
}
console.log(f1() || f2()) f1返回0,f2返回3,则执行f1和f2,返回值为3
console.log(f2() || f1()) f2返回3,则执行f2,短路现象,返回值为0
console.log(f1() && f2()) f1返回0,则执行f1,返回值为0
console.log(f2() && f1()) f2返回3,f1返回0,则执行f1,f2,返回值为0
特殊情况(当有一个值为0时):
if(f1() && f2()){ f1返回0,则不执行if内部语句
console.log("++++");
}
if(f1() || f2()){ f1返回0,f2返回3,则执行if内部语句
console.log("++++");
}
注意:或、与操作符若发生短路,则返回 f1
;若没有短路则返回 f2
typeof
是一目操作符,for-in
中 in 是二目操作符
有多少个操作数,就是多少目操作符
只能返回基本数据类型
书写形式:
console.log(typeof 变量标识符);
typeof
操作符来得到一个值的所属类型,typeof
会得到以下的结果:操作 | 结果 |
---|---|
typeof 55 / typeof (34) |
number |
typeof "hello" |
string |
typeof true |
boolean |
typeof undefined |
undefined |
typeof null |
object |
typeof {info: "info"} |
object |
typeof [1, 2, 3, 4] |
object |
typeof function(){} |
function |
typeof arguments |
Object |
以上结果 与 **”函数“**存在特殊性:
JS
中的重要性,因此 最终会返回一个描述来提高重视。函数在 JS
中又被成为**”一等公民“**,虽然函数本质是由对象构建,但是由于它在该语言中非常的重要,因此认为函数与对象是同等地位(与非纯函数式的编程语言相关)。引用类型:
数组本质上也是对象;数组、、标准统称为引用类型;引用类型属于浅拷贝。
不是函数 console.log ( typeof (34) );
console.log(typeof (34));
中34的括号,不是函数,是为了声明范围改变优先级
三目运算符 ? :
express1?express2:express3
解析:若express1
为真(非零),则执行express2
,否则执行express3
用null或undefined,undefined表示该处未定义表达式
(index > 10)? (index = 0): undefined;
(index < 10) || (index = 0) 表示若index>10就执行index = 0 赋值表达式
i++
与 i = i+1
的区别
console.log(i = 5)
i = 5是赋值表达式,console返回的值是表达式的值
i++ 先取值后加一
++i 先加一后取值
ES6中
i = i ** 3 表示 i 的 3 次方
JavaScript
中的对象是一种简单的hash
哈希结构,是一种映射结构,也就是我们常说的键:值对(key: value)
结构,对象这种数据准确来讲是一种数据集合而不是一个单一的数据。key是指变量标识符,可以修改
在哈希结构中key( String
类型)所对应的 value
可以是任意类型,key在 JavaScript 中实际上是字符串类型
存放的是数据集合
引用类型在比较时,只能比较地址,因为对象是动态类型,无法进行值比较,而值类型比较时,是比较值
采用对象字面量(语法糖)方法创建对象:
var obj={
// key: value, 键:值 / 值对
name: "ruby",
it: {},
"hair-color": "black", //内部有特殊字符,必须用引号
hariColor: "black", //小驼峰命名法
HariColor : "black", //大驼峰命名法
foo: function (val){
......
}
......
}
delete obj.haircolor; delete 删除into键,只能用于对象属性
console.log(obj[haircolor]); 报错,key是字符串类型,必须有引号
console.log(obj["haircolor"]); 根据语法糖,可以简写成以下形式,若键名中有特殊字符,必须使用[""]来表示
console.log(obj.hairColor); //‘.’点操作是获取值
对象只能用 for-in
遍历
for-in
,下标(键)采用 index
表示for-in
来遍历var o={
one: "11",
two: "22",
three: "33"
}
或
var o=new object();
o.one = "11";
o.two = "22";
o.three = "33";
解析:在o结构中我们要依次遍历key变量
for(var key in o){
console.log(key);
}
结果值: one two three
for(var key in o){
console.log(o[key]);
}
结果值:11 22 33
in 用来判断一个是否存在于对象当中,返回boolean
console.log(下标 in 变量标识符); 有问题
console.log ("key" in 变量标识符);
数组也是一种集合,一组数组中的数值可以是任意类型的数值,用逗号分隔,用中括号包裹。在javascript
数组是对象中的一种特殊形式。
empty
var arr=[1, " ", "", 2, true, "!"];
console.log(arr[4]); 打印下标为4的元素
var arr = ["I", " ", "L", "o", "v", "e", " ", "Y", "o", "u", "!"];
for(var index in arr){
console.log(arr[index]);
}
结果: I Love You!,且每个字符自动换行
数组还可以采用forEach
函数来遍历
数组添加元素的方式:
var arr = []; arr[0] = 1;
现阶段创建数组有两种方法:
数组字面量,本质是语法糖,通过中括号直接定义数组
var arr = [1, 2, 3];
要通过构造函数构造出新的数组
var arr = new Array(element, element, .... element);
var arr = new Array(length);
Array构造函数会因为传入的参数不同而表现出功能的不一致性
给定参数:
当传入多个参数或传入一个非类型的参数,则这些参数将会成为新数组的元素;
若传入的参数个数为1,且为类型,则该参数表示被构造数组的长度,该值必须为无符号整数,否则会报错;
若参数的个数为1,且是类型,则返回该数组是有一个元素的数组,且该元素为类型;
数组可以是一维,二维,多维数组
var arr = [1, 2, 3]; //一维
var arr = [1, 2, [1, 2]]; //二维
var arr = [1, 2, [1, 2, [3, 4, 5]]]; //三维
function函数定义:为了把某些代码或者某些逻辑,封装在一个结构当中,当需要执行该代码或逻辑时,就可以调用该函数。
语法:函数的本质就是对象
除最后一个函数体以外,前面两个都称为参数名,必须以字符串的形式创建
var f = new Function("arg1", "arg2", "congsole.log(arg1 + arg2);");
f("h","o");
但是更建议使用语法糖
function foo(arg1, arg2){
console.log(arg1 + arg2)
}
f("h","o");
对于javascript
来说,输入和输出都是可选的。
foo
为函数名,函数标识符,命名方式与变量相同
声明:
必须用 function 关键字声明
如果要使用函数,必须要经过声明,且同变量的声明一样可以声明提前(但函数表达式不具备声明提前特性);一次性声明,次次调用
该语句称为函数声明语句,也称形参列表,用来接收实参的赋值,并封装代码或逻辑
funciont foo (a, b, c,....){ }
调用:
函数在调用的时候可以进行输入,即函数的实参,调用时投入的实参需要形参进行接收;函数的输出就是其返回值,在编程语言中输入输出都是可选的
若不调用,则不执行
该语句称为实参列表
foo(1, 2, 3,...);
例如:
function add(x, y){ //x,y用于接收数据,功能与变量相同
console.log(x + y);
}
add(44, 88); //等同于赋值,x=44,y=88
结果值:132
活动对象 AO
AO
活动对象,它是执行上下文赞中的一个过程,用来存储当前作用域中可用的数据,当我们需要使用时就会向活动对象中查找。arguments: [形参]
,函数内的可用函数的声明,函数内的变量arguments
参数中找,否则再向上一层作用域 AO
中查找。javascript
function fn(a) {
console.log(a);
var a = 2;
function a() {}
console.log(a);
}
fn(1);
运行结果:
ƒ a() { }
2执行步骤:
- 调用
fn()
将数值 1 传送给fn(a)
函数中的 a ;- 所有变量都具有声明提前的特性,因此
var a
和function a
的声明被系统放在了fn
作用域的最前端,但因为函数声明提前比变量更早一步,所以var a无效,但它的值还在,所以第一句console.log()
打印的是f a(){}
;- 执行完第一句
console.log()
后,对变量 a 进行了赋值,所以第二句console.log()
打印的是变量 a 的值。
在javascript
中实参与形参的个数可以不对称
现象:
undefined
函数分有参 / 无参函数、有返回值 / 无返回值函数
标识符.length:表示形参的个数
arguments对象,
该对象的结构符合类数组对象,每一个函数体内部都有一个天生的arguments对象,继承于 Object
类数组对象:key
从0 起步依次递增,步长为 1;具有正确的 length
属性
注意:只能在函数内使用,只有在调用时才有用,不同函数之间的arguments互不干扰,它的key无含义
function foo(){
console.log(arguments) //Arguments{...}
console.log(arguments.__proto__ === Object.prototype) //true
}
foo();
undefined
;function add(a,b){
console.log(arr.length); 返回形参的个数
return a + b;
}
//等同于,a=33; b=88; add(33,88)=a+b;也就是result = a+b
var result = add(33, 88);
console.log(result);
结果值:2 121
var result1 = add;
标识符表达式,返回存储的引用地址(返回内部存储的内容)
定义:表示某个量所起作用的区域,也可以认为是一个量的生命区域
重点:
javascript
中只有函数才能创建作用域注意:本质上只有对象才能创建作用域,不过函数作用域与对象作用域有些不同
对象作用域是用来限制成员变量的
函数作用域是用来限制普通变量的
函数:就是函数
方法:若函数是一个对象(成员变量)的值,那么就称该函数为方法;
在 JavaScript
中始终会产生一个 Global
作用域在所有作用域的最顶层
作用域分两种:
JS
使用的是静态作用域量的查找:若某作用域要使用某个量,那么优先在本作用域中查找该量,若没有,则向上层中查找,若还没有,则向更上一层作用域查找,直到找到Global
为止。
若最终在 Global
也没有找到该量,则会报错。该查找过程只遵循冒泡 (Bubble
) 原则。
函数形参的作用域就是该函数创建的局部作用域。
var a = 33;
function f3(a){
f3中的a,是局部作用域的形参a,是全局作用域中var a的值,而不是地址,所以并不会影响全局变量a的地址和值
console.log(a + 33);
}
f3(a); //输出的是f3中a的值
console.log(a); //输出的是var a的值
结果值: 66 33
作用域链
定义:根据作用域查找关系,会形成一个查找链条,而这个链条就是作用域链
闭包
定义:函数的一种状态。
作用:使用封闭的作用域可以获得外部自由变量的使用权,**“共享”**外部变量数据。
查找方式:闭包是沿着作用域链来查找自由变量的,即从该封闭作用域开始向链条的末端开始查找,作用域链的顶端是最深层的局部作用域,末端是Global
全局作用域。
f3() -> f2() -> f1() -> Global
若把函数当成一个值来操作,那么函数就会变成函数声明表达式,这样的表达式也分为两种:
匿名函数声明表达式:没有标识符 -> 立即执行函数
具名函数声明表达式:有标识符,
注意:函数声明语句可以整体提前,只有声明语句提前,函数声明表达式不提前
var count = 0;
var foo = function bar(){ //匿名函数声明表达式
console.log("--++");
if(count < 5){
count = count + 1;
bar();
}
}
foo();
结果值:循环5次 --++
立即执行函数(匿名),使用立即执行函数时,上一条语句必须要有分号结尾
function (){ 系统将它当成了函数声明语句,所以会报错
console.log("---")
}()
+function (){ +是操作符,两侧必须是表达式,但不标准
console.log("---")
}()
;
(function (){
console.log("---")
})()
回调函数:它不由我们手动调用,而是交给某些接口,当某些条件发生时,由接口调用的函数我们称之为回调函数Callback,它并不是指某个函数,而是同一类型
Array.prototype.mysome = function (callback){//callback在这里是形参
for(i = 0; i < this.length; i++){
if(callback(this[i],i,this)){
return true;
}
}
return false;//若没有任何元素满足条件,则返回false
}
var arr = [1,2,3,4,5];
var arr1 =arr.mysome(function(value){//value在这里是形参
return value > 3; //return 返回实参
//返回给callback,callback接收实参
})//function(value)在这里是mysome的实参,传送给callback
console.log(arr1);
解析:
1、function(value)中的value是形参,接收return value>6的返回值(实参)
2、function(value)是mysome的实参,返回值传送给function(callback)中的callback形参,若为真则执行if,若为假则不执行
3、function(value)是callback的声明,callback(this[i],i,this)中this[i],i,this都是实参,传送给value
4、callback接收实参,调用mysome时,for循环语句中的if调用callback(元素,下标,目标数组),for(){ if( this[i]>3 ),return true; }
callback(元素,下标,目标数组)用来做测试函数处理
运行过程:
1、当i = 0, if(callback(1, 0, arr)){ return true}中将实参传送给value
value = 1, return 1 > 3,返回false,所以不运行if,i = 2
4、当 i = 3时,callback(4, 3, arr),value = 4, return 4>3,返回true,执行if语句,return返回true,最终结果为true
若 return value > 6,当i = 4时,callback(5, 4, arr),value = 5,return 5>6返回false,不执行if,i = 6,i>this.length,for循环结束,返回false
function F(){
this.data = "some data" //this.data表示在F函数中的变量
}
F.isF = function(o){ //function在这里是对象、方法、值
return o instanceof this; //this指向F
}
var f1 = F(); //返回undefined,F在这里是函数
var f2 = new F(); //F在这里是构造函数,返回some data
console.log(F.isF(f1)); //false,F在这里是对象
console.log(F.isF(f2)); //true
内存泄漏:当一个对象已经不需要再使用本该被回收时,另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏。
个人解析:当一个对象使用完毕后本该释放空间时,但它依然占据着空间(存放在堆内存中),导致了内存泄漏。
JavaScript
无需手动释放内存,有自动垃圾回收机制。JavaScript
是单线程执行环境:代码是一行一行执行,不可以跳过
但在单线程执行环境中很容易造成阻塞现象,该现象可以用异步操作来解决
用异步操作的场景(只要与时间有关):1、耗时长;2、执行时间不确定
例1:
for(var i = 0; i < 5; i++){
var delay = 1000 * i; //同步代码
setTimeout(function(){
console.log(i,new Date);
}, delay);
}
console.log("----")
代码解析:
1、for循环,congsole.log("----")都主线程代码;创建定时器,本身不是异步操作,内部的回调函数function()才是异步操作。
2、for循环并不会等待function执行完毕后再进行i++,它会优先执行完自身的非异步操作(包括var delay = 1000 * i),congsole.log("----")同理
3、在执行for循环时,function()会被踢进“任务队列”中,每当监听时间到了代码就会回归;但因为function()作用域中没有i,所以根据闭包规则在外部查找,最终在全局作用域中找到i,此时的
i = 5(for循环已经执行完毕)
4、function()会执行5次,声明5次,它的执行时间是可见的,执行时间在非异步代码中,所以每次都返回不同的时间
5、congsole.log("----")当function()执行之前,该语句已经执行完毕
运行过程:
var i = 0; delay = 0S
i = 1; delay = 1S
i = 2; delay = 2S
i = 3; delay = 3S
i = 4; delay = 4S
i =5;
执行 console.log("----");
function(){ i = 5; new Date } delay = 0S
function(){ i = 5; new Date } delay = 1S
.....
解决方法(i 的值):
代码解析:function(t)
是立即执行函数,也是主线程代码,i是实参,t接收实参
setTiemout
依赖于外部的 foo
函数,所以 foo
函数不会被释放掉; setTimeout
执行完毕后,就会释放掉它的父级 foo
函数
foo
会被创建 5 次,但因为 JS
中标识名唯一性,会造成后面执行的 foo
函数的返回值会覆盖掉前面的返回值,所以 5 次用的都是同一个函数名(即覆盖)。
若没有 t 进行接收实参,则 setTimeout
还是会找到for 循环中的 i ,此时的 i 已经执行完毕值为 5 ,
for(var i = 0; i < 5; i++){
(function foo(t){
setTimeout(function(){
console.log(t, new Date);
}, 1000 * t);
})(i)
}
console.log(i)
for (var i = 0; i < 5; i++) {
setTimeout(function (t) {
console.log(t);
}, 1000 * i, i); //将函数的实参放在setTimeout的第三个参数上,用 t 来接收
}
例2:
(function f(){
function f(){return 1;}
return f();
function f(){return 2;}
})(); //2
解析:第二个 f 函数声明语句覆盖了第一个
深浅拷贝的由来:由于 JS
对不同的数据类型分配了不同的内存空间,导致出现了深浅拷贝现象
栈先进后出,只存储静态数据,值类型都存放在栈当中;
堆先进先出,只存储动态数据,对象(又称引用类型)存放在堆当中,非连贯性存储,堆空间中一班存放的都是集合类型(动态类型),集合类型的内部数据结构具有可变性
javascript
中规定:值类型拷贝的是值,而不是地址,所以值拷贝并不会影响被拷贝元素的地址;
但集合类(对象),拷贝时执行的是引用地址拷贝,而不是值;
其次要注意变量的值都存储在栈当中,而变量里面存储的都是指向值的地址。
深拷贝:值的拷贝,分配栈空间
浅拷贝:引用类型的拷贝,具有可变性,例如
在进行数据传递(当数据与标识符进行关联)时,若数据为值类型,那么将会值拷贝一份,然后将拷贝出来的值的地址
若数据为引用类型,那么将会把标识符里面存储的引用地址拷贝一份并赋给新的标识符
var a = 5;
var b = a;
b = 88;
console.log(a);
结果值:5
在这里b拷贝的是a的值,而不是地址
var ol{
num = 66
}
var om = ol;
om.num = 99;
console.log(ol.num);
结果值:99
原因:om引用了ol的地址,所以当om中num值发生改变时,ol也会改变