一、全局作用域
在函数外部定义的变量或函数,叫全局变量或全局函数。它们可以在当前程序的任意位置使用。在全局中定义变量可以用var,也可以直接添加window的属性。
生命周期:它们会一直占用内存,只能在当前文件中使用。如果想在多个文件中使用变量,需要用到cookie或本地存储。
例1 测试全局作用域
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
var x = 100
function fn() {
x += 10
console.log(x);//1010
}
function fn1() {
x *= 10
fn()
console.log(x);//1010
}
fn1()
console.log(x);//1010
</script>
</html>
二、局部作用域
在函数内部定义的变量或函数,叫局部变量或局部函数。它们只能在函数内部使用。在函数内部用var定义局部变量,如果用window定义的变量,则为全局变量。
生命周期:从调用时开始创建,到函数执行完后立即销毁。
例2 测试局部作用域
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
function fn() {
var x = 'hello'
window.y = 'world'
function show(str) {
return str + ',hello'
}
console.log(show('tom'));//tom,hello
}
fn()
//console.log(x);//报错
//console.log(show('tom'));//报错
console.log(y);// 如果在函数内部用window定义变量,该变量为全局变量
</script>
</html>
三、块级作用域
在ES5中没有块级作用域,ES6中有。一对{ }为一个块,块级作用域就是变量或函数只在当前这组{}中有效。
在ES5中可以用IIFE实现块级作用域,ES6中用let实现。
四、IIFE
1、什么是IIFE?
IIFE(Immediately Invokeed Function Expression:立即执行函数表达式),声明函数的同时立即执行调用这个函数。定义的函数没有函数名,只能执行一次,无法在其它地方被调用,也不会造成全局污染。
2、IIFE的作用
可以用于解决运算中产生的全局污染问题(实际上它是一个闭包的写法)。它也是ES5中实现块级作用域的方式。
3、IIFE和特点
(1)将所有运算代码都放在匿名函数中;
(2)函数是一个表达式,没有函数名,只能被执行一次;
(3)执行一次后所产生的数据和变量就立即销毁,不会造成全局污染。
4、IIFE语法
(function(params){
...
})(exprs)
将函数的定义和调用写在一起。
5、IIFE的用法(推荐用前面两种写法)
(function(形参列表){
...
window.变量 = 表达式;
})(实参列表);
例3 测试第一种写法 计算1+2+3+...+100
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
(function () {
for (var i = 0, sum = 0; i <= 100; i++) {
sum += i
}
window.rs=sum
})()
console.log(rs);
</script>
</html>
var 变量名 = (function(形参列表){
...
return 表达式;
})(实参列表);
例4 测试第二种写法 计算1+2+3+...+100
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
var rs = (function () {
for (var i = 0, sum = 0; i <= 100; i++) {
sum += i
}
return sum
})()
console.log(rs);
</script>
</html>
!(function(形参列表){
...
return 表达式;
})(实参列表);
+(function(形参列表){
...
return 表达式;
})(实参列表);
五、闭包
1、什么是闭包?
闭包就是一种作用域的体现,函数外部可以访问函数内部的数据。正常情况下,函数外部不能访问函数内部的变量,但是通过这种特殊的写法,将函数内的子函数暴露在全局上,可以在外部调用且可以访问到函数内部的数据,即可以让全局访问局部的数据。
简单地说:就是在函数外部获取函数内部的数据。
2、闭包的作用
(1)实现函数外部可以访问函数内部的数据;
(2)减少了全局变量的使用,避免全局变量的污染。
3、闭包的特点
(1)闭包一定是函数内嵌套函数;
(2)闭包是一种作用域的体现,函数外可以访问函数内的数据;
(3)闭包采用IIFE写法,由于内部数据被全局所调用,将延缓资源的回收(即闭包中的变量的值用完后不会立即销毁,会驻留内存一段时间)。
4、闭包的缺点
(1)闭包中的变量会占用更多的内存空间;
(2)可能会导致内存泄露。
5、闭包的写法
第1种写法:
(function(){
...
window.函数名 = function(){
...
return 变量;
}
})()
例5 测试第一种写法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
(function(){
var i=10;
window.result=function(){
++i
return i
}
})()
console.log(result());//11
console.log(result());//12
</script>
</html>
第2种写法:
var 变量 = (function(){
...
return function(){
...
return 变量;
}
})()
例6 测试第二种写法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
var rs = (function () {
var i = 10;
return function () {
++i;
return i;
}
})()
console.log(rs());//11
console.log(rs());//12
console.log(rs());//13
</script>
</html>
六、内存管理
在闭包中调用局部变量,会导致这个局部变量无法及时被销毁,相当于全局变量一样会一直占用着内存。如果需要回收这些变量占用的内存,可以手动将变量设置为null。然而在使用闭包的过程中,比较容易形成 JavaScript 对象和 DOM 对象的循环引用,就有可能造成内存泄露。这是因为浏览器的垃圾回收机制中,如果两个对象之间形成了循环引用,那么它们都无法被回收。
解决方案:
(1)把循环引用中的变量设为 null 即可;
(2)把闭包写法改造成一个引用外部函数写法。
例7 测试内存管理
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="box">box</div>
</body>
<script>
(function () {
var box = document.getElementsByClassName('box')[0]
box.onclick = txt
box = null
})()
function txt() {
console.log(document.getElementsByClassName('box')[0].innerHTML)
}
</script>
</html>
七、JavaScript对象
对象分为内置对象(构造函数/方法)和用户自定义对象。
1、内置对象:String Date Math RegExp Function Object Array Boolean Number...
var str1 = new String('abc');// 类型为object
var str2 = 'abc' // 类型为string
2、用户自定义对象
(1)字面量
var obj1 = {
name: 'aaa',
sex: 'male'
}
(2)用new构建
var obj2 = new Object({
name: 'aaa',
sex: 'male'
})
(3)构造函数(相当于ES6中类)
a.创建方法
function 构造函数名/类名(形参列表){
// 类的特征-属性
this.属性名 = 参数;
...
// 类的形为-方法
this.方法名 = function(){
...
}
...
}
b.使用构造函数实例化对象
var 对象名 = new 构造函数名/类名(实参列表)
c.构造函数特征
i)构造函数名(类名)首字母要大写(W3C的规定)(要使用大驼峰写法);
补充:
小驼峰写法:studentClassicScore
大驼峰写法:StudentClassicScore
ii)属性和方法都要挂载到this上;
iii)没有return语句。
d.实例化机制
i)创建一个对象;
ii)将构造函数中的this指向新对象;
iii)执行构造函数中的语句,将所有的属性和方法挂载到新的对象上;
iv)把新的对象的内存地址赋值给变量。
e.问题
如果构造函数实例化的对象中的属性和方法的内容一样,因为它们会各自占用独立的空间,会造成内存的浪费。
例8 构造函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
//创建构造函数
function Person(name, sex, age) {
this.name = name
this.sex = sex
this.age = age
this.classic = 'student'
this.show = function () {
console.log('message:' + this.name + ',' + this.sex + ',' + this.age);
}
this.play = function () {
console.log('play()');
}
}
//实例化对象
var zs = new Person('zs', 'man', 19)
console.log(zs);
zs.show()
zs.play()
var ls = new Person('ls', 'woman', 19)
console.log(ls);
ls.show()
ls.play()
</script>
</html>
4、原型
a.什么上原型?
原型就是prototype,所有function定义的函数都拥有这个属性。
prototype这个属性是一个对象,也是一个指针,是用来添加所有实例共享属性和方法的。
b.原型的作用
i)解决方法过载(为每个对象都单独创建一个方法,浪费了大量的内存资源,这种情况叫方法过载);
ii)扩展构造函数的属性和方法(功能)。
c.原型的写法
i)扩展属性
构造函数名.prototype.属性名 = 表达式;
ii)扩展方法
构造函数名.prototype.方法名 = function(){
...
}
注意:在开发过程中,私有属性和方法放在构造函数中,而共享属性和方法用原型添加。
例9 原型
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
// 创建构造函数
function Person(name, sex, age) {
this.name = name;
this.sex = sex;
this.age = age;
this.show = function () {
console.log('您的信息为:' + this.name + ',' + this.sex + ',' + this.age)
}
}
Person.prototype.classic = { type: 'student' } // 为Person构造函数添加了一个共享属性classic
Person.prototype.play = function () { // 为Person构造函数添加了一个共享方法play()
console.log('喜欢打篮球!')
}
//实例化对象
var zs=new Person('zs','man',20)
console.log(zs);
zs.play()
var ls=new Person('ls','woman',20)
console.log(ls);
ls.play()
console.log(zs.classic === ls.classic);//true
console.log(zs.play === ls.play);//true 说明play()是共用了一段内存空间
</script>
</html>
5、混合模式
混合模式 = 构造函数 + 原型
在构造函数中定义私有的属性和方法,通过原型添加共享的属性和方法。
例10 混合模式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
//混合模式
function CreatePerson(name, age) {
this.name = name
this.age = age
}
CreatePerson.prototype.showName = function () {
return ('name is:' + this.name)
}
CreatePerson.prototype.showAge = function () {
return ('age is:' + this.age)
}
var per1 = new CreatePerson('zs', 20)
var per2 = new CreatePerson('ls', 20)
console.log(per1.showName());
console.log(per1.showAge());
console.log(per2.showName());
console.log(per2.showAge());
</script>
</html>
6、基本模式(JSON语法格式)
var 对象 = {
属性:属性值,
...,
方法:function(){...},
...
}
这样做的目的是为了减少全局变量和全局方法的使用,从而避免全局污染。
例11 基本模式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="box">id</div>
<div class="box">class</div>
<p>pppppp</p>
<p>pppppp</p>
<p>pppppp</p>
</body>
<script>
//封装获取DOM
var $ = {
id: function (id) {
return document.getElementById(id)
},
cls: function (className) {
return document.getElementsByClassName(className)
},
tag: function (tagName) {
return document.getElementsByTagName(tagName)
}
}
$.id('box').innerHTML = 'this is id'
$.cls('box')[0].style.color = 'red'
$.tag('p')[1].style.cssText = 'color:blue;border:1px solid #eee'
</script>
</html>
7、工厂模式
一般用于大批量创建对象(这批对象有相同的属性和方法)。给定一些值,创建一个结果。
a.创建方法:
创建一个函数,在函数中创建对象,最后再返回这个对象。
b.缺陷
让不同对象有不同属性或方法,工厂模式就很难实现了。
c.优点
减少了重复性代码。
混合模式(构造函数+原型)在ECMAScript中是使用最广泛、认同度最高的一种创建自定义对象的方法。
例12 工厂模式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
function fn(name, sex) {
var obj = new Object()
obj.name = name
obj.sex = sex
obj.show = function () {
console.log(obj.name);
}
return obj
}
var s1 = fn('zs', 'man')
var s2 = fn('ls', 'woman')
s1.show()
s2.show()
console.log(s1 instanceof fn) // false 说明s1不是fn的实例
console.log(s1 instanceof Object) //true
</script>
</html>
八、this(面试核心点)
this是一个地址指针。
1、全局中的this,指向window对象。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
var x = 100
console.log(this);//window
console.log(window.x, this.x, x);//100,100,100
console.log(window === this);//true
</script>
</html>
2、函数中的this
原则:谁调用就指向谁。一般情况下是指向window对象的。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
var name = 'tom'
function fn() {
var name = 'mike'
console.log(this.name);//tom
console.log(name);//mike
}
fn()
</script>
</html>
如果开启了"use strict;"严格代码格式,this指向的是window。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
'use strict'
var name = 'tom'
function fn() {
var name = 'mike'
//console.log(this.name);//在严格代码格式下,this是没有指向的
console.log(this);//undefined
console.log(name);//mike
}
fn()
</script>
</html>
3、对象方法中的this,指向当前对象。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
var obj = {
id: '001',
goodsName: 'hello',
price: 9999,
salePrice: function () {
console.log(this.goodsName + ":" + this.price);
console.log(this);//指向当前对象
}
}
obj.salePrice()
</script>
</html>
4、构造函数中的this,指向当前实例化的对象。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
function Car(name, type, color) {
this.name = name;
this.type = type;
this.color = color;
this.show = function () {
console.log(this.name + this.type + this.color);
console.log(this)
}
}
Car.prototype.price = 190000;
Car.prototype.run = function (time) {
console.log(this.name + '百公里加速时间为:' + time + '秒!')
}
var car1 = new Car('大众', '家用版', '白色');
var car2 = new Car('卡宴', '豪华版', '土豪金');
car1.show()
car2.show()
car1.run(15)
car2.run(5)
</script>
</html>
5、借来的this,所有函数都有三个方法(call、apply和bind),可以用它们实现函数中this指向新的对象(修改函数中this的指向)。
(1)call:主动式将函数中的this指向新的对象,调用一次就立即执行一次(谁调用就指向谁)。
语法:被调用对象的方法.call(当前对象,参数1,参数2,...)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var p1 = {
name: 'zs'
}
p1.tel=function(){
console.log(this.name+" call for ls");
}
var p2={
name:'ww'
}
p1.tel.call(p2)// p2借p1的tel方法 this指向了p2这个新的对象
console.log(p1);
console.log(p2);
</script>
</body>
</html>
(2)apply
用法与call一样,只是传参格式不一样。传参必须以伪数组的方式传递,不管有几个参数,都必须以这种格式进行传递。
语法:被调用对象的方法.apply(当前对象,[参数1,参数2,...])
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var p1 = {
name: 'zs'
}
p1.tel = function (callName, time) {
console.log(this.name + '给' + callName + "打了" + time + "分钟电话");
}
var p2 = {
name: 'ls'
}
// p1.tel.call(p2, 'ww', 30)//传参用列表
p1.tel.apply(p2, ['ww', 30])//传参用伪数组
</script>
</body>
</html>
(3)bind(ES5新增)
被动式改变this指向,实现对原对象的拷贝。
语法:回调函数.bind(对象,参数1,参数2,...)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var name = '这是全局的name'
var obj = {}
obj.name = 'zs'
obj.show = function (str) {
console.log(this);
setTimeout(function (str1, str2) {
console.log(this.name + str + str1 + str2);
}.bind(this, 'aaa', 'bbb'), 1000);
}
obj.show('is a good people')
</script>
</body>
</html>
6、call、apply和bind的区别
相同点:
i)都是函数原型的方法,可以改变函数中this的指向;
ii)第一个参数都是this要修改的目标对象;
iii)都可以传递参数。
不同点:
i)call和apply是主动式的,修改this指向时立即调用1次;
ii)bind是被动式的,只修改this的指向,不执行;
iii)call的参数是直接列举在对象的后面,而apply是以伪数组的形式传参;
iv)bind一般用在回调函数中。