基本数据类型
Number String Boolean undefined null BigInt Symbol
引用数据类型
Object (包含 Array、Function)
函数作用
将重复代码封装,以便下次使用,减少代码冗余
构造函数创建
var fn1= new Function("console.log('调用了函数')") //函数声明
fn1() //函数调用
console.log(typeof fn1)
使用关键字创建
function fn2(){
console.log('调用了函数')
}
fn2()
通过函数表达式方式创建
var fn3 = function(){
console.log('调用了函数')
}
fn3()
eval(String)
将字符串当函数执行
eval('var num = 123')
eval('console.log(num)')
function test(str){
alert(str);
}
var a='test';
var b='345';
eval(a+'('+123+')');
eval(a+'(b)');
会存在sql注入问题,因此一般不采用
匿名函数
function(){
}
带参函数
function fn( 形参1,形参2){
}
fn(实参1,实参2)
基本类型数据和引用数据类型数据作为函数参数传递
var a = 10 ,b = 20
function Fn(a,b){
a=100
b=200
console.log(a,b) //100 200
}
Fn(a,b)
console.log(a,b) //10 20
var obj = {a:10,b:20}
function Fn(obj){
obj.a = 100
obj.b = 200
}
Fn(obj)
console.log(obj.a,obj.b) //100 200
上面两个例子Fn()分别传递了基本数据类型和引用数据类型的数据,
其中传递基本数据类型的Fn参数其实就是数据本身,它一旦改变,后面获取到该变量的值也会发生改变,但用于作用域的原因,函数内对该变量的改变,改变不了全局变量。
传递引用数据类型的Fn其实传递的是栈内存中的地址,在函数内部,通过地址改变了存储在堆内存中的数据,因此在函数外部通过该地址读取的数据也发生了改变
(通俗易懂地来说,小张有把钥匙,他把家里打扫得特别干净,然而小明也有一把一样的钥匙,他把房间布局改变了,小张通过他手里的钥匙进入房间,里面的布局能不改变吗?)
立即执行函数
声明立即执行函数
(function(){
console.log('调用了函数')
})
执行立即执行函数
(function(){
console.log('调用了函数')
})(); //结束时必须添加分号
带参立即执行函数
(function(形参1,形参2){
console.log(形参1,形参2)
})(实参1,实参2);
Arguments
函数内部的内置对象(伪数组),用于存储函数相关参数
function Fn(a,b){
console.log(arguments) //Arguments伪数组 [‘1’,2,NaN,callee,length,Symbol]
console.log(arguments.callee) //Fn
console.log(arguments.callee.caller)
}
Fn(‘1’,2,NaN)
arguments.callee
返回当前函数的引用,通俗一点,该argument在哪个函数中,arguments.callee就显示那个函数的函数名
使用场景:递归
arguments.callee.caller
返回当前函数调用者,
使用关键字声明的函数在调用时返回null
function Fn(a,b){
console.log(arguments.callee.caller) //null
}
Fn('1',2,NaN)
构造函数中的方法,实例化的对象,调用方法后arguments.callee.caller返回结果为null
function Product(pname,price){
this.pname =pname
this.price=price
this.show = function(a,b){
console.log('品名',this.pname,'价格',this.price)
console.dir(arguments.callee.caller) //null
}
}
let p1 = new Product('华为',4000)
p1.show(1,2)
对象的中的方法调用,返回null
let obj = {
show:function(a,b){
console.log(arguments.callee.caller) //null
}
}
obj.show(1,2)
当一个函数被另一个函数调用时,后一个函数的arguments.callee.caller返回前一个函数的函数体
function Fn1(){
Fn2(1,2)
}
function Fn2(a,b){
console.log(arguments.callee.caller) //ƒ Fn1(){ Fn2(1,2) }
}
Fn1()
什么是伪数组以及怎样判断伪数组、伪数组转为真数组的方法前面已经提到过了
工厂函数
function Product(pname,price){
this.pname =pname //this代表当前创造出来的Object
this.price=price
this.show = function(){
console.log('品名',this.pname,'价格',this.price)
}
}
let p1 = new Product('华为',4000) //创建对象p1 同时p1也叫Product的实例
let p2 = new Product('小米',3000) //创建对象p2 p1和p2不是同一个对象!
p1.show() //p1.show()与p2.show()也不是同一个 当前show的this指向p1
p2.show() //当前show的this指向p2
作用域
即一个变量的作用范围
function Fn(){
var a=0
}
console.log(a) //报错 caught ReferenceError: a is not defined
全局作用域
在script中在函数外用var、let、const 或直接声明的变量或常量,该变量在所有函数中都能调用
页面开启全局作用域就开启,页面关闭会被浏览器销毁
全局作用域中有一个全局对象window,它的key值包含console、document、alert、prompt、scroll、scrollBy、scrollTo
局部作用域(函数作用域)
函数中声明的变量或常量,只能在该函数内使用
声明提升与预解析
作用域链
var a=1
function Fn1(){
function Fn2(){
console.log(a)
}
}
Fn2找不到a寻找Fn1中的a,而Fn1中没有a,在全局作用域中寻找,寻找变量a所形成的链形结构就叫作用域链
预解析
console.log(a) //undefined
var a=0
console.log(a) //0
js代码执行时先预解析(1.检查语法;2.声明提升)再执行代码
当前代码在预解析时,将变量a提前,此时a值为undefined,因此第一句返回undefined
当运行到第二句时,a被赋值将值从undefined变为0
当运行第三句时,输出已经改变了值的变量a
声明提升
console.log(a) //undefined
if(false){
var a=10
}
console.log(a) //undefined
分析
var a console.log(a) //undefined if(false){ var a=10 } console.log(a) //undefined
console.log(Fn) //ƒ Fn(){ var a=1 console.log(a) }
console.log(Fn()) //1 undefined
function Fn(){
var a=1
console.log(a)
}
console.log(Fn()) //1 undefined
js代码执行时先预解析,Fn及其函数体被提前
执行第一句,打印ƒ Fn(){ var a=1 console.log(a) }
执行第二句,返回1,undefined(由于没有return,因此多返回了undefined)
执行第七句,再次调用解析时的Fn,返回1,undefined
由此可以看出,变量的预解析与function声明的函数的声明提升结果是不同的,
函数提升优先于变量提升
console.log(Fn) //undefined
console.log(Fn()) //报错,后面代码不会执行
let Fn=function(){
var a=1
console.log(a)
}
console.log(Fn())
var A=1
function Fn(){
console.log(A)
var A=2
console.log(A)
function A(){
return 111
}
}
Fn()
它的解析与执行顺序为:
var A //变量提升 function Fn(){ console.log(A) var A=2 console.log(A) function A(){ return 111 } } A=1 //执行 //Fn() 执行 预解析Fn()内部变量或者函数 function A(){ //function声明的函数变量提升的优先级高于变量优先级 return 111 } var A //重复声明,值延用之前的值 console.log(A) //执行 输出ƒ Fn(){ return 111 } A=2 ///执行 console.log(A) //执行 输出2
f1()
console.log(c)
console.log(b)
console.log(a)
function f1(){
var a=b=c=6
console.log(a)
console.log(b)
console.log(c)
}
分析: function f1(){ //变量提升 var a=b=c=6 console.log(a) console.log(b) console.log(c) } f1() //执行 function f1(){ var a //变量提升 a=b=c=6 //执行 b与c作为全局变量,并赋值为6 console.log(a) //执行 读取当前作用域a的值为6 console.log(b) //执行 读取全局作用域a的值为6 console.log(c) //执行 读取全局作用域a的值为6 } console.log(c) //执行 读取函数f1执行后定义的全局变量c 6 console.log(b) //执行 读取函数f1执行后定义的全局变量b 6 console.log(a) //执行 用于变量a在函数f1中声明 作用域只存在于函数f1内 因此全局变量找不到变量a 报错caught ReferenceError: a is not defined
var num = 123
function f1(){
console.log( num ); //2、123
}
function f2(){
console.log( num ); //1、undefined
var num = 456
f1()
console.log( num ); //3、456
}
f2()
(function(a){
console.log(a); //100
var a = 10
console.log(a); //10
})(100)
变量提升的顺序
var声明的变量与function声明的函数
按照顺序进行预解析,但需要注意:var在function前,先得到的undefined被function覆盖,function在var前,先得到function,但后面的undefined会变为先前得到的function
立即执行函数形参与var
先把立即执行函数形参赋值,解析var的同名变量得到undefined,但会变为赋值的参数(实参)
立即执行函数形参与function
先把立即执行函数形参赋值,解析function的同名函数得到函数体,该函数体会替换先前赋值的实参
(function(a){ //100 f a(){ console.log(a); }(100被替换)
console.log(a); //f a(){ console.log(a); }
function a(){
console.log(a);
}
console.log(a); //f a(){ console.log(a); }
a() //f a(){ console.log(a); }
})(100)
立即执行函数形参与var function
先把立即执行函数形参赋值,解析var的同名变量得到undefined,但会变为赋值的参数(实参),解析function的同名函数得到函数体,该函数体会替换先前的优胜者(实参)
(function(a){ //100 undefined(取100) f a(){ console.log(a); }(100被替换)
console.log(a); //f a(){ console.log(a); }
var a = 10
console.log(a); //10
function a(){
console.log(a);
}
a() //10() is not a function
})(100)
立即执行函数形参与function var
先把立即执行函数形参赋值,解析function的同名函数得到函数体,该函数体会替换先前赋值的实参,解析var的同名变量得到undefined,但会变为先前的优胜者(function的同名函数函数体)
(function(a){ //100 f a(){ console.log(a); }(100被替换) undefined(取f a(){ console.log(a); })
console.log(a); //f a(){ console.log(a); }
function a(){
console.log(a);
}
console.log(a); //f a(){ console.log(a); }
var a = 10
a() //10() is not a function
})(100)
let声明提升与var声明提升
console.log(a) //undefined
var a=1
console.log(b) //报错 let不存在声明提升 643:1 Uncaught ReferenceError: b is not defined
let b=1
区别
let(const)不允许重复声明 //caught SyntaxError: Identifier ‘变量名’ has already been declared
let(const)具有块级作用域
const声明必须赋初始值
const声明常量,常量名建议大写
const可以声明对象、数组,不过一般不建议
<body>
<button>按钮一</button>
<button>按钮二</button>
<button>按钮三</button>
<button>按钮四</button>
</body>
<script>
const btns = document.querySelector All("button")
for(var i=0; i<btns.length; i++) {
btns[i] .onclick=function() {
alert(i)
}
</script>
//无论点击哪个都是4
<script>
const btns = document.querySelector All("button")
for(let i=1; i<btns.length; i++) {
btns[i] .onclick=function() {
alert(i)
}
</script>
//点击按钮显示对应数值