1、闭包
1.1 变量作用域
js中变量的作用域分为两种:(ES6之后新增了块级作用域)
全局作用域:在script标签中直接定义的属性和方法。在全局作用域中声明的变量和函数,都属于window对象。
局部作用域:局部变量属于某个函数。在函数中定义的变量成为局部变量。调用函数时创建函数作用域,函数执行完毕之后,函数作用域销毁,每调用一次函数就会创建一个新的函数作用域,它们之间是相互独立的
1.2 什么是闭包
闭包就是能够读取其他函数内部变量的函数。
正常情况下,我们在函数的外部是无法读取到函数内部的变量。
那我们把这个变量变成全局变量不就解决了吗?但是全局变量就意味着此变量可以被所有函数访问,安全性低。
这个时候就需要用到闭包。
外层方法中定义一个局部变量
外层方法中定义一个内层方法,此方法修改局部变量,并返回局部变量
外层方法将内层方法作为返回值返回
在外部调用外层方法,并接受内层方法对象。
在外部调用内层方法,可以获取或者修改外层方法中的局部变量
1.3 闭包的缺陷
为了实现闭包,我们需要把函数内部的作用域强行保留在内存中。这就是导致了这部分的内存无法回收。大量的闭包会导致内存消耗太多。所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。
1.4 作用域链
JavaScript中,JavaScript里一切都是对象,包括函数。函数对象和其它对象一样,拥有可以通过代码访问的属性和一系列仅供JavaScript引擎访问的内部属性。其中一个内部属性是作用域,包含了函数被创建的作用域中对象的集合,称为函数的作用域链。
作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。
变量取值:到创建 这个变量 的函数的作用域中取值
在作用域链中,只能子调用父,不能父调用子
2、js中的真假判断
不是只有boolean类型才能进行逻辑运算。在js中变量可以转换为布尔值。
通常用来判断变量是否为空
var a = 0;
var b = 6;
console.log(a&&b);
console.log(b&&a);
在js中以下都是假变量:
false
0 数字类型 注意
空字符串
null
undefined
NaN (not a number)
3、js函数中的隐藏参数(Arguments)
(1)如果函数调用时设置了过多的参数,参数将无法被引用,因为无法找到对应的参数名。 只能(2)使用 arguments 对象来调用。
(3)JavaScript 函数有个内置的对象 arguments 对象。
(4)argument 对象包含了函数调用的参数数组。
(4)当我们不确定函数的参数有几个时,我们可以用arguments获取。
(5)arguments 是当前函数的一个内置对象,其存储了传递的所有实参
4、立即执行函数
4.1 特点
1、立即执行函数模式被广泛使用,它可以帮你封装大量的工作而不会在背后遗留任何全局变量。
2、定义的所有变量都会成员立即执行函数的局部变量,所以你不用担心这些临时变量会污染全局空间。
3、这种模式经常被使用在书签工具(bookmarklets)中,因为书签工具在任何页面上运行并且保持全局命名空间干净是非常必要的;
4、这种模式也可以让你将独立的功能封装在自包含模块中。
5、可以将这些代码封装进一个立即执行函数中,并且确保页面没有它的情况下也能正常工作。
6、可以添加更多的加强模块,移除它们,单独测试它们,允许用户去禁用它们等等
4.2 写法
立即执行函数就是在声明一个匿名函数时,立即调用这个函数。格式一共有三种写法:
//第一种格式:
(function(a,b){
alert(a+b);
}(15,23));
//第二种格式
(function(a,b){
alert(a*b)
})(5,12);
//第三种格式(不常用)
+function(a,b){
alert(a*b)
}(9,10)
4.3 与闭包的区别
立即执行函数和闭包没有关系,虽然两者会经常结合在一起使用,但两者有本质的不同:
立即执行函数只是函数的一种调用方式,只是声明完之后立即执行,这类函数一般都只是调用一次(可用于单例对象上),调用完之后会立即销毁,不会占用内存。
闭包则主要是让外部函数可以访问内部函数的作用域,也减少了全局变量的使用,保证了内部变量的安全,但因被引用的内部变量不能被销毁,增大了内存消耗,使用不当易造成内存泄露。
5、变量的提升
通常JS引擎会在正式执行之前先进行一次预编译,在这个过程中,首先将变量声明及函数声明提升至当前作用域的顶端,然后进行接下来的处理。
function foo() {
a=10; // a变量没有声明直接赋值,会产生变量的提升,提升到全局作用域
console.log(a); // 输出10
a=20;
}
foo();
console.log(a) // 输出20
function foo() {
a=10; // a变量没有声明直接赋值,会产生变量的提升,但是由于本作用域中出现了声明代码,所以变量提升到本作用域顶端
console.log(a); // 输出10
var a=20;
}
foo();
console.log(a) // 报错
let 会阻止变量的提升
function foo() {
a=10; // 报错。 没有变量的提升
console.log(a);
let a=20; // 使用let声明变量,最阻止变量的提升
}
foo();
console.log(a)
6、函数的提升
js在预编译阶段,会把函数的声明,提前到代码的前端。
函数的声明和函数的调用是两回事。函数的声明会提升,但是函数的调用是顺序执行的。
函数的作用域,只有在函数执行时才会产生,运行完毕后销毁。
函数的声明有两种:
(1)函数式声明。会有函数的提升
(2)字面量声明。不会有函数的提升
7、递归
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。简单理解:函数内部自己调用自己, 这个函数就是递归函数。
function fn(){
fn();
}
fn();
8、原型
首先,JavaScript 是基于原型继承(Prototypal inheritance)的语言。原型(prototype)是给其他对象提供共享属性的对象,每个函数都有一个 prototype 属性,它指向的是一个 prototype 对象。每个对象都有一个隐式引用([[Prototype]]),并且 [[Prototype]] 指向它的原型对象,并从中继承数据、结构和行为。同时原型对象同样拥有原型(函数也是对象,它也有[[Prototype]]),这样一层一层,最终指向 null,这种关系被称为原型链
无论何时,只要创建一个函数,就会按照特定的规则为这个函数创建一个 prototype 属性(指向原型对象)。默认情况下,所有原型对象自动获得一个名为 constructor 的属性,指回与之关联的构造函数
function Foo() {}
Foo.prototype.name = 'johan';
console.dir(Foo);
console.dir(Foo.prototype);
9、数组
数组(array)是按次序排列的一组值。JS其实没有真正的数组,只是用对象模拟数组。本质上,数组属于一种特殊的对象。typeof运算符会返回数组的类型是object。
JavaScript 语言规定,对象的键名一律为字符串,所以,数组的键名其实也是字符串。之所以可以用数值读取,是因为非字符串的键名会被转为字符串。
9.1 创建数组
let arr =[1,2,3]
let arr = new Array(1,2,3)
let arr= new Array(3)//这里3指的是数组的长度
9.2 基本方法
(1)删
let arr = ['a' , 'b' , 'c']
delete arr[ '0']
arr//[empty, 'b','c']
(2)查
arr.indexOf(item)//存在返回索引,否则返回-1
arr.find(item =>item % 2===0)//找第一个偶数
arr.findIndex(item => item % 2===0)//找第一个偶数的索引
(3)改
arr.forEach(function(item,index){
console.log(`${}index}:${item}`)
})
(4)添加
在尾部加元素
arr.push(newltem)//修改arr返回新长度
arr.push(item1, item2)//修改arr,返回新长度
在头部加元素
arr.unshift(newltem)//修改arr,返回新长度
arr.unshift(item1, item2)//修改arr,返回新长度
在中间添加元素
arr.splice(index,0, 'x')//在index处插入'x'
arr.splice(index,0, 'x', 'y')
10、判断数据类型
typeof:此方法直接返回原型链上的最后一个对象。
instanceof: 此操作符会查找原型链上是否存在这个构造函数。存在返回true。
11、事件
11.1 绑定事件
绑定事件有三种方式:
在html中添加事件属性。html和js代码耦合在一起,不利于维护。不推荐。
<body>
<ul id="list" οnclick="func()">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</body>
<script>
function func(){
console.log('触发了')
}
</script>
在js中调用事件属性。会产生覆盖,只能写一次。本质上和上面一样。
<body>
<ul id="list">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</body>
<script>
var listDom = document.getElementById('list');
listDom.onclick = function(){
console.log('触发了1')
};
listDom.onclick = function(){
console.log('触发了2')
};
</script>
在js中添加事件监听器。不会覆盖,可以绑定多个。
<body>
<ul id="list">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</body>
<script>
var listDom = document.getElementById('list');
listDom.onclick = function(){
console.log('触发了1')
};
listDom.onclick = function(){
console.log('触发了2')
};
listDom.addEventListener('click',function(){
console.log('触发了3');
});
listDom.addEventListener('click',function(){
console.log('触发了4');
});
var func5 = function(){
console.log('触发了5');
}
listDom.addEventListener('click',func5); // 添加事件
listDom.removeEventListener('click',func5); // 移除指定方法
</script>
11.2 事件传播机制
事件有两种传播机制:
冒泡 。先触发子元素事件,再触发父元素事件
捕获。 先触发父元素事件,再触发子元素事件
<body>
<ul id="parent">
<li id="chlid1">1</li>
<li>2</li>
<li>3</li>
</ul>
</body>
<script>
var parentDom = document.getElementById('parent');
parentDom.addEventListener('click',function(){
console.log('parent');
},true) // 第三个参数控制事件传播机制,默认false冒泡,true捕获
var child1Dom = document.getElementById('chlid1');
child1Dom.addEventListener('click',function(){
console.log('chlid1');
})
</script>
11.3 阻止冒泡
var parentDom = document.getElementById('parent');
parentDom.addEventListener('click',function(){
console.log('parent');
})
var child1Dom = document.getElementById('chlid1');
child1Dom.addEventListener('click',function(event){
console.log('chlid1');
event.stopPropagation(); // event为事件对象,调用stopPropagation()阻止冒泡
})