JaveScript(函数篇 I)
\(update : 2022.1.20\)
学习原文
怎么说 , 鸽了好多天 , 要坚持.
跟 \(c\) 的函数差不多 , 但很多细节不一样.
\(① : 函数的定义与调用\)
function abs(x){
if(x>=0){
return x;
}else{
return -x;
}
}
这里 function 指明了是一个函数定义 ; abs 是函数名 ; x 代表函数参量 ;
同时作者又指出了函数在 JavaScript 中仅仅是一个对象 , 函数名 abs 可以是指向该函数的变量
所以函数又有了如下定义
var abs = function(x){
if(x >= 0){
return x;
}else {
return -x;
}
}; // 要注意这里有一个分号 , 因为是赋值.
二者等价 , 直接调用即可. //例如 abs(-3) ;
注意函数一遇到 return 就会返回结果 , 一个函数没有 return 的话 , 返回的就是 undefined .
接下来就是传参的问题
" 由于JavaScript允许传入任意个参数而不影响调用,因此传入的参数比定义的参数多也没有问题,虽然函数内部并不需要这些参数 "
拿上文的 abs 函数举例
abs(3,'blabla') // 结果为3
abs(-1,'fdas',232) //结果为1
那么如果参数少了呢?
那么要看这个参数在函数里面又没有用到 , 有用到的话函数自动返回 NaN , 否则就没有影响.
下面讲到了 arguments : "JavaScript还有一个免费赠送的关键字arguments,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments类似Array但它不是一个Array"
注意传入的所有参数 , 有下列的例子.
var abs = function(){
if(arguments.length == 0)
return 0;
var x = arguments[0];
return x>=0?x:-x;
};
abs() // 0
abs(3) // 3
abs(-3) // 3
注意这里函数连传入的参数定义都没有 , 但你只要用函数传入了参数 , 就可以用arguments调用出来.
接下来 rest 参数的介绍
原文举了一个很鲜明的例子来介绍了 rest 的作用
function foo(a, b) {
var i, rest = [];
if (arguments.length > 2) {
for (i = 2; i
\(② :变量的作用域与解构赋值\)
这里先讲到 var 的申请的变量实际上有作用域
例如在函数内申请(要与后文在循环内申请区分) , 那么作用域就在函数的内部
function foo(){
var t = 100;
}
t = 10 //ReferenceError! 无法在函数体外引用变量t.
依据以上的结论 , 那么我们在两个函数中的同名变量就不会冲突.
同时在 JavaScript 中函数是允许嵌套的 , 那么就有以下情况
function foo(){
var x = 10;
function boo(){
var x = 'ABC';
console.log(x);
}
boo(); // 输出ABC
console.log(x);// 输出10
}
foo();
这里跟 c 就一致的 , 内部的函数变量会覆盖外部函数的同名变量 , 等内部的变量空间释放之后 , 外部的同名变量恢复.
利用这个结论 , 内部引用外部的变量则不会错 , 反之则会报错.
function foo(){
var x = 10;
function boo(){
var y = x+10;
console.log(y);// 20
}
var z = y+10; // 报错
}
foo();
在 c 中感觉这样嵌套函数是有些奇怪的
function boo(){
var x = 'ABC';
console.log(x);
}
function foo(){
var x = 10;
boo();
console.log(x);
}
这样是可以的 , 甚至可以将 boo 定义到 foo 的后面也行 (这在 c 中是不允许的 , 后面会讲原因.)
接下来作者谈到了 " 变量提升 ".
JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部 ( 注意这里是 函数 的特点 )
'use strict'
function foo(){
var x = 'H' + y;
console.log(x);
var y = 'i';
}
foo();
这里虽然运行了 strict 模式 , 但在 var x = 'H' + y 时并没有报错 , 原因就是上面说的函数特点 , 先扫描了函数的整体 , 申明的变量都提升到了函数的顶部 , 但我们会发现输出是 Hundefied , 这是因为提升了变量 'y' 的申明 , 但不会提升 'y' 的赋值.
所以作者给出了一个忠告 , 在函数内部首先声明所有的变量.
function foo(){
var x = 1 , y = x+1 , z,i;
}
接下来作者提到了 " 全局作用域 " 的概念.
不在任何函数内定义的变量就具有全局作用域. 实际上,JavaScript默认有一个全局对象 window,全局作用域的变量实际上被绑定到window的一个属性
var course = 'Learn JavaScript';
alert(course); // 'Learn JavaScript'
alert(window.course); // 'Learn JavaScript'
之前讲过函数的第二种定义方式 var foo = function(){}; , 其实这里也暗示了函数就是一个全局变量 , 会绑定在 window 的对象中.
function foo() {
alert('foo');
}
foo(); // 直接调用foo()
window.foo(); // 通过window.foo()调用
甚至我们之前调用的 alert() 函数也是 window 的一个变量.
这里作者给出了一个总结 : 这说明JavaScript实际上只有一个全局作用域。任何变量(函数也视为变量),如果没有在当前函数作用域中找到,就会继续往上查找,最后如果在全局作用域中也没有找到,则报ReferenceError错误.
这里我觉得要注意一下 , 只要全局变量才会绑定到window的对象 , 一个函数中变量为局部变量 , 并不会绑定到 window
function boo(){
var x = 10;
}
window.x; //undefined
依据这个结论 , 我们也可以解释了为什么前文说 boo 定义到 foo 的后面也行.
update : 2022.1.22
讨论一下 , 在函数外部用 var 定义的变量为全局变量 , 又说全局变量在不同的JaveScript文件中冲突.
为了减少上面说的冲突 , 把自己的所有变量与函数绑定到一个全局变量当中.
// 唯一的全局变量MYAPP:
var MYAPP = {};
// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;
// 其他函数:
MYAPP.foo = function () {
return 'foo';
};
作者接下来提到了 局部作用域
他提及在 for 循环等语句块中无法定义具有局部作用域的变量
注意是在 for 循环当中 , 不是在函数内部.
function boo(){
for(var i=1;i<=100;++i){
var x = 10;
}
var y = x+10;
console.log(y);//20 , 所以证明 x 并没有只作用在for语句块内.
}
boo();
关键字 let 的出现解决了这个问题
function foo() {
var sum = 0;
for (let i=0; i<100; i++) {
sum += i;
}
// SyntaxError:
i += 1;
}
update : 2022.1.22 竟然没报错.
这里 let 代替 var 申请的是一个块级作用域.
同时关键字 const 用来定义常量
const PI = 3.14;
PI = 3; // 某些浏览器不报错,但是无效果!
PI; // 3.14
习惯上常量默认大写.
接下来提到了结构赋值
也就是给多个变量赋值 ,
给上几个例子
var [x, y, z] = ['hello', 'JavaScript', 'ES6'];
let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
x; // 'hello'
y; // 'JavaScript'
z; // 'ES6'
这是针对有嵌套的情况.
let [, , z] = ['hello', 'JavaScript', 'ES6']; // 忽略前两个元素,只对z赋值第三个元素
z; // 'ES6'
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678',
school: 'No.4 middle school'
};
var {name, age, passport} = person;
// name, age, passport分别被赋值为对应属性:
//小明 20 G-12345678
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678',
school: 'No.4 middle school',
address: {
city: 'Beijing',
street: 'No.1 Road',
zipcode: '100001'
}
};
var {name, address: {city, zip}} = person;
name; // '小明'
city; // 'Beijing'
zip; // undefined, 因为属性名是zipcode而不是zip
// 注意: address不是变量,而是为了让city和zip获得嵌套的address对象的属性:
address; // Uncaught ReferenceError: address is not defined
对嵌套的对象进行赋值 , 只要保证对应的参差一致.
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678',
school: 'No.4 middle school'
};
// 把passport属性赋值给变量id:
let {name, passport:id} = person;
name; // '小明'
id; // 'G-12345678'
// 注意: passport不是变量,而是为了让变量id获得passport属性:
passport; // Uncaught ReferenceError: passport is not defined
当变量名和属性名不一样.
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678'
};
// 如果person对象没有single属性,默认赋值为true:
var {name, single=true} = person;
name; // '小明'
single; // true
var x,y;
({x,y} = {name:'小明',x:100,y:200});
console.log(x,y);//100,200
变量已经声明了的情况
手玩了几种情况
var {x,y} = {x:100,z:200};
console.log(x,y); //100 , undefined
var x,y;
({x,y} = {x:100,x:'小明',y:200})
console.log(x,y) //小明 , 200.
使用场景
var x=1, y=2;
[x, y] = [y, x];
console.log(x,y);
交换变量.
var {hostname:domain, pathname:path} = location;
console.log(domain,path);
快速获取当前页面的域名和路径:
function buildDate({year, month, day, hour=0, minute=0, second=0}) {
return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second);
}
函数传入的的变量是对象 , 创建一个新的对象 Date , 且只用传year , month , day 三个值.
\(③ : 方法\)
在一个对象中绑定函数 , 称为这个对象的方法.
先给出一个例子
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var y = new Date().getFullYear();
return y - this.birth;
}
};
xiaoming.age; // function xiaoming.age()
xiaoming.age(); // 今年调用是32,明年调用就变成33了
注意第一个返回值是将函数整体的语句返回 , 而第二个返回值则是函数运行之后的返回值.
new Date().getFullYear() // 返回今年年份.
这里出现了一个新的关键字 "this" .
在一个方法的内部 (这个前提很重要) , this 是一个特殊的变量 , 它始终指向当前的对象 .
接下来有几个特殊的例子
//注意这里可以将函数拆开写
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
xiaoming.age(); // 25, 正常结果
getAge(); // NaN
这里我们主要注意 NaN 的情况 , 这里我们单独调用了getAge函数 , 而不是用xiaoming对象中的方法 , 而单独的函数内部调用了 this , 这个 this 指向的是全局对象 .
同样有一个例子
var fn = xiaoming.age; // 先拿到xiaoming的age函数
fn(); // NaN
这里照样是 NaN , 要保证 this 指向正确,必须用obj.xxx()的形式调用!(obj 即对象的意思)
不过在 strict 模式下返回的就不是 NaN .
它会让 this 指向 undefined .
又有一个例子 :
'use strict';
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
function getAgeFromBirth() {
var y = new Date().getFullYear();
return y - this.birth;
}
return getAgeFromBirth();
}
};
xiaoming.age(); // Uncaught TypeError: Cannot read property 'birth' of undefined
这种操作叫做方法重构
原因是在 age 方法内的 this 才指向对象 xiaoming , 但你函数内部又定义个函数 , 这时候的指针又指向 undefined 了 .
这时候利用这个特征 , 我们提出解决方法
'use strict';
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var that = this; // 在方法内部一开始就捕获this
function getAgeFromBirth() {
var y = new Date().getFullYear();
return y - that.birth; // 用that而不是this
}
return getAgeFromBirth();
}
};
xiaoming.age(); // 25
你看直接在方法内部存一下 this 就行了.
接下来作者引入了 apply , 它可以指定函数的 this 指向哪个对象 , 这样就不一定在 方法 里面才去用 this.
举个例子
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
xiaoming.age(); // 25
getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空
要注意它和函数绑定使用 , 且它接受两个参数 , 第一个是绑定 this 的变量 , 第二个是 Array , 里面装着函数的参数.
另一个与apply()类似的方法是call(),唯一区别是:
apply()把参数打包成Array再传入;
call()把参数按顺序传入.
Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5
因为一般的函数调用不用 this (无对象) , 把 this
绑定 null 即可.
装饰器 (为什么这样子叫我也不知道)
利用 apply() , 我们可以动态改变函数
提出问题 :我们想统计一下代码一共调用了多少次parseInt()
//parseInt()是本身就有的一个函数 , 可解析一个字符串 , 并返回一个整数.
'use strict';
var count = 0;
var oldParseInt = parseInt; // 保存原函数
window.parseInt = function () {
count += 1;
return oldParseInt.apply(null, arguments); // 调用原函数
};
// 测试:
parseInt('10');
parseInt('20');
parseInt('30');
console.log('count = ' + count); // 3
但我没搞懂为什么一定要用apply.