1.javaScript:负责⽹⻚功能的
笔试可能会遇到的问题 面试可能遇到的问题/重点概念
html+css实现静态页面,js负责网页的功能
ECMAScript(核⼼5.0) + DOM(⻚⾯操作) + BOM(浏览器操作相关的)
script需要放在body的结束标签之前;也可以像css一样外部引入:在<body>标签中<script src=”路径”></script>
script标签要么用来引入js文件,要么用来写js代码,但是不能两样事情都做,即不能在script标签中既用src引入js文件,又在里面写js代码。
2.编程语⾔
c :操作系统、嵌⼊式、驱动开发
c++ :桌⾯软件、游戏 (英雄联盟)
c# :Windows桌⾯软件、.NET Web、服务器
java:企业级应⽤,web开发、服务器后端 python
php
javaScript
3.程序员
程序运行于内存中,内存大的在同时开启多个软件时的运行更加流畅,更加快速。
程序运行于内存中,内存是处理数据的,因此数据在内存中。
变量:内存中专⻔⽤来存储数据的空间
程序: 处理数据的 接受数据--处理数据--输出数据 程序运⾏在内存中
数据:语⾔啊 阿拉伯数字
4.变量
临时存储数据的
何时使⽤:如果数据需要临时存储的时候,那么就需要变量
关键字:程序中特殊含义的字符
如何使⽤: 声明—命名—初始化
1.声明变量(var) 2.命名 (变量的命名规则:不能⽤关键字,不能⽤中⽂,不能⽤特殊符号,⻅名知意,不能⽤纯数字,不能⽤数字打头,可以⼤写,严格区分大小写(通常小写);名字由多个单词组成的话要用驼峰式命名(除了首字母外其他单词的首字母大写myName)或大驼峰(全部的首字母都大写MyName)) 3.初始化变量后 用等号加数据 =是赋值的意思
如var money=123
2.使⽤变量就相当于使⽤变量⾥⾯的数据
3.⼀个变量只能存⼀个数据
4.变量是可以更改的 变量名=新值
5.变量声明了但是没有赋值,如果此时要输出该变量的值,结果为undefined
6.对于未声明的变量直接赋值(直接使用是错的),那么js会⾃动帮你在全局声明(即使是在函数内对未声明的变量进行了直接赋值,那这时就变成了全局变量)
//在函数内a未进行声明就直接赋值了,这时js会自动帮忙在全局声明,因此a就变成了全局变量,在全局范围内都可以访问的到了
function fn() {
a = 5;//a未声明直接赋值了
}
fn()
console.log(a)//输出结果:5
7.变量声明提前/变量提升:只要是变量声明语句都会提前,变量声明语句会⾃动提升到当前作⽤域最顶部(只是声明提前,赋值并没有提前)
例:变量声明提前+变量只是声明了但没有赋值结果为undefined
function fn() {
//相当于将变量声明的语句var a 提前到了这里,但是赋值的语句并没有提前
//将变量声明提前到了该作用域,即函数内的最顶部,而不是全局的最顶部
console.log(a)
var a = 1
}
fn()
//输出结果为undefined
例:注意:下面的程序中函数内部的变量声明提前的问题 ×
var a = 5;//声明并赋值全局变量a
function fn() {
//变量声明提前 var a
a = 8;//由于变量声明提前了,这里修改的a是局部变量a,局部变量a修改为8
var a = 10;//变量声明提前至函数内的最顶部,//再次修改局部变量a 的值为10
console.log(a)//这里输出的是局部变量a 的值10
}
fn();
console.log(a)//这里输出的是全局变量a的值,在函数内部由于变量声明提升,全局变量a根本就没有被修改过,全局变量a值还是为5
//输出结果为 10 5
例:变量声明提前+未声明的变量js自动声明为全局变量 √
//变量声明提前var a
function fn() {
console.log(a)//函数内部没有局部变量,访问的是全局变量a,在这时全局变量a的值为5,还未执行到a=8的时候
a = 8;//变量未声明,js自动帮忙声明为全局变量,因此在函数内部没有局部变量。
//执行a=8将全局变量a的值改为8
}
var a = 5;//全局变量a声明提前,先给全局变量a赋值为5在调用函数
fn();
console.log(a);//在函数内部将全局变量a 的值修改为了8,输出为8
//结果:5 8
例:函数的执行顺序+变量声明提前+未声明的变量js自动声明为全局变量+声明了但尚未赋值的变量输出的结果为undefined √
// var a js自动声明为全局变量
a = 3;//全局变量,js自动声明为全局变量
function fn() {
//var a
console.log(a)//输出的是局部变量a,但是局部变量a只是声明了未赋值,结果为undefined
a = 5;
var a;//声明提前,局部变量a
console.log(a)//输出的是局部变量a
}
console.log(a)
fn();
//结果:3 undefined 5
例子:变量声明提前+(a++)与(++a)区别+全局变量与局部变量 √
// var a; 变量声明提前
a = 8;//全局变量a=8
function fn() {
//var a; 局部变量声明提前
a = 1;//局部变量a=1
var a = 12;//局部变量声明提前. //局部变量a=12
console.log(a++);//输出局部变量a=12,后a+1局部变量变为a=13
}
var a;//全局变量声明提前
console.log(a++);//输出全局变量a=8,后a+1变为全局变量a=9
fn();
//结果:8 12
例子:调用两个函数+局部变量与全局变量+未声明的变量js自动声明为全局变量 √
//var a; js帮忙声明为全局变量,既是函数外的a自动声明,又是函数fn内的a自动声明
a = 5;//变量未声明,js自动帮忙声明为全局变量,全局变量a=5
function fn() {
console.log(a)//输出为全局变量a=5,在该函数内没有局部变量a,访问的是全局变量a
a = 12;//变量未声明,js自动帮忙声明为全局变量,//将全局变量值改为a=12
}
function fn2() {
//var a; 局部变量声明提前
a = 4;//局部变量a=4
console.log(a);//输出局部变量a=4
var a = 8;//局部变量声明提前.//将局部变量a修改为a=8
}
fn();//使用的是全局变量a
fn2();//使用的是局部变量a
console.log(a)//输出全局变量a=12,在fn()函数中的a为全局变量,修改过全局变量a的值
//结果:5 4 12
8.等号左边⼀定是变量,等号右边⼀定是数据或者表达式(结果是个数据)
5.控制台
是⼀个可以输出js代码的地⽅
作⽤:1.⽤来调试的 2.提示错误 3.扯淡
控制台不报错不代表没有错,控制台报错了不⼀定代表是所标注的错误,但是⼀定代表有错
console.log(要输出的数据):在控制台中输出数据
alert()
作⽤:调试的 提示/警告 会中断程序运⾏,直到将弹窗关闭之后后面的语句才会继续执行 以弹窗的形式出现
console.error(要输出的数据):以错误数据的形式在控制台上显示
6.数据类型
原始数据类型
1.number(数字类型)
2.string(字符串) 字符或者字符与其他数据的组合
字符串⼀定要加引号,加引号的数据⼀定是字符串
如果引号嵌套的情况,双引号⾥⾯可以放单引号,单引号⾥⾯不允许放双引号
变量名不能加引号
3.boolean(布尔类型) true false
4.undefined(未定义) ⽤来⾃动初始化变量的
5.null(空指针,也是对象的一种) ⽤来主动释放对象的,像全局变量需要手动释放 空地址,没有引用的内容
var a=[1,2,3,4];a=null; 并不是把数据删掉了,只是将栈中的指针设为null,让指针没有指向的数据,切断了栈和堆之间的联系,这样在堆中的数据才会被垃圾回收机制回收。
引用数据类型
- array(数组)
- //function (函数)
- object(对象) 函数function和数组array也是对象
js中一切皆对象,数组和函数是对象的一种,null也是属于object
两种数据类型的区别
主要差别在于存储位置不同,虽然都在内存中,但是也是有差别的
原始数据类型:数据存在变量本地 栈
引⽤数据类型:数据不存在变量本地 堆。也会在栈中开辟空间,里面存的是指针,即数据在堆中
的地址
栈 / 堆:本质上是内存中的⼀块存储空间
每次只要声明一个变量,就在栈中开辟一块空间放入数据,当数据为原始数据类型时直接将数据存入栈中即可;如果数据为引用数据类型的,这时在栈中仍然会开辟一块空间,但这是发现空间不够放入数据,这时将数据存入堆中,此时栈中给引用数据类型的变量开辟的空间仍然存在,里面存入的是数据在堆中的存放的地址即指针,之后每次要访问该引用数据类型的数据时,先在栈中找,然后根据栈中的指针的地址在堆中找到相应的数据。如:
var a=1;//number,原始数据类型,数据存放在栈中
var b="hello"//string ,原始数据类型,数据存放在栈中
var c=true//boolean 原始数据类型,数据存放在栈中
var arr=[1,2,3,4]//array 引用数据类型,数据存放在堆中,栈中存的是指针,即数据在堆中的地址
var d = null//地址就是null,但是在堆中没有引用内容
//其数据类型为object对象
注意:变量复制的时候、变量比较({}==={} false)的时候需要格外注意,引用数据类型操作的都是指针,而不是数据本身。
7.运算符
表达式:由运算符连接的,最终运算结果是⼀个值的式⼦
表达式跟值是等效的
程序模拟⼈类进⾏计算的符号
(1)算术运算符:+ - * / % ++ -- 仅适⽤于number类型的数据
次方:基数**指数或Math.pow(基数,指数)(10**3或Math.pow(10,3)=1000)(与之前学习的不同的是,在js中^表示的是异或,不是次方)
关于++,如果单独使⽤,放前放后都可以
如果不是单独使⽤,后++,先⽤旧值参与表达式,表达式结束之后再+1
前++,先+1,再参与表达式
(2)关系运算符: > < >= <= ==(不严格判相等) ===(严格判相等) != (不严格判不相等) !== (严格判不相等) 仅适⽤于number类型的数据(将那些不是number类型的数据转换为number类型的数据) 其结果是布尔类型的true或false 不允许连着写
==:不严格的判断是否相等,允许发生类型转换。如a=true;b=1;结果为true。如a=5;b=”5”;结果为true.
===:严格的判断是否相等,不允许发发生类型转换,必须一模一样
布尔与数字的运算时:true会自动转换为1,false会自动转换为0.
(3)逻辑运算符:与(&&) 或(|| ) ⾮ (!) &&和||同时出现的话,先计算 && ,再计算 ||
如果使用 && 或 || 的时候两边跟的不是条件判断而是一个普通的值时,返回的也是一个普通的值。因此,逻辑运算符返回的不一定都是布尔值,要看两边的实际内容。
&&和||具有短路性:后面的有些运算可能压根就不执行。
如:
var a = 5;
var b = 0;
var c = 7;
console.log(b && a++);
console.log(a); //a输出的结果为5,因为&&前面的b为false,无论后面的值为什么结果都是b的内容,所以后面的a++压根不用执行,因此a的值还是5
关于逻辑运算的返回值:
- 只要“||”前⾯为false或者转换为布尔值是false,⽆论“||”后⾯是true还是false还是普遍的数字值,结果都返回“||”后⾯的值。
- 只要“||”前⾯为true或者转换为布尔值为true,⽆论“||”后⾯是true还是false还是普遍的数字值,结果都返回“||”前⾯的值。
- 只要“&&”前⾯是false或者转换为布尔值是false,⽆论“&&”后⾯是true还是false,结果都将返“&&”前⾯的值;
- 只要“&&”前⾯是true或者转换为布尔值是true,⽆论“&&”后⾯是true还是false还是普通的数字值,结果都将返“&&”后⾯的值;
- 需要说明的是“&&”的优先级是⾼于“||”的,下⾯测试:
1 console.log(1||'a'&&2); //这段代码返回的是1
(4)赋值运算符:= += -= *= /= %=
(5)字符串连接运算符:+ 任何数据(包括布尔类型的ture和false)与字符串拼接,结果都是字符串
(6)三⽬(元)运算符: 条件(结果为true或false的结果) ? 条件成⽴时候的值/表达式 : 条件不成⽴时候的值/表达式
//使用三元运算符 计算三个中或者更多的最大值;先比较a>b?,若成立再比较a>c?若不成立则比较b>c?
var a = 555;
var b = 256;
var c = 10;
var d = a > b ? (a > c ? a : c) : (b > c ? b : c)
console.log(d)
typeof():⽤来检测数据类型,是一个函数 console.log(typeof(a));
typeof的返回值是有固定的值的:number、string、boolean、undefined、object、function、symbol。
使用 typeof 无法区分array 和 object ,但是可以区分 function.
如:
//typeof的返回值类型
var a = 12
var b = "hello"
var c = true
var d = null
var e
var f = [1, 2, 3, 4]
var g = { name: "Lily", age: 18 }
function fn() {}
console.log(typeof (a))//number
console.log(typeof (b))//string
console.log(typeof (c))//boolean
console.log(typeof (d))//null->object
console.log(typeof (e))//undefined
console.log(typeof (f))//array->object
console.log(typeof (g))//object->object
//无法用typeof区分array和object,但可以区分函数function
console.log(typeof (fn))//function
8.语句
if else 可以进⾏多重条件判断
if (条件1) {
满足条件时执行的语句
}
else if (条件2) {
满足条件时执行的语句
}
else if (条件3) {
满足条件时执行的语句
}
else {
不满足条件时执行的语句
}
以下六种情况都算false ,其他的都是true
①false ②0 ③undefined ④null ⑤"" ⑥NaN(not a number,通常出现在对非数字的进行数字运算而无法算出结果的时候,结果就是NaN)。
空数组[]和空对象{}是true
9.类型转换
显示类型转换
我们主动更改数据类型
toString()其他类型的转为字符串;Number()将字符串类型的数字转为数字类型的数字;
parseInt() parseFloat()将以数字开头的字符串转为数字;
split()将字符串切割转换为数组;join()将数组拼接转换为字符串;
JSON.parse()将字符串转换为对象;JSON.stringfy()将对象转换为字符串;
隐式类型转换
自动发生的,程序自动进行的,方便运算
js中的数据会根据具体情况⾃动改变数据的类型。
布尔与数字的运算时:true会自动转换为1,false会自动转换为0.
字符串与其他类型的拼接时:其他的数据类型都会发生隐式类型转换,转换为字符串类型。
字符串比较大小:使用编码法则转为数字
字符串与布尔类型的关系运算:全部转换为number类型,true为1,false为0,其他的字符按照编码转。
判断真假(是否成立,逻辑运算符):转换为布尔值。数字0会转为false,其他的数(包括小数和负数)都转换为true;字符串中只有空字符””转为false,其他的都转为true. undefined算false
10.函数/方法及其例题
(1)什么情况下⽤函数:如果⼀段代码要反复调⽤,那么就考虑封装成函数了。
函数:封装⼀段执⾏专⻔任务的代码段。
函数是不调⽤不执⾏的
(2)函数声明方法1:function 函数名(形参){
代码段//想⼲的事
}
函数声明方法2(声明变量的方式声明函数):var 函数名=function(参数){代码段} (等号右边的函数也是数据,函数本质上就是一个值,它的数据类型是对象) 要注意声明和调用的顺序,先声明,后调用
这两种函数声明的方法实现的效果是一样的,但是在函数提升上是不一样的。按照函数方式声明时可以将函数整体提前;而按照变量的方式声明的函数只是将函数声明提前了,函数的整体并没有提前。因此在使用以函数方式声明的方法时声明与调用的顺序都可以,而使用以变量方式声明的方法时一定要先声明后调用,否则会出现函数提升的错误。
例子:函数声明方法2 中的函数提升的对比:
// 使用声明变量的方式声明函数,这种是正确的
var fn = function () {
console.log(223)
}
fn()
//var fn; 结果是错误的,只是将函数的声明提前了,整体未提前
fn()//按照变量声明提前的规则,只是将函数声明提前了,函数整体并没有提前,结果出现错误
var fn = function () {
console.log(223)
}
(3)函数名与变量名不能重名,因为函数的第二种声明方式是按照变量的方式声明的,函数本质上是一个值,将fn声明为了函数,又将fn声明为变量,由于变量声明比函数声明更优先,在这时如果调用函数会出现fn不是函数的错误,但是如果是使用变量fn则可以正常使用。
//函数名与变量名同名问题:由于变量声明比函数声明更优先,有了变量声明之后,函数的声明就失效了。
//函数声明
function fn() {
console.log(125)
}
//变量声明
var fn = 5
// fn(); //调用失败,出现fn不是函数的错误
console.log(++fn)//正常使用变量fn,结果为6
(4)函数的调⽤:函数名(实参)
参数:函数内独有的变量,接受函数外的数据,在函数内部处理,参数可以让⽅法更灵活
形参:形式上的参数,方法定义时的参数
实参:实际上的参数,方法调用时的参数
参数不限制数量,不限制数据类型,多个参数之间以逗号隔开就⾏
(5)函数提升:整体提前到当前作用域的最顶部
(6)return关键字:
函数是⼀个纯过程,没有任何结果。如果函数的执⾏你需要⼀个结果,可以加return关键字
return 你想要的值 函数的结果就是return后⾯的表达式
return的本意其实是退出函数的运⾏,如果return后⾯有值的话,那么会在退出的同时,返回⼀个结果
(7)js中函数的参数传递方式为:按值传递。
按引用传递:
如:
function fn(a) {
a = 5;
}
var m = 10
fn(m)
console.log(m)//输出结果:10
//js中的函数传递为按值传递,传进去的m在函数内的操作修改,不影响函数外的值
如:
var arr = [1, 2, 3, 4]
function fn(a) {
a[0] = 5
}
fn(arr)
console.log(arr)//输出结果:[5,2,3,4]
//因为变量传进去的是指针,指向同一块数据,只要做了修改都会受到影响
11.作用域scope
⼀个变量可⽤的范围
全局作⽤域:函数外,全局不能访问局部的数据
局部作⽤域:函数内, 局部内可以访问全局的数据
函数调⽤的时候才创建,调⽤结束之后⽴即销毁
局部内要更改某个数据,优先⽤局部内的,局部内没有会往外层找
全局变量:在全局作⽤域内/函数外声明(不是使用)的变量叫全局变量。
局部变量:在局部作⽤域内/函数内声明(不是使用)的变量叫局部变量。
作用域链:对于嵌套的函数,内层的函数中的变量从内层一层一层的向外层找相同的变量,直到找到了或者找到全局变量为止。
即函数在执行的过程中,先从自己内部寻找变量,如果找不到,再从创建当前函数所在的作用域去找,从此往上,也就是向上一级找,直到找到全局作用域还是没找到。
作用域链的本质是底层的变量查找机制。
12.JS垃圾回收机制
垃圾回收机制简称GC。
JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。
内存的生命周期
JS环境中分配的内存,一般有如下生命周期:
1.内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存。
2.内存使用:即读写内存,也就是使用变量、函数等。
3.内存回收:使用完毕,由垃圾回收自动回收不再使用的内存
4.说明: 全局变量一般不会回收(关闭页面才会回收);
一般情况下局部变量的值,不用了,会被自动回收掉
内存泄漏
程序中分配的内存由于某种原因程序未释放或者无法释放,叫做内存泄漏。
垃圾回收机制的算法说明
堆栈空间分配区别:
1.栈(操作系统):由操作系统自动分配释放函数的参数值、局部变量等,基本数据类型放到栈里面。
2.堆(操作系统):一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收。复杂数据类型放到堆里面。
下面介绍两种常见的浏览器垃圾回收算法:引用计数法和标记清除法
引用计数法
IE采用的引用计数算法,定义“内存不再使用”,就是看一个对象是否有指向它的引用,没有引用了就回收对象。
算法:
1.跟踪记录被引用的次数
2.如果被引用了一次,那么就记录次数1,多次引用会累加++
3.如果减少一个引用就减1--
4.如果引用次数是0,则释放内存。
首先声明变量person,系统自动分配内存空间,该数据是一个引用类型的数据,将对象数据存储在堆中,在栈中存储的是数据在堆中的地址即指针。此时该对象数据被引用次数+1,变为了1次。
然后,声明变量p,分配内存空间,把person存储的指针赋值给p,person和p都指向堆中的同一块数据。此时,该对象数据被引用次数+1,变为了2次。
然后,把person的值变为1,为原始数据类型,直接把数据存储在栈中。此时person不再指向堆中的对象。此时,该对象数据被引用次数-1,变为了1.
最后,把p值变为nul,为原始数据类型直接存储在栈中,此时p也不再指向堆中的对象。此时,该对象数据被引用次数-1,变为了0。
在这时,引用次数变为了0,该对象被垃圾回收器回收。
缺点:
但它却存在一个致命的问题:嵌套引用(循环引用)如果两个对象相互引用,尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄露。
因为他们的引用次数永远不会是0。这样的相互引用如果说很大量的存在就会导致大量的内存泄露。
标记清除法
现代的浏览器已经不再使用引用计数算法了。现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的。
核心:
1.标记清除算法将“不再使用的对象”定义为“无法达到的对象”。
2.就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。凡是能从根部到达的对象,都是还需要使用的。
3.那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。
13.闭包(面试重点)⭐⭐⭐⭐⭐
概念:内部函数使⽤了外部函数的局部变量,这种结构叫闭包(函数套函数)
作⽤:保护变量的/避免全局污染(变量放在全局被不小心篡改)
性能问题/弊端:内存泄漏 /内存溢出 因为闭包中的局部变量⼀直被全局变量使⽤着,而全局变量无法被垃圾回收机制清除,导致该局部作⽤域也无法被垃圾回收机制回收,内存释放不掉,依此导致的内存泄露问题。
垃圾回收机制:自动回收垃圾(不用了的变量,调用结束了的局部变量)。
全局变量永远不会被回收,占内存。
函数作用域在调用的时候才会创建,在调用结束之后立即就会销毁。函数占用的内存很少,全局变量才会占用大量的内存。
将变量声明和使用放在函数内部,这时会出现每次调用该函数时变量会被重新赋值,之前对变量的操作无法进行保存,
如果只想多次执行函数内的一部分,而另一部分不想多次执行,这时可以将不想多次执行的语句放在外面的函数里,想要多次执行的语句再放进一个函数里进行函数嵌套使用,调用的时候调用里面嵌套的函数。使用的时候外部函数中的局部变量的值会得到保存,而内部函数中局部变量的值每次都得到刷新。
产生问题:调用嵌套的函数属于局部的,无法直接进行调用。解决:在函数内return fn2;这样虽然无法将局部变为全局,但是可以在全局中就可以拿到局部的数据了。
function fn() {
var n = 0;//将n声明为局部变量
// function fn2() {
// return n++;
// }
// //fn2函数相当于var fn2=function(){return n++;}
// return fn2;
//上面的内容等价于:直接返回值是一个函数,就不用fn2这个临时的变量去存储了直接使用
return function () { return n++; }
// 在外面全局想要拿到局部变量,可以使用return拿到局部变量到全局中使用
}
var c = fn();//fn()返回的是函数fn2,相当于var c=function(){return n++;}
//相当于var c=function(){return n++:}
//相当于function c(){return n++;}
//这时计数器就变成了c()
console.log(c());
//等价于
//console.log(fn()());
//因为执行fn()返回的是一个函数,在调用的时候按照函数的形式调用,fn()()返回的才是一个值。
//但这时会出现结果错误,因为每次都在调用完fn()函数之后,产生的局部变量就会别自动回收,下次再次调用的时候会重新创建作用域
//为什么给fn()赋给了c之后就不会回收,因为c是全局变量,全局变量永远不会被自动回收。核心是被全局变量使用了,内存释放不掉
console.log(c());
console.log(c());
console.log(c());
n = 152;//这时再在全局修改n的值就不会产生影响了
console.log(c());
console.log(c());
console.log(c());
调用几次函数就需要有几个标记变量,想要用这个变量又不能把他放在局部变量,放全局变量的话又封装不彻底数据污染导致调用几次函数就得要声明几个变量,在这时就可以使用闭包来使用这个标记变量。
目前使用到了闭包的场景:DOM实战2注册界面的眼睛、定时器防抖函数和节流函数都用到了闭包。
//使用闭包实现的眼睛,这样就不用再使用了两个flag了
images[0].onclick = eyesSwitch(0)
images[1].onclick = eyesSwitch(1)
function eyesSwitch(index) {
var flag = true
return function () {
if (flag) {
images[index].src = "images/open.png"
inputs[index + 1].type = "text"
} else {
images[index].src = "images/close.png"
inputs[index + 1].type = "password"
}
flag = !flag
}
}
描述闭包导致的内存泄漏问题的原因。
由于闭包中的局部变量始终被全局变量使用着,而全局变量是不能销毁的(不能被垃圾回收机制清除),导致闭包内的局部作用域也无法被垃圾回收机制回收,使得内存无法得到释放,从而导致内存泄露问题。
关键点是函数fn在全局c中被调用了,把返回的函数结果给到了全局变量,而全局变量不能被销毁,导致fn无法被释放.
//不会导致内存泄漏问题的情况
function fn() {
let n = 0;
return function () {
return n++
}
}
console.log(fn()())//0
console.log(fn()())//0
console.log(fn()())//0
console.log(fn()())//0
//这种情况下没有导致闭包,是因为局部变量fn调用结束之后就被销毁了
//会导致内存泄漏的情况
function fn() {
let n = 0;
return function () {
return n++
}
}
let c = fn();//这一步是闭包的关键点,函数fn被全局变量c一直使用着,而全局变量是不能被销毁的,导致局部变量fn也不能被销毁,内存无法得到释放
console.log(c())//0
console.log(c())//1
console.log(c())//2
console.log(c())//3
- 闭包一定有return 吗?不
什么时候使用return ? 当函数外想要使用闭包内的变量时加return
闭包可以实现数据的私有,外面的可以使用这个变量,但是外面是无法进行修改的
- 闭包一定会导致内存泄漏吗?不
不是所有的内存泄漏都要手动回收的。
比如react里面很多闭包是不能回收的
函数的作用:避免变量全局污染;使数据私有化,外部无法修改内部数据;可以让外部可以使用内部的私有数据。
闭包的核心作用:使变量可以驻留在内存,不被回收。
闭包的作用:封闭数据,提供操作,外部也可以访问函数内部的变量
14.循环语句
(1)循环:程序反复执⾏⼀套相同的代码
(2)循环三要素:
1.循环变量:循环中做判断的量 循环变量⼀定是向着循环退出的趋势去变化
2.循环条件:保证循环继续运⾏的条件
3.循环体:循环中每次要做的事
(3)while循环
while(循环条件){要做的事}
for循环 for in 循环也适用于数组(但是其角标)输出的是字符串类型的)
⽤途:⽤作数组遍历
for(var i=0;i<10;i++){要做的事} 在这时的i还是全局变量
(4)break:结束当前循环
continue:直接跳过该轮循环,不再继续向下运行,直接进行下一轮循环
15.数组
数组:批量存储多个同类型数据的,多个数据以逗号隔开,数组是没有任何数据类型限制的也没有任何数量限制的。
var arr=[1,2,3,4,5,6];
数组其实就相当于多个变量的集合。
数组的访问:数组名[⻆标]
数组的更改 :数组名[⻆标]=新值
数组的属性:length 直接返回数组的⻓度
在数组中做判断条件时要将时同一类型的放在else中,不是同一类型的不要放在else中。
另外,在用if判断时不要一开始就做比较判断,先做其他类型的判断,因为不是数字类型的数据也是可以做比较大小的,如number 和string类型的。
浅拷贝/浅复制⭐⭐⭐
对于引用数据类型的数据进行复制赋值时,给的都是指针,而不是直接给数据。使得他们都拥有相同的指针,指向相同的数据,这时修改其中一个中的数据,另一个的内容也是会受到影响的。这种情况就是浅拷贝。浅拷贝复制的是指针,不是数据
var a = [1, 2, 3, 4, 5]
var b = a; //复制的是指针,不是数据
b[0] = 100;
console.log(a)//结果为:[100,2,3,4,5]
console.log(b)//结果为:[100,2,3,4,5]
//数组a和数组b的输出内容一样,因为var b=a是将a的指针赋值给b,这时a和b有着相同的指针地址,他们指着堆中相同的数据,这时修改b中的数据,a中的数据也是会受到影响的。
//对于引用数据类型的数据进行复制赋值时,给的都是指针,而不是直接给数据
字符串VS数组VS对象
字符串和数组相同点:都可以通过角标访问;都具有length长度属性;
数组:可以更改内容;
字符串:不能更改内容;
数组和对象相同点:都是用来存储多个数据的;内容都是可更改的;
数组:用来存储同类型的数据 (虽然可以存储不同类型的数据,但不这么用);
对象:用多个数据共同描述一个事物;
如:
如: var str = "hello"
str = "hi"//这不是更改字符串的内容,这是将原来的字符串的内容丢掉重新赋值的
console.log(str)//输出结果是hi
var str = "hello"
console.log(str[1])//输出为e,通过角标访问的是字符串中的某个字符
var str = "hello"
str[1] = "i"//更改字符串
console.log(str)//输出为hello,字符串未更改成功
//因此,字符串的内容是是无法像数组一样进行更改的
console.log(str.length)//结果为5,输出字符串的长度
二维数组
var arr=[[1,2,3],[4,5,6]] 访问时arr[1][0]结果为4,数组arr的长度为2
16.冒泡排序算法(小 -> 大)
n个数据,第0轮比较n-1次,第1轮比较n-1-1次,第2轮,比较n-1-2次,...,第n-2轮比较n-1-(n-2)=1次
每轮拿第0项和第1项比较,第0项>第1项则交换顺序,否则不动;
拿第1项和第2项做比较,第1项>第2项则交换顺序,否则不动;
依次类推,比较n-1次,最后得到:最后一个值是该轮中最大的数。
从头再来一次,比较n-2次:每次拿第0项和第1项做比较......
/* 冒泡排序 从小到小 n个数据*/
/*每次拿第0项和第1项比较,第0项>第1项则交换顺序,否则不动;
拿第1项和第2项做比较,第1项>第2项则交换顺序,否则不动;
一次类推,比较n-1次,最后得到:最后一个值是该轮中最大的数。
从头再来一次,比较n-2次:每次拿第0项和第1项做比较......*/
/*性能优化:
1 在一轮比较中,n个数据需要比较n-1次找到该轮中最大的值
2 共n个数据,需要比较n-1轮,就可以找到所有数据的顺序,最后一个是数据根本不用比较一定是最小的数据
3 每轮得到最后一个该轮最大的数在之后的每轮比较中是不用再参与比较的,因为他们一定是剩下的数中最大的数*/
/* 即 n个数据,第0轮比较n-1次,第1轮比较n-1-1次,第2轮,比较n-1-2次,...,第n-2轮比较n-1-(n-2)=1次 */
var a = [12, 9, 123, 96, 8, 6, 56, 47, 33]
var n
for (var i = 0; i < a.length - 1; i++) {
for (var j = 0; j < a.length - 1 - i; j++) {
if (a[j] > a[j + 1]) {
n = a[j]
a[j] = a[j + 1]
a[j + 1] = n
}
console.log("执行了一次")
}
}
console.log(a)