JavaScript面向对象基础 - 函数篇
初识函数
- 含有默认值的参数
(function greet(name, say = 'Hi,'){
console.log(say + name);
})('ppo!');
- 剩余参数
(function transferParam(num1, ...theNums){
theNums.unshift(num1);
console.log(theNums);
})(1,2,3,4,5,6);
(function transferParam(...theNums){
console.log(theNums);
})(1,2,3,4,5,6);
- 函数的调用
function getSum(){
var sum = 0;
for(var i in arguments){
sum += arguments[i];
}
return sum;
}
console.log(getSum(1,2,3,4,5,6));
- 垃圾回收机制
- 由于字符串、对象和数组没有固定大小,所有当他们的大小已知时,才能对他们进行动态的存储分配。JavaScript程序每次创建字符串、数组或对象时,解释器都必须分配内存来存储那个实体。只要像这样动态地分配了内存,最终都要释放这些内存以便他们能够被再用,否则,JavaScript的解释器将会消耗完系统中所有可用的内存,造成系统崩溃。
- 若要在开发中保留局部变量的值。可以利用return返回,或者通过全局变量保存。
var num = (function sum(num){
num += 1;
return num;
})(20);
console.log(num);
var money;
(function total(num){
money = num + 1;
})(20);
console.log(money);
- 内存中的函数对象
- 定义时:
- 创建2个对象:函数对象和作用域链对象
- 函数对象:封装了函数的定义,但暂时不读取函数定义
- 作用域链对象:专门保存函数可用的变量所在位置的对象
- 调用时:
- 创建1个新对象:活动对象
- 活动对象:临时封装本次调用函数时使用的局部变量
- 并将活动对象的引用,压入作用域链对象中。
- 调用后:
- 作用域链中的活动对象引用出栈
- 活动对象及其内部的局部变量一同释放!
- 定义时:
匿名函数
- 函数表达式中省略函数名
var fn = function(num1, num2){
return num1 + num2;
}
console.log(fn(1, 2));
- 自调用方式
var res = (function(num1, num2){
return num1 + num2;
})(1, 2);
console.log(res);
- 处理事件
document.onclick = function(){
alert('你好,世界!');
}
- 箭头函数 - 标准语法
((p1,p2,p3) => {
console.log(p1+ p2+ p3);
})(1,2,3);
- 箭头函数 - 返回值
var sum = ((p1,p2,p3) => {
return p1+p2+p3;
})(1,2,3);
console.log(sum);
- 箭头函数 - 一个参数
(x => {
console.log(x);
})("你好,世界!");
- 箭头函数 - 两个参数
((x, y) => {
console.log(x+ y);
})("你好,", "世界!");
- 箭头函数 - 无参函数
(() => {
console.log("你好,");
})();
(_ => {
console.log("世界!");
})();
回调函数
- 案例
function cal(a, b, fn){
return fn(a, b);
}
function add(num1, num2){
return num1+ num2;
}
function mul(num1, num2){
return num1* num2;
}
console.log(cal(45, 5, add));
console.log(cal(45, 5, mul));
- 数组回调函数API
find()
- 返回数组中满足回调函数的第一个元素的值,否则返回undefinedevery()
- 测试数组的所有元素是否都通过了回调函数的测试some()
- 测试数组中的某些元素是否通过由回调函数实现的测试forEach()
- 对数组的每个元素执行一次提供的函数map()
- 创建一个新数组,其结果是该数组中的每个元素都调用一次提供的回调函数后返回的结果reduce()
- 对累加器和数组中的每个元素(从左到右)应用一个函数,将其减少为单个值reduceRight()
- 接收一个函数作为累加器(accumulator)和数组的每个值(从右到左)将其减少为单个值
var arr = ['a', 'b', 'c'];
arr.map((value, index) => {
console.log(value, index);
});
递归函数
- 阶乘案例
function factorial(n){
if(n== 1){
return 1;
}else{
return n* factorial(n- 1);
}
}
var n= parseInt(prompt('请输入阶乘数:'));
if(isNaN(n)){
console.log('n值输入不合法');
}else{
console.log(factorial(n));
}
- 斐波那契数列第N项的值
var recursion= (n)=> {
if(n< 0){
return '输入的数字不能小于0';
}else if(n== 0){
return 0;
}else if(n== 1){
return 1;
}else if(n> 1){
return recursion(n- 1)+ recursion(n- 2);
}
}
console.log(recursion(10));
闭包函数
- 作用域链
- 由于作用域是相对于变量而言的,而如果存在多级作用域,这个变量又来自于哪里?这个问题就需要好好地探究一下了,我们把这个变量的查找过程称之为变量的作用域链
- 作用域链的意义:查找变量(确定变量来自于哪里,变量是否可以访问)
- 简单来说,作用域链可以用以下几句话来概括:(或者说:确定一个变量来自于哪个作用域)
- 查看当前作用域,如果当前作用域声明了这个变量,就确定结果
- 查找当前作用域的上级作用域,也就是当前函数的上级函数,看看上级函数中有没有声明
- 再查找上级函数的上级函数,直到全局作用域为止
- 如果全局作用域中也没有,我们就认为这个变量未声明(xxx is not defined)
var name = 'sunny';
(()=>{
var name = 'storm';
console.log(name);
})();
var name = 'sunny';
(()=>{
console.log(name);
var name = 'storm';
})();
var name = "sunny";
var f1 = ()=> {
return ()=>{
console.log(name);
}
var name="storm";
}
var nameNew = f1();
nameNew();
var name = "sunny";
var f1 = ()=>{
return {
say:()=>{
console.log(name);
var name = "storm";
}
}
}
var nameNew = f1();
console.log(nameNew.say());
- 闭包函数的实现
- 函数执行完毕后,作用域中保留了最新的timer变量的值
- 闭包函数常应用于模块化开发,防止变量被破坏
var count = ()=>{
var timer = 0;
var c = ()=>{
return timer++;
}
return c;
}
var counter = count();
console.log(counter());
console.log(counter());
console.log(counter());
-
闭包的创建
- 先用外层函数封装一个受保护的局部变量
- 再在内层函数中操作外层函数的变量
- 外层函数将内层函数返回到外部,在外部反复调用
-
闭包的判断
- 函数嵌套!
- 内层函数使用外层函数的局部变量
- 内层函数被返回到外部,在外部调用
-
判断闭包输出
- 同一次外层函数调用返回的内层函数,操作同一个变量
- 外层函数调用了几次,就有几个受保护的变量副本
-
闭包的优缺点
- 优点:即要重用变量,又要保护变量不被污染
- 缺点:占用更多内存空间——因为outer的活动对象无法释放
-
函数的四种调用方式
- 在ES6的箭头函数之前的时代,想要判断一个函数内部的this指向谁,就是根据这四种方式来决定的,函数内部的this跟大小写、书写位置无关
- 函数调用
- 方法调用
- 构造函数调用(new)
- 上下文方式调用(call、apply、bind)
// 函数调用,this指向window
var age = 18;
var person = {
age:15,
say:function(){
console.log('我今年' + this.age + '岁!');
}
}
var sunny = person.say;
sunny();
// 方法调用,this指向对象person
var age = 18;
var person={
age:15,
say:function (){
console.log('我今年' + this.age + '岁!');
}
}
person.say();
// 构造函数调用,this将指向构造函数实例本身
var age=18;
var person={
age:15,
say:function(){
console.log('我今年' + this.age + '岁!');
}
}
new person.say();
//上下文调用方式,有3种,call、apply、bind
function f1(){
console.log(this);
}
//call方法的第一个参数决定了函数内部的this的值
f1.call([1,3,5])
f1.call({age:20,height:1000})
f1.call(1)
f1.call("abc")
f1.call(true);
f1.call(null)
f1.call(undefined);
//上述代码可以用apply完全替换
//总结:
//call方法的第一个参数:
//1、如果是一个对象类型,那么函数内部的this指向该对象
//2、如果是undefined、null,那么函数内部的this指向window
//3、如果是数字-->this:对应的Number构造函数的实例
// --> 1 --> new Number(1)
// 如果是字符串-->this:String构造函数的实例
// --> "abc" --> new String("abc")
// 如果是布尔值-->this:Boolean构造函数的实例
// --> false --> new Boolean(false)
- bind()函数
// 普通方法调用
var person = {
age:18,
run : function(){
console.log(this); //this指向person
var _that=this;
setTimeout(function(){
console.log(this.age); //this指向window
console.log(_that.age);
},50);
}
}
person.run();
// 通过执行了bind方法,匿名函数本身并没有执行,只是改变了该函数内部的this的值,指向person
var person = {
age:18,
run : function(){
console.log(this); // this指向person
setTimeout((function(){
console.log(this.age);
}).bind(this),50); // 绑定this指向person
}
}
person.run();
// bind函数基本用法
function speed(){
console.log(this.seconds);
}
speed({ seconds:100 });
// 执行了bind方法之后,产生了一个新函数,
// 这个新函数里面的逻辑和原来还是一样的,唯一的不同是this指向{ seconds:100 }
var speedBind = speed.bind({ seconds:100 });
speedBind(); //100
// bind函数常规写法
(function eat(){
console.log(this.seconds);
}).bind({ seconds:360 })()
// bind函数案例
var obj={
name:"西瓜",
drink:(function(){
//this指向了:{ name:"橙汁" }
console.log(this.name);
}).bind({ name:"橙汁" })
}
obj.drink(); //"橙汁"
var p10={
height:88,
run:function(){
//this
setInterval((function(){
console.log(this.height); //88
}).bind(this),100)
}
}
p10.run();
// 手写bind函数
Function.prototype._bind = target => {
// 这里的this指向函数实例
// target表示新函数的内部的this的值
// 利用闭包创建一个内部函数,返回那个所谓的新函数
return () => {
this.call(target);
}
}
function person(){
console.log(this);
}
person();
var sunny = person.bind({age:18});
sunny();