1 函数(Function)的基本理解
函数就是在程序设计中,将一段代码封装起来,完成一个特定的功能,并给这段代码起一个名称,程序通过名称就可以执行这段代码。函数也是一个对象,也具有普通对象的功能,函数中可以封装一些代码,在需要的时候可以去调用函数来执行这些代码,使用typeof检查一个函数时会返回function。
1.1 函数的声明
// 函数的声明
function func(name, age, sex) {
console.log(name, age, sex);
}
// 函数表达式
var func_ = function (name, age, sex) {
console.log(name, age, sex);
}
JavaScript定义函数有两种方式:一种方式是显示定义函数;一种方式是匿名定义函数。显示定义函数通过函数声明来定义,定义语法如下:
- 显示定义函数
function functionname(parameters){函数代码}
其中,function是定义函数的关键字,functionname是函数名称,parameters是函数传入的参数,可以传入多个参数,每个参数之间用英文逗号分隔。JS函数不需要声明返回类型,当函数需要返回值时,直接使用return语句返回即可。
- 匿名函数
通过一个表达式来定义,函数没有名称,定义的函数会赋值给一个变量,该变量指向函数的内存地址,当访问变量时,函数即被调用。
varfun = function(parameters){函数代码;}
其中,varfun是变量,用于存储函数的内存地址,function是定义函数的关键字,parameters是传入函数的参数,可以传入多个参数,每个参数之间用英文逗号分隔。
1.2 函数的调用
JS有多种方式来调用函数,有直接调用,表达式内调用,在事件响应中调用,通过链接调用。不管哪种方式调用函数,调用时都要写入函数名称,若是匿名函数,要写入变量名称。
- 直接调用就是函数调用语句占单独一行,直接调用比较适合没有返回值的函数。
<script type="text/javascript">
function add() {
num1 = parseInt(document.getElementById("op1").value);
num2 = parseInt(document.getElementById("op2").value);
alert(sum(num1, num2));
}
function sum(a, b) {
return a + b;
}
add();
</script>
语句add()就是直接调用,该语句调用函数add()。
- 函数可以在表达式内调用,函数的返回值参与表达式的计算。在表达式内调用函数,一般适用于有返回值的函数。
function sum(a,b){return a+b;}
sum函数计算a和b两数的和,并使用return语句返回计算结果。调用sum函数就需要在表达式内调用。
value = sum(10,90)
value = sum(10,90) * 2
- 立即执行函数-函数定义完,立即被调用,立即执行函数往往只会执行一次
(function (name, age) {
console.log(name, age)
})('zhaoshuai-la', 27);
-
调用函数时JS解析器不会检查实参的类型和个数,可以传递任意数据类型的值:
如果实参的数量大于形参,多余实参将不会赋值;
如果实参的数量小于形参,则没有对应实参的形参将会赋值undefined; -
返回值,就是函数执行的结果:
语法:return 值;
该值就会成为函数的返回值,可以通过一个变量来接收返回值,
return后边的代码都不会执行,一旦执行到return语句时,函数将会立刻退出。
return后可以跟任意类型的值,可以是基本数据类型,也可以是一个对象。
如果return后不跟值,或者是不写return则函数默认返回undefined。
break、continue和return
break-退出循环
continue-跳过当次循环
return-退出函数 -
参数,函数的实参也可以是任意的数据类型。
2 js函数中的 arguments
简单来说:
-
arguments是一个类数组元素,它用来封装函数执行过程中的实参
-
所以即使不定义形参,也可以通过arguments来使用实参
-
arguments中有一个属性callee表示当前执行的函数对象
-
arguments 对象是所有(非箭头)函数中都可用的局部变量。你可以使用arguments 对象在函数中引用函数的参数。此对象包含传递给函数的每个参数,第一个参数在索引0处。例如,如果一个函数传递了三个参数,你可以以如下方式引用他们:
arguments[0]
arguments[1]
arguments[2]
参数也可以被设置:
arguments[0] = 'value';
arguments 是一个对象,不是一个 Array 。它类似于Array ,但除了length属性和索引元素之外没有任何Array 属性。例如,它没有 pop 方法。但是它可以被转换为一个真正的Array :
// 由于arguments不是 Array,所以无法使用 Array 的方法,所以通过这种方法转换为数组
var args = [].slice.call(arguments); // 方式一
var args = Array.prototype.slice.call(arguments); // 方式二
// 下面是 es6 提供的语法
let args = Array.from(arguments) // 方式一
let args = [...arguments]; // 方式二
-
arguments上的属性
arguments.callee:指向当前执行的函数(在 严格模式 下,第5版 ECMAScript (ES5) 禁止使用 arguments.callee() )
argunments.length:指向传递给当前函数的参数数量 -
arguments与剩余参数、默认参数和解构赋值参数的结合使用
在严格模式下,剩余参数、默认参数和解构赋值参数的存在不会改变 arguments对象的行为,但是在非严格模式下就有所不同了:
function func(a) {
arguments[0] = 99; // 更新了arguments[0] 同样更新了a
console.log(a);
}
func(10); // 99
// 并且
function func(a) {
a = 99; // 更新了a 同样更新了arguments[0]
console.log(arguments[0]);
}
func(10); // 99
当非严格模式中的函数没有包含剩余参数、默认参数和解构赋值,那么arguments对象中的值会跟踪参数的值(反之亦然)。看下面的代码:
function func(a = 55) {
arguments[0] = 99; // updating arguments[0] does not also update a
console.log(a);
}
func(10); // 10
//
function func(a = 55) {
a = 99; // updating a does not also update arguments[0]
console.log(arguments[0]);
}
func(10); // 10
function func(a = 55) {
console.log(arguments[0]);
}
func(); // undefined
3 如何在 JavaScript 中使用 apply,call,bind - 作用改变this的指向
要检查函数是否为一个 Function 对象,我们可以使用以下代码进行判断,该代码段返回 true。
(function () { }).constructor === Function ? console.log(true) : console.log(false);
- apply() 方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。
function.apply(this,[argumentsArray])
- thisArg 可选的。在 func 函数运行时使用的 this值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。
- argsArray 可选的。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。
- apply实现继承
function SuperType() {
this.color = ['red', 'blue', 'yellow']
}
function SubType() {
SuperType.apply(this);
}
var instance = new SubType();
instance.color.push('green');
console.log('instance1.color:', instance.color);
// instance1.color: [ 'red', 'blue', 'yellow', 'green' ]
- 用 apply 将数组添加到另一个数组
var array = ['a', 'b'];
var elements = [1, 2, 3];
array.push(elements);
console.log(array); // ['a', 'b', [1, 2, 3]]
以上代码中,当我们将一个数组 push 进另一个数组时,整个数组被视为一个元素直接 push 进去。但如果我们想要将数组 elemennts 中的元素分别 push 进数组 array 中呢?当然有很多方法可以这样做,在这里我们使用 apply()。
var array = ['a', 'b'];
var elements = [1, 2, 3];
[].push.apply(array, elements);
console.log(array); // ["a", "b", 1, 2, 3]
该例中,使用 apply 连接给定数组,参数为:数组 elements,this 指向变量 array,实现数组 elements 中的元素被 push 进 this 指向的对象(array)中。最终返回的结果为第二个数组中的每个元素被 push 进 this 指向的数组中。
- 元素的最大值
var numbers = [53, 65, 25, 37, 78];
console.log(Math.max(numbers)); //NaN
JS 中 max 函数用于查找给定元素的最大值。但正如我们所见,如果给定值为数组,返回结果为 NaN。当然,JavaScript 中有很多方法可以解决,在这里我们使用 apply。
var numbers = [53, 65, 25, 37, 78];
console.log(Math.max.apply(null, numbers)); //78
当我们使用 apply 调用 Math.max() 时,得到了期望结果。apply 将 numbers 中所有值作为单独的参数,然后再调用 max 进行处理,最终返回数组中的最大值。
值得注意的是,我们使用 null 代替了 this。由于提供的参数是数字数组,即使使用了 this,它也仍会指向同一个数组,最终得到相同的结果。因此,这种情况下我们可以省略 this,改用 null 代替。也就是说,apply 函数中的 this 参数是一个可选参数。
- call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
function.call(thisArg,arg1,arg2,...)
- thisArg: 在 fun 函数运行时指定的 this 值。
if(thisArg == undefined|null) this = window,if(thisArg == number|boolean|string) this == new Number()|new Boolean()| new String()
- arg1, arg2, …:指定的参数列表。
- 使用 call 方法调用父构造函数实现继承
function Father(name, age) {
this.name = name;
this.age = age;
this.book = ['数据结构', '计算机网络', '算法']
console.log('1.执行到Father---名字:' + this.name + '; 年龄:' + this.age + ' 书籍:' + this.book);
}
function Son(name, age) {
Father.call(this, name, age);
this.subject = '软件专业';
console.log('2.执行到Son---专业:' + this.subject);
}
var son = new Son('gxm', '20');
son.book.push('javaScript');
console.log('3.改变Son.book:' + son.book);
/**
* 1.执行到Father---名字:gxm; 年龄:20 书籍:数据结构,计算机网络,算法
2.执行到Son---专业:软件专业
3.改变Son.book:数据结构,计算机网络,算法,javaScript
*/
在该例子中,子构造函数Sun()通过调用父构造函数Father()的 call 方法来实现继承,此时Sun()函数中拥有了Father()函数中的属性。
- 使用 call 方法调用函数并且指定上下文的 this
function play() {
var reply = [this.player, '打', this.playName, '打了', this.playTimes].join(' ');
console.log(reply);
}
var obj = {
player: 'gxm', playName: '羽毛球', playTimes: '1小时'
};
play.call(obj); //gxm 打 羽毛球 打了 1小时
如果直接运行play()函数,结果就只会是打 打了。但指定了上下文就不一样了,play.call(obj)就是将play()函数的上下文从全局的window指定到obj对象上了。所以运行出来的结果过成为gxm 打 羽毛球 打了 1小时。
function foo() {
return () => {
return () => {
return () => {
console.log('id:', this.id);
};
};
};
}
var f = foo.call({id: 1});
var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
var t3 = f()().call({id: 4}); // id: 1
上面代码之中,只有一个this,就是函数foo的this,所以t1、t2、t3都输出同样的结果。因为所有的内层函数都是箭头函数,都没有自己的this,它们的this其实都是最外层foo函数的this。
- 使用 call 方法调用函数并且不指定第一个参数(argument)节
在下面的例子中,我们调用了 display 方法,但并没有传递它的第一个参数。如果没有传递第一个参数,this 的值将会被绑定为全局对象。
var sData = 'Wisen';
function display() {
console.log('sData value is %s ', this.sData);
}
display.call(); // sData value is Wisen
注意:在严格模式下,this 的值将会是 undefined。
- bind() 方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入bind() 方法的第一个参数作为 this, 第二个以及以后的参数,加上绑定函数运行时本身的参数,按照顺序作为原函数的参数来调用原函数。
function.bind(this,arg1,arg2,arg3,...)
bind 方法与 call 方法类似,主要区别在于 bind 返回一个新函数,而 call 不返回。根据 ECMAScript 5 规范,bind 方法返回的函数是一种特殊类型的函数对象,称为绑定函数( BF )。BF 中包含原始函数对象,调用 BF 时会执行该函数。
var x = 9;
var module = {
x: 81,
getX: function () {
return this.x;
}
};
console.log(module.getX()); // 81
var retrieveX = module.getX;
console.log(retrieveX()); // 9
var boundGetX = retrieveX.bind(module);
console.log(boundGetX()); // 81
在上面的代码中,我们定义了一个变量 x 和一个对象 module,该对象中还定义了一个属性 x 以及一个返回 x 值的函数。
当调用函数 getX 时,它返回的是对象内定义的 x 的值,而不是全局作用域中的 x。
另一个变量在全局作用域中声明,并调用 module对象中的 getX 函数。但由于该变量处于全局作用域下,因此 getX 中的 this 指向全局作用域下的 x,返回 9。
最后又定义了另一个变量 boundGetX,该变量调用函数 retrieveX,与之前不同的是,这次将函数 retrieveX与对象 module 绑定,返回的是对象内 x 的值。这是由于 bind 将函数中的 this 指向对象中的 x 值而不是全局 x,因此输出 81。
4 this是函数的上下文对象
- 任何函数本质上都是通过某个对象来调用的,如果没有直接指定就是window
- 所有函数内部都有一个变量this
- 它的值是调用函数的当前对象
<script>
function Person(color) {
console.log(this)
this.color = color;
this.getColor = function () {
console.log(this)
return this.color;
};
this.setColor = function (color) {
console.log(this)
this.color = color;
};
}
Person("red"); //this是谁? window
const p = new Person("yello"); //this是谁? p
p.getColor(); //this是谁? p
const obj = {};
//调用call会改变this指向-->让我的p函数成为`obj`的临时方法进行调用
p.setColor.call(obj, "black"); //this是谁? obj
const test = p.setColor;
test(); //this是谁? window -->因为直接调用了
function fun1() {
function fun2() {
console.log(this);
}
fun2(); //this是谁? window
}
fun1();//调用fun1
</script>
5 箭头函数中的this指向问题
- 引出问题1:
radius = 200
let circle = {
radius: 10,
outer: function () {
console.log(this) // ===> { radius: 10, outer: [Function: outer] }
let inner = function () {
console.log(this) // ===> window
console.log(2 * this.radius) // 400
}
inner()
}
}
circle.outer()
radius = 200
let circle = {
radius: 10,
outer: function () {
console.log(this) // ===> { radius: 10, outer: [Function: outer] }
let self = this
let inner = function () {
console.log(self) // ===> { radius: 10, outer: [Function: outer] }
console.log(2 * self.radius)
}
inner() // window 调用 this 为 window
}
}
circle.outer()
- 引出问题2:
let circle = {
radius: 10,
outer: function () {
let inner = function () {
console.log(2 * this.radius) // 20
}
inner = inner.bind(this);
inner()
}
}
circle.outer()
- 引出问题3:
let circle = {
radius: 10,
outer: function () {
let inner = () => {
console.log(this) // ===> { radius: 10, outer: [Function: outer] }
console.log(2 * this.radius) // 20
}
inner()
}
}
circle.outer()
5.1 箭头函数本身不具有this,箭头函数的this指向函数创建时所在作用域空间的this
- 箭头函数本身不具有this,箭头函数的this指向函数创建时所在作用域空间的this
<script>
var b = 'hello';
let obj = {
b: 'HELLO',
foo: () => {
console.log(this) // window
console.log(this.b); // hello 这个是属性,在定义obj时就创建了这个箭头函数,obj对象是在window作用域创建的
}
}
obj.foo()
</script>
- 箭头函数中的this是不能被call apply去改变
<script>
class Foo {
print = () => {
console.log(this) // 生成foo实例时创建, 此时的 this 指向的是 foo 实例
console.log(this.x) // 1
};
constructor() {
this.x = 1
}
}
let foo = new Foo();
foo.print()
foo.print.call({x: 2}) // 箭头函数中的this是不能被call apply去改变
</script>
- 箭头函数中的this = 箭头函数创建时所在作用域空间的this
<script>
function printThis() { // printThis调用时所在作用域空间的this
console.log(this) // [1]
let print = () => console.log(this) // [1]
print()
}
printThis.call([1])
// 箭头函数中的this = 箭头函数创建时所在作用域空间的this
</script>
6 作用域 一个变量的作用范围
全局作用域:
- 直接在script标签中编写的代码都运行在全局作用域中
- 全局作用域在打开页面时创建,在页面关闭时销毁
- 全局作用域中有一个全局对象window,window对象由浏览器提供,可以在页面中直接使用,它代表的是整个的浏览器的窗口。
- 在全局作用域中创建的变量都会作为window对象的属性保存
- 在全局作用域中创建的变量和函数可以在页面的任意位置访问
- 在函数作用域中也可以访问到全局作用域的变量
- 尽量不要在全局中创建变量
函数作用域:
- 函数作用域是函数执行时创建的作用域,每次调用函数都会创建一个新的函数作用域
- 函数作用域在函数执行时创建,在函数执行结束时销毁
- 在函数作用域中创建的变量,不能在全局中访问
- 当在函数作用域中使用一个变量时,它会先在自身作用域中寻找,
如果找到了则直接使用,如果没有找到则到上一级作用域中寻找,
如果找到了则使用,找不到则继续向上找 ,
7 变量的声明提前
- 在全局作用域中,使用var关键字声明的变量会在所有的代码执行之前被声明,但是不会赋值,所以我们可以在变量声明前使用变量。但是不使用var关键字声明的变量不会被声明提前。
- 在函数作用域中,也具有该特性,使用var关键字声明的变量会在函数所有的代码执行前被声明.
- 如果没有使用var关键字声明变量,则变量会变成全局变量
<script>
function fun() {
a = 100
}
fun()
console.log(a) // 100
</script>
8 函数的声明提前
- 在全局作用域中,使用函数声明创建的函数 function fun(){}, 会在所有的代码执行之前被创建,也就是我们可以在函数声明前去调用函数,
- 但是使用函数表达式 var fun = function(){} 创建的函数没有该特性
- 在函数作用域中,使用函数声明创建的函数,会在所有的函数中的代码执行之前就被创建好了。
9 构造函数是专门用来创建对象的函数
- 一个构造函数我们也可以称为一个类
- 通过一个构造函数创建的对象,我们称该对象是这个构造函数的实例
- 通过同一个构造函数创建的对象,我们称为类对象
- 构造函数就是一个普通的函数,只是他的调用方式不同,如果直接调用,它就是一个普通函数,如果使用new来调用,则它就是一个构造函数
<script>
function Person(name, age, gender) {
console.log('this - ', this)
this.name = name;
this.age = age;
this.gender = gender;
this.sayHi = () => {
console.log(this)
console.log('hello-zhaoshuai-lc')
}
}
var person = new Person('zhaoshuai-lc', 22, 'male');
person.sayHi() // person
</script>
构造函数的执行流程:
- 创建一个新的对象
- 将新的对象作为函数的上下文对象(this)
- 执行函数中的代码
- 将新建的对象返回