函数语法
重复代码:让程序难以维护
函数主要用于减少重复代码
创建(定义、声明)函数
function 函数名(){
// 函数体
}
函数体的代码不会直接运行,必须要手动调用函数,才能运行其中的代码。
调用函数
运行函数体
函数名();
函数提升
通过字面量声明的函数,会提升到脚本块的顶部。
通过字面量声明的函数,会成为全局对象的属性。
其他特点
通过typeof 函数名,得到的结果是"function"
函数内部声明的变量:
- 如果不使用var声明,和全局变量一致,表示给全局对象添加属性
- 如果使用var声明,变量提升到所在函数的顶部,函数外部不可以使用该变量
函数中声明的变量,仅能在函数中使用,在外部无效
参数
参数表示函数运行的未知条件,需要调用者告知的数据
// 参数的有效返回在函数体中
function 函数名(形参1, 形参2, ...){
}
函数名(实参)
如果实参没有传递,则对应的形参为undefined
返回值
函数运行后,得到的结果,调用函数时,调用表达式的值就是函数的返回值
return 会直接结束整个函数的运行
return 后面如果不跟任何数据,返回undefined
如果函数中没有书写return,则该函数会在末尾自动return undefined。
文档注释
/**
*
*
*/
作用域和闭包
作用域
作用域表示一个代码区域,也表示一个运行环境
JS中,有两种作用域:
- 全局作用域
直接在脚本中书写的代码
在全局作用域中声明的变量,会被提升到脚本块的顶部,并且会成为全局对象的属性。
- 函数作用域
函数中的代码
在函数作用域中声明的变量,会被提升到函数的顶部,并且不会成为全局对象的属性.
因此,函数中声明的变量不会导致全局对象的污染
尽量的把功能封装在函数中
但是,当函数成为一个表达式时,它既不会提升,也不会污染全局对象。
将函数变为一个函数表达式的方式之一,将函数用小括号括起来。
然而,这样一来,函数无法通过名称调用。
如果书写一个函数表达式,然后将立即调用,该函数称之为立即执行函数 IIFE(Imdiately Invoked Function Expression)。
由于大部分情况下,函数表达式的函数名没有实际意义,因此,可以省略函数名。
没有名字的函数,称之为匿名函数
思考下面的代码为什么不能正常调用?
<script>
var sayHello = function test6(){
alert("今天是周六");
};
test6();
</script>
原因是上面的一句话,函数无法通过函数表达式。
创建函数表达式的方式目前看来有两种
- var a = function 函数名();
- var a = function();
- 立即调用函数表达式 (IIFE)
(function() {
// 函数体
})();
IIFE是一种立即执行的匿名函数表达式,它在声明后立即执行。这有助于创建私有作用域,防止变量污染全局作用域。
4.箭头函数
var myFunction = () => {
// 函数体
};
为什么这里末尾会有个分号?
你可能想知道,为什么函数表达式结尾有一个分号 ;,而函数声明没有:
function sayHi() {
// ...
}
let sayHi = function() {
// ...
};
答案很简单:
- 在代码块的结尾不需要加分号
;,像if { ... },for { },function f { }等语法结构后面都不用加。 - 函数表达式是在语句内部的:
let sayHi = ...;,作为一个值。它不是代码块而是一个赋值语句。不管值是什么,都建议在语句末尾添加分号;。所以这里的分号与函数表达式本身没有任何关系,它只是用于终止语句。
函数表达式 vs 函数声明
函数声明:在主代码流中声明为单独的语句的函数。
// 函数声明
function sum(a, b) {
return a + b;
}
函数表达式:在一个表达式中或另一个语法结构中创建的函数。下面这个函数是在赋值表达式 = 右侧创建的:
// 函数表达式
let sum = function(a, b) {
return a + b;
};
更细微的差别是,JavaScript 引擎会在 什么时候 创建函数。 函数表达式是在代码执行到达时被创建,并且仅从那一刻起可用。 一旦代码执行到赋值表达式 let sum = function… 的右侧,此时就会开始创建该函数,并且可以从现在开始使用(分配,调用等)。 函数声明则不同。 在函数声明被定义之前,它就可以被调用。 例如,一个全局函数声明对整个脚本来说都是可见的,无论它被写在这个脚本的哪个位置。 这是内部算法的原故。当 JavaScript 准备 运行脚本时,首先会在脚本中寻找全局函数声明,并创建这些函数。我们可以将其视为“初始化阶段”。 在处理完所有函数声明后,代码才被执行。所以运行时能够使用这些函数。 例如下面的代码会正常工作:
sayHi("John"); // Hello, John
function sayHi(name) {
alert( `Hello, ${name}` );
}
函数声明 sayHi 是在 JavaScript 准备运行脚本时被创建的,在这个脚本的任何位置都可见。
……如果它是一个函数表达式,它就不会工作:
sayHi("John"); // error!
let sayHi = function(name) { // (*) no magic any more
alert( `Hello, ${name}` );
};
函数表达式在代码执行到它时才会被创建。只会发生在 (*) 行。为时已晚。 函数声明的另外一个特殊的功能是它们的块级作用域。 严格模式下,当一个函数声明在一个代码块内时,它在该代码块内的任何位置都是可见的。但在代码块外不可见。 例如,想象一下我们需要依赖于在代码运行过程中获得的变量 age 声明一个函数 welcome()。并且我们计划在之后的某个时间使用它。 如果我们使用函数声明,以下则代码不能如愿工作:
let age = prompt("What is your age?", 18);
// 有条件地声明一个函数
if (age < 18) {
function welcome() {
alert("Hello!");
}
} else {
function welcome() {
alert("Greetings!");
}
}
// ……稍后使用
welcome(); // Error: welcome is not defined
这是因为函数声明只在它所在的代码块中可见。
下面是另一个例子:
let age = 16; // 拿 16 作为例子
if (age < 18) {
welcome(); // \ (运行)
// |
function welcome() { // |
alert("Hello!"); // | 函数声明在声明它的代码块内任意位置都可用
} // |
// |
welcome(); // / (运行)
} else {
function welcome() {
alert("Greetings!");
}
}
// 在这里,我们在花括号外部调用函数,我们看不到它们内部的函数声明。
welcome(); // Error: welcome is not defined
我们怎么才能让 welcome 在 if 外可见呢?
正确的做法是使用函数表达式,并将 welcome 赋值给在 if 外声明的变量,并具有正确的可见性。
下面的代码可以如愿运行:
let age = prompt("What is your age?", 18);
let welcome;
if (age < 18) {
welcome = function() {
alert("Hello!");
};
} else {
welcome = function() {
alert("Greetings!");
};
}
welcome(); // 现在可以了
什么时候选择函数声明与函数表达式? 根据经验,当我们需要声明一个函数时,首先考虑函数声明语法。它能够为组织代码提供更多的灵活性。因为我们可以在声明这些函数之前调用这些函数。 这对代码可读性也更好,因为在代码中查找 function f(…) {…} 比 let f = function(…) {…} 更容易。函数声明更“醒目”。 ……但是,如果由于某种原因而导致函数声明不适合我们(我们刚刚看过上面的例子),那么应该使用函数表达式。
总结
函数是值。它们可以在代码的任何地方被分配,复制或声明。 如果函数在主代码流中被声明为单独的语句,则称为“函数声明”。 如果该函数是作为表达式的一部分创建的,则称其“函数表达式”。 在执行代码块之前,内部算法会先处理函数声明。所以函数声明在其被声明的代码块内的任何位置都是可见的。 函数表达式在执行流程到达时创建。
在大多数情况下,当我们需要声明一个函数时,最好使用函数声明,因为函数在被声明之前也是可见的。这使我们在代码组织方面更具灵活性,通常也会使得代码可读性更高。 所以,仅当函数声明不适合对应的任务时,才应使用函数表达式。在本章中,我们已经看到了几个例子,以后还会看到更多的例子。
作用域中可以使用的变量
全局作用域只能使用全局作用域中声明的变量(包括函数)
函数作用域不仅能使用自身作用域中声明的变量(包括函数),还能使用外部环境的变量(包括函数)
有的时候,某个函数比较复杂,在编写的过程,可能需要另外一些函数来辅助它完成一些功能,而这些函数仅仅会被该函数使用,不会在其他位置使用,则可以将这些函数声明到该函数的内部。
函数内部声明的变量和外部冲突时,使用内部的。
闭包
闭包(closure),是一种现象,内部函数,可以使用外部函数环境中的变量。 此处需要看的文章
闭包的产生的条件: 1.外部函数和内部函数,外包函数里面声明变量,内部函数里面使用这个变量。
最典型的应用:
function outer() {
let a = 1;
function f(){
a++;
console.log(a);
}
return f;
}
var method = outer();
method();
闭包可以简单的理解为上面的函数f和变量a,函数f访问变量a就叫闭包。 闭包右两个重要的特点: 1.可以访问函数的内部变量。一般来说只有全局的变量才能在外部进行访问。定义在函数内部的变量,正常是不能访问的到,可以通过闭包去访问到。
2.让这些变量始终保持在内存中,而不是说当前函数调用完,变量就没了。有的时候我们需要在外部(函数外部)去调这些函数,而函数调用完之后的结果我们希望能保存起来,第一种方法我们其实可以在函数外部声明一个全局变量。而这里的闭包虽然是个局部变量,也可以达到同样的效果,而且避免了对全局对象的变量污染。 上面的方法每次执行完都会将a的值存取来。
值得提醒的是这里不论是否有没有return f;这句都是闭包。
如何在对象中定义属性和方法?
<script>
var obj = {
a: "asfd",
b: 234,
c: function() {
console.log("ccccc");
},
d: function() {
console.log("dddd");
}
myMethod() {
console.log("eee");
}
};
// console.log(obj.c, typeof obj.c);
obj.text = "这是一段测试文字";
console.log(obj.text);
obj["abc"] = "这是第二段测试文字";
console.log(obj.abc);
console.log(obj["abc"]);
var e = "ddd";
obj[e] = "haha";
console.log(obj.ddd);//可以正常访问
console.log(obj.e);//不能正常访问
var b = obj.c;
b();
obj.d();
</script>
下面先讲解属性的定义的 属性的定义首先可以分为两种: 1.在对象内部定义 内部定义就是 属性名 :属性值,这里注意两点,第一属性名前面不需要加 var,这里是定义属性而不是定义变量 第二 定义的属性的之间用 ,号隔开。
2.在对象外部定义,比方说上面的obj.text,在oc中一般要先定义,JS语言比较灵活,可以直接使用,js语言帮忙定义。
此外除了还可以使用上面的["属性名"],访问可以使用 对象.属性名 或者 对象["属性名"] 来访问。
3.还可以用变量名作为属性名
继续看对象里面方法的定义
a.最常规的方式是在对象里面使用下面的方式来定义
c: function() {
console.log("ccccc");
},
b.ES6及以后版本中,你可以使用更短的语法,可以使用下面的语法来定义
myMethod() {
console.log("eee");
}
c.使用箭头函数,写法如下
myMethod: () => { // 方法体 }
函数表达式和this
函数表达式
JS中,函数也是一个数据,语法上,函数可以用于任何需要数据的地方
JS中,函数是一等公民
函数是一个引用类型,将其赋值给某个变量时,变量中保存的是函数的地址
this关键字
this无法被赋值
- 在全局作用域中,this关键字固定指向全局对象。(就是script 最外层的作用域)
- 在函数作用域中,取决于函数是如何被调用的
- 函数直接调用,this指向全局对象
- 通过一个对象的属性调用,格式为
对象.属性()或对象["属性"](),this指向对象 - 构造函数,后面通过 new 构造函数创建对象,构造函数里面的this指向生成的新对象。
按照目前自己的理解,this就分两种情况,函数里面和对象中,函数中就指向的是window,通过对象里面是指向当前所在的对象。
下面这句是最核心的。
哪个对象调用函数,函数里面的 this 指向哪个对象。 this只有在调用到的时候才能确定指向,不能在定义的位置和时间确定
this指向示例1
<script>
function test(){
console.log(this);
}
var b = test;
//直接调用函数,this 指向全局对象。
b();
//在函数外,this指向全局对象。
console.log(this);
</script>
this指向示例2
function test(){
console.log(this);
}
var b = test;
var arr = [b, test];
arr[0]();
上面this指向arr对象。如果直接test() 调用,this指向window.
this指向示例3
var obj = {
a: function() {
console.log(this);
},
b: {
x: 2342,
func: function() {
console.log(this);
}
}
};
obj.a(); this 指向obj
var b = obj.a;
b(); this 指向window
obj.b.func(); this 指向b
说到底,一切的关键是谁在调用,this就指向谁。
构造函数
对象中的属性,如果是一个函数,也称该属性为对象的方法
普通创建对象的方法
// 创建一个用户
function createUser(name, age, gender) {
return {
// 下面是语法糖简写形式,真正应该写的话是 name:name,age:age,gender:gender,
name,
age,
gender,
sayHello() {
console.log(`我叫${this.name},年龄${this.age}岁,性别${this.gender}`);
}
};
}
var u1 = createUser("张三", 18, "男");
u1.sayHello();
上面的代码需要注意一个问题就是当创建的对象的属性 name 和传入的形参的名字一致的时候,对象中可以将 name = name, 简写成 name,.里面的 sayHello() 也是正确的写法。
用于创建对象的函数
用函数创建对象,可以减少繁琐的对象创建流程
- 函数返回一个对象
- 构造函数:构造函数专门用于创建对象
new 函数名(参数);
如果使用上面的格式创建对象,则该函数叫做构造函数。
- 函数名使用大驼峰命名法
- 构造函数内部,会自动创建一个新对象,this指向新创建的对象,并且自动返回新对象
- 构造函数中如果出现返回值,如果返回的是原始类型,则直接忽略;如果返回的是引用类型,则使用返回的结果
- 所有的对象,最终都是通过构造函数创建的
下面是实际的一个例子
// 创建一个用户
function User(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
this.sayHello = function() {
console.log(`我叫${this.name},年龄${this.age}岁,性别${this.gender}`);
}
}
var u1 = new User("张三", 18, "男");
u1.sayHello();
需要注意的是,如果上面对象的创建不是通过 new User("张三", 18, "男"); 来创建的,比方说是通过User("张三", 18, "男");来创建,先注意的一点是这里创建不了,如果想要正常创建的话,上面的对象,需要 return 一个对象出来,由于这里是构造函数,自动帮忙return一个对象出来了。
对于Array来说,
// arr = [3, 5, 7, 2]; // 语法糖,这里的写法其实相当于上面的写法。
对于Object来说,
//相当于
// var obj = new Object();
// obj.name = "asdf";
// obj.age = 234;
// obj.gender = "男";
// var obj = {
// name: "asdf",
// age: 234,
// gender: "男"
// }; 语法糖,这里的写法其实相当于上面的写法。
new.target
该表达式在函数中使用,返回的是当前的构造函数,但是,如果该函数不是通过new调用的,则返回undefined
通常用于判断某个函数是否是通过new在调用。
上面的知识可以有一个用处,可以做到用户是否使用 new User 都返回一个对象出来。写法可以参照下面
// 创建一个用户
function User(name, age, gender) {
var temp = function() {
console.log(`我叫${this.name},年龄${this.age}岁,性别${this.gender}`);
};
if (new.target === User) {
//正常的构造函数调用
this.name = name;
this.age = age;
this.gender = gender;
this.sayHello = temp;
} else {
return {
name,
age,
gender,
sayHello: temp
}
}
}
var u1 = new User("ss", 18, "女");
函数的本质
函数的本质就是对象。
某些教程中,将构造函数称之为构造器 所有的对象都是通过关键字new出来的,
new 构造函数()
所有的函数,都是通过new Function创建。
举一个例子
// function sum(a, b) {
// return a + b;
// }
下面的写法其实就相当于上面的写法。
var sum = new Function("a", "b", "return a+b");
console.log(typeof sum);
console.log(sum(3, 5));
Function
由于函数本身就是对象,因此函数中,可以拥有各种属性。 也可以动态的添加各种方法和属性。
function User(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.fullName = this.firstName + " " + this.lastName;
}
User.abc = 123;
User.test = function(){
console.log("test")
}
User.test();
console.log(User.abc);
包装类
JS为了增强原始类型的功能,为boolean、string、number分别创建了一个构造函数:
- Boolean
- String
- Number
如果语法上,将原始类型当作对象使用时(一般是在使用属性时),JS会自动在该位置利用对应的构造函数,创建对象来访问原始类型的属性。
类:在JS中,可以认为,类就是构造函数
成员属性(方法)、实例属性(方法):表示该属性是通过构造函数创建的对象调用的。 静态属性(方法)、类属性(方法):表示该属性是通过构造函数本身调用的。