函数
可复用的代码块
- 函数是一种对象
- 所有的函数都是由Function构造的
定义函数
函数声明(具名函数)
function printName(){
return 返回值;
}
函数表达式 (匿名函数)
var a = function(x,y){//左边是赋值,右边是表达式
return x+y;
}
构造函数
var add = new Function('x','y','return x + y')
//等同于
function add(x,y){
return x + y;
}
函数本身和函数调用
fn和fn()
let fn = () => console.log('hello');//fn保存了匿名函数地址
let fn2 = fn;//将这个地址赋值给fn2
fn2();//hello fn2调用了匿名函数
参数
函数可以传递多个参数
没有传递值的命名参数将自动被赋予undefined值。
function fn(a){
return a;
}
fn();//undefined
形参和实参
function add(x,y){//x,y是形参
return x+y;
}
add(1,2);//1,2是实参
形参可认为是变量声明
function add(){
var x = arguments[0];
var y = arguments[1];
return x+y;
}
add(2,3);//5
arguments
arguments对象是所有(非箭头)函数中都可用的局部变量。
arguments对象不是一个 Array,它是一个(伪)类数组,没有数组的方法。
在函数体内可以通过arguments对象来访问这个参数数组,从而获取传递给参数的每一个参数arguments[i]。
使用arguments对象的length属性可以获知有多少个参数传递给了函数。
typeof arguments; // "undefined"
function howManyArgs() {
console.log(arguments.length);
}
howManyArgs('string',45); // 2,传递了2个参数
使用Array.from()方法将参数转换为真实数组
var args = Array.from(arguments);
//
var args = Array.from('arr')
console.log(args);//["a", "r", "r"]
//
function f() {
return Array.from(arguments);
}
f(1, 2, 3);//[1, 2, 3]
重载
ECMAScript没有重载
- 在其他语言中,可以为一个函数编写两个定义,只要两个定义的签名(接受的参数的类型和数量)不同就可以。
- 但是在ECMAScript中函数没有签名,其参数是包含零个或多个值的数组来组成的。它所谓的命名参数只是提供便利,但不是必需的。没有函数签名,就做到无法重载。
- 如果ECMAScript定义了两个名字相同的函数,则该名字属于后定义的函数。
声明提前
变量声明前置
示例:
console.log(a);
var a = 1;
console.log(a);
结果为undefined 1。
JavaScript 仅提升声明,而不提升初始化
上面示例可理解为:
var a;
console.log(a);
a = 1;
console.log(a);
函数声明前置
执行代码前会先读取函数声明,即函数声明不必放在调用的前面,它可以放在当前作用域的任何位置。
示例:
fn();
function fn() {
return('hello');
}
function声明会在代码执行之前就创建、初始化、赋值(都提升)
理解:
- 找到function函数中所有被声明的变量,在环境中创建变量。
- 将这些变量初始化并赋值。
- 开始执行代码fn()。
比较
变量声明和函数声明都会提升,函数声明提升的优先级高于变量声明提升
关于let、var和function:
- let 的「创建」过程被提升了,但是初始化没有提升。
- var 的「创建」和「初始化」都被提升了。
- function 的「创建」「初始化」和「赋值」都被提升了。
示例: 注意区分函数声明和函数表达式。
fn()
sum(3,4)
var fn = function(){
console.log('fn...')
}//函数表达式
function sum(a,b){
return a + b;
}
可理解为:
function sum(a,b){
return a + b;
}
var fn
fn()
sum(3,4)
fn = function(){
console.log('fn...')
}
function fn(){
console.log(2)
}
var fn = function(){
console.log(3)
}
fn()// 3
可理解为
function fn(){
console.log(2)
}
var fn
fn = function(){
console.log(3)
}
fn()// 3
// 出现命名冲突,就近原则函数表达式的值为最后结果
立即执行的函数表达式
IIFE也称自执行匿名函数,表达式的变量不能从外部访问。
(function () {
statements
})();
声明一个匿名函数,马上调用这个匿名函数。
作用:创建一个独立的作用域,避免了外界访问此 IIFE 中的变量,而且又不会污染全局作用域。
命名冲突
就近原则
function fn(fn){
console.log(fn);
var fn = 3;
console.log(fn);
}
fn(10) //10 3
可以写为
function fn(){
var fn = arguments[0];
console.log(fn);
var fn = 3;
console.log(fn);
}
fn(10) //10 3
递归
自己调用自己
需要设置结束条件。
function f(n){
if(n === 1){
return 1
}
return n * f(n-1)
}
f(3) //6
尾递归
即最后一步调用另外一个函数
function f(n,total){
if(n === 1){
return total;
}
return f(n-1,n*total)
}
作用域
JavaScript作用域是靠函数来形成的,也就是一个函数内定义的变量,函数外不可以访问。
作用域链
当代码在一个环境中执行时,会创建变量对象的一个作用域链。
作用域链的用途:是保证对执行环境有权访问的所有变量和函数的有序访问。
- 作用域的前端始终是当前执行代码所在环境的变量对象。
- 全局执行环境的变量对象是作用域链中的最后一个对象。
步骤:
- 函数在执行的过程中,先从自己内部找变量。
- 如果找不到,再从创建当前函数所在的作用域去找,以此往上。
- 注意找的是变量的当前状态。
实例
var color = "blue";
function changeColor(){
var anotherColor = "red";
function swapColors(){
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
// 这里可以访问color、anotherColor、tempColor
}
// 这里可以访问color、anotherColor,但不能访问tempColor
swapColors();
}
// 这里只能访问color
changeColor();
作用域链图
var a = 1;
function fn1(){
function fn3(){
function fn2(){
console.log(a)
}
fn2();
var a = 4;
}
var a = 2;
return fn3;
}
var fn = fn1();
fn()
//输出 undefined
可理解为
var a = 1;
function fn1(){
var a;
function fn3(){
var a;
function fn2(){
console.log(a)
}
fn2();
a = 4;
}
a = 2;
return fn3;
}
var fn = fn1();
fn() //undefined
解析: 在本例中先执行var a = 1,var fn = fn1(),进入function fn1(),执行return fn3,进入function fn3(),由于变量声明前置,先执行var a,在执行fn2(),进入function fn2(),执行console.log(a),在该作用域内没有a,前往上一级作用域寻找a,找到 var a,未赋值,所以结果为undefined。
示例2
var a = 1
function fn1(){
function fn3(){
var a = 4
fn2()
}
var a = 2
return fn3
}
function fn2(){
console.log(a)
}
var fn = fn1()
fn()
//输出1
可理解为
var a = 1
function fn1(){
var a
function fn3(){
var a = 4
fn2()
}
a = 2
return fn3
}
function fn2(){
console.log(a)
}
var fn = fn1()
fn() //1
示例3
var a = 1
function fn1(){
function fn3(){
function fn2(){
console.log(a)
}
fn2()
var a = 4
}
var a = 2
return fn3
}
var fn = fn1()
fn()
//输出 undefined
可理解为
var a = 1
function fn1(){
var a
function fn3(){
var a
function fn2(){
console.log(a)
}
fn2()
a = 4
}
a = 2
return fn3
}
var fn = fn1()
fn()
数据类型详解
基本类型
保存在栈内存中的简单数据段。
包括:number、string、Boolean、null、undefined
引用类型
保存在堆内存中的是可能由多个值构成的对象。
变量中保存的实际上是一个指针,这个指针指向的位置保存的是对象。
包括:Object、Array、Function、正则
基本类型和引用类型比较
不同
存储位置
- 基本类型保存在栈内存中
- 引用类型保存在堆内存中
访问方式
- 基本类型是按值访问的
- 引用类型是按引用访问的
值能否改变
- 基本类型的值是不可变的,互相不影响
变量会被赋予新值,但是原值不可改变。
var a = 'hello'
a.toUpperCase() //HELLO
console.log(a) // hello
// a的值未改变。
var a = 5
a = 10
console.log(a) // 10
//结果是10是因为原值被替换了但是没有被改变。
var num1 = 10
var num2 = num1
num1 = 100
console.log(num1) // 100
console.log(num2) // 10
//将num1的值赋给num2,再改变num1的值,num1和num2仍然独立的个体,num2不受num1影响。
- 引用类型的值可变的,且互相影响
var a = {num:1}
var b = a
b.num = 2
console.log(a.num) //2
console.log(a == b) //true
//变量a和b实际上是指针都指向同一个对象,当为b改变num属性的值时,可以通过a访问到,因为引用的时同一对象。
值能否添加属性和方法
- 基本类型的值不可以添加属性和方法
var name = 'Jane'
name.age = 18
cosnole.log(name.age) // undefined
- 引用类型的值可以添加、改变、删除其属性和方法
var person = new Object()
person.name = 'John'
console.log(person.name) // John
person.name = 'Ann'
console.log(person.name) // Ann
比较
- 基本类型的比较是值的比较
var a = 1
var b = true
console.log(a == b) //true
- 引用类型的比较是引用的比较
引用类型比较的是指向堆内存的地址是否相同。
var a = {}
var b = {}
console.log(a == b) // false
var a = {num:1}
var b = a
b.num = 2
console.log(a.num) //2
console.log(a == b) // true
检测数据类型
- 检测基本类型typeOf操作符
typeOf检测null和对象都会返回Object,检测函数时会返回function。
- 检测引用类型instanceOf操作符
instanceOf检测基本类型会始终返回false。
相同
ECMAScript中所有函数的参数都是按值传递的
function add(n){
n += 10
return n
}
var count = 100
var result = add(count)
console.log(count) // 100
console.log(result) // 110
function setName(obj){
obj.name = 'Nicholas'
obj = new Object()//这里obj引用的地址改变了
obj.name = 'Jane'
}
var person = new Object()
setName(person)
console.log(person.name) // Nicholas
//创建一个对象并将其保存在变量person中,这个变量被传递到setName()中复制给了obj。在函数内部obj和person引用的是同一个对象。
//若引用类型的参数是按引用传递的,obj.name修改成jane时,会影响person.name的值,但是结果并没有,所以是按值传递的