JavaScript高级第2天:定义函数的三种方式、函数的原型链结构、完整原型链、作用域以及作用域链、函数的四种调用模式、闭包、计数器、斐波那契数列优化、三种继承方式

JavaScript高级第二天

01-定义函数的三种方式

1.函数声明 function:可以先调用再声明,因为预解析(把函数声明、变量声明进行提升)

function fn() {
  //函数体
  conle.log(1);
}

2.函数表达式:不可以先调用再声明,因为预解析只是把变量声明提升了,赋值留在原位。

var fn2 = function() {
  conle.log(2);
}
fn(2);

3.函数也是对象,函数也是通过构造函数new出来的 Function(大写的)(了解,工作当中不会使用到)

语法:

var fn3 = new Function(arg1, arg2, arg3..., bodyFn);

参数:

1.参数的个数是若干个

2.参数的类型都是字符串类型的

3.除了最后一个参数bodyFn外,其他参数都是创建的函数的形参

4.最后一个参数bodyFn是创建的函数的函数体

例:

var fn4 = new Function('n1', 'n2', 'alert(n1 + n2)');

// 等价于以下代码:
// var fn4 = function(n1, n2) {
  // alert(n1 + n2);
// }

02-函数的原型链

function Person(){}
// 底层是通过 Function new 出来的
// var Person = new Function();  //Person是实例对象,是函数

Person的原型链:
Person ==> Function.prototype ==> Object.prototype ==> null

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WaCxbBnW-1594806215236)(file:///D:/%E7%BA%BF%E4%B8%8B67%E6%9C%9F%E5%89%8D%E7%AB%AF/2.JavaScript%E9%AB%98%E7%BA%A7/day01/day01%E7%AC%AC%E4%BA%8C%E9%83%A8%E5%88%86/01-%E6%95%99%E5%AD%A6%E8%B5%84%E6%96%99/%E7%AC%94%E8%AE%B0/preview/images/function.jpg)]

Function.prototype常用成员

  • call:调用函数,重新指向this
  • apply:调用函数,重新指向this
  • bind:重新指向this,返回一个新的函数,不调用。

03-完整版原型链

核心的点:函数的双重身份,函数是函数,也是对象

1.函数作为构造函数来使用去创造对象

function Person(){

}
var p = new Person();

绘制原型三角关系:

  1. 构造函数:Person
  2. 原型对象:Person.prototype
  3. 实例对象:p

2.把函数当实例看,函数也是构造函数 Function 创建

底层就是 var Person = new Function();

绘制原型三角关系:

  1. 构造函数:Function
  2. 原型对象:Function.prototype
  3. 实例对象:Person

3.把Object考虑进来 ==> 当构造函数来使用

var obj = new Object();

绘制原型三角关系:

  1. 构造函数:Object
  2. 原型对象:Object.prototype
  3. 实例对象:obj

4.把Object当成实例对象来使用

任何函数底层都是 Function 创建的,都是Function的实例对象

底层 var Object = new Function();

绘制原型三角关系:

  1. 构造函数:Function
  2. 原型对象:Function.prototype
  3. 实例对象:Object

5.Function 当实例对象来看,谁把 Function 创建出来的

var Function = new Function();

绘制原型三角关系:

  1. 构造函数:Function
  2. 原型对象:Function.prototype
  3. 实例对象:Function

绘制完整版原型链的目的是辅助大家理解js中对象的继承关系:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-keSDbrrA-1594806215238)(file:///D:/%E7%BA%BF%E4%B8%8B67%E6%9C%9F%E5%89%8D%E7%AB%AF/2.JavaScript%E9%AB%98%E7%BA%A7/day01/day01%E7%AC%AC%E4%BA%8C%E9%83%A8%E5%88%86/01-%E6%95%99%E5%AD%A6%E8%B5%84%E6%96%99/%E7%AC%94%E8%AE%B0/preview/images/full.png)]

小结:

  1. 任何对象的原型链上都有Object.prototype

  2. 任何函数的原型链上都有 Function.prototype

  3. 函数是一等公民,

    1. typeof 函数==> function
    2. Function.prototype ==> 原型对象的类型是个函数
    3. Function 自己生自己
  4. 函数的双重身份

    是函数,也是对象

    是函数,有prototype属性

    是对象,有 _ _ proto_ _ 属性

    所以说函数有prototype属性,也有 _ _ proto_ _ 属性

练习题1:(看图就可以完全答出来)

Function.prototype === Function.prototype   true
Object.__proto__ === Function.prototype       true
Function.prototype.__proto__ === Object.prototype  true
Object.prototype.__proto__ === Object.prototype    false
Object.__proto__.__proto__ === Object.prototype    true

04-练习题

function Person() {}
var p = new Person()

console.log(p.constructor)  // Person
console.log(Person.constructor)  // function

05-instanceof

instanceof:实例对象

语法:

对象 instanceof 构造函数

作用:

  • 字面上理解:对象是否是构造函数的实例对象,如果是的,就返回 true
  • 从原型链角度来理解:
    • 构造函数的prototype属性是否在对象的原型链上,如果在 返回true,否则返回false

练习题


06-作用域

  • 变量可以起作用的区域(就是说一个变量声明定义好了,就可以在哪些区域内使用)
  • 在js中,有全局作用域和函数作用域(词法作用域,静态作用域)

函数作用域:当函数声明定义好了,其函数作用域就定下来了,其作用域链也定下来了

作用域链:任何函数可以形成作用域,函数嵌套在另外一个函数中,那么外层函数也有自己的作用域,这样从里到外直到全局作用域,形成的链式结构叫做作用域链。

每一个函数的作用域链:都是从当前函数作用域出发,从里到外形成

07-变量的搜索原则

沿着作用域链来查找

  1. 首先会在当前作用域内查找是否有声明该变量,如果有就返回变量的值
  2. 如果没有,就去外层作用域中查找
  3. 如果外面也没有,就沿着作用域链继续往外找,直到全局作用域,如果有就返回
  4. 如果还没有,报错。(属性值找不到就是undefined,变量找不到就报错)

08-练习题


09-函数的四种调用模式

分析this指向的问题:

  1. 任何函数都有属于自己的this指向。
  2. this指向是动态灵活的,只有当函数调用的时候,才能确定下来this的指向
  3. 如何去分析this指向:
    1. 看这个this属于哪个函数
    2. 看这个函数是如何调用的 ==> 就能决定下来this指向谁

函数的四种调用模式:

第一种:函数调用模式

​ 这种调用模式,函数内的this指向window。

第二种:构造函数调用

​ new + 构造函数():这种调用模式,构造函数的this指向新创建出来的实例对象

第三种:方法调用模式

​ 对象.方法名():这种调用模式,方法内的this指向调用方法的那个对象

​ 谁调用,this指向谁

是点语法和中括号语法,都是方法调用模式

​ 练习题:

function foo() {
  conle.log(this);  //arr
}
var arr = [10, 20, foo];
// 方法调用模式
arr[2]();

第四种:上下文调用模式 ==> call()、apply()、bind() 这三个方法

10-练习题

// 1.
var age = 38;
var obj = {
    age: 18,
    getAge: function () {
        console.log(this.age);
    }
}

var f = obj.getAge;  //仅仅是把getAge方法赋值给了变量f
// 函数调用模式,函数内的this,this指向window
f();//38

// 2.
var age = 38;
var obj = {
  age:18,
  getAge:function () {
    // 方法内的this指向obj
    console.log(this.age);//18
    
    function foo(){
      // 任何函数都有属于自己的this
      // foo内的this和getAge方法内的this不是一个,没有关系
      console.log(this.age);//38
    }
    // 函数调用模式
    foo();
  }
}
// 方法调用模式,
obj.getAge();
obj["getAge"]();

// 3.
var length = 10

function fn() {
    console.log(this.length)  // 10 3
}

var obj = {
    length: 5,
    method: function (fn) {
      // 函数调用模式 fn函数内的this指向window
        fn()  // 10
        // arguments 伪数组:实参列表 ==> 类似于这样[fn, 10, 5]
        
        // 方法调用模式,fn这个方法被arguments调用
        arguments[0]();
    }
}
obj.method(fn, 10, 5);

几种特殊的this指向

  • 定时器中的this指向了window,因为定时器的function最终是由window来调用的。
  • 事件中的this指向的是当前的元素,在事件触发的时候,浏览器让当前元素调用了function

11-上下文调用模式

作用:可以自己去取指定this指向

call()、apply()、bind() 三个方法,任何函数都可以去使用以上三个方法

1. call()方法:可以用来调用函数

1.除了小括号可以调用函数外,还可以使用call()调用函数

演示:

function fn() {
	conle.log(111);
}
fn();
fn.call();

2.call的第一个参数可以用来修改函数内的this指向

function fn(2) {
  conle.log(2);
  conle.log(this);
}
fn2();  // 2 window
fn.call([10, 20, 30]);  // 2 [10, 20, 30]

3.call的参数是若干个

第一个参数 ==> 用来修改this指向

除了第一个参数之外的所有其他参数 ==> 都是用来给函数传递实参的

function fn3(n1, n2) {
  conle.log(this);
  conle.log(n1 + n2);
}
// fn3(10, 20)
fn3.call([], 100, 200);  //

作用:

  1. 调用函数
  2. 第一个参数来改this指向
  3. 给函数传递实参 ==> 除第一个以外的所有参数

12-方法借用(调)模式

上下文调用模式又称方法借用(调)模式

13-伪数组与数组

伪数组与数组最大的区别:伪数组不能使用数组的方法,但可以让伪数组去借用数组的方法

伪数组定义:是个对象,有数字下标还有length属性,所以可以和数组一样进行循环,但是伪数组不能使用数组的方法。

常见的伪数组:

  1. arguments实参列表
  2. querySelectorAll()、getElementByTagName() 获取到的元素
  3. jq对象

实例:

// 需求:给obj伪数组添加 2: '波哥'
var obj = {
  0: '班班',
  1: '大飞哥',
  length: 2
}

// 这是常规做法(第一种方法)
// obj[2] = '波哥';
// 还需要手动维护length
// obj.length++;
// conle.log(obj);

// Array.prototype.push:是从数组中的原型对象中去拿到push

// [].push ==> 从空数组上去获取到push方法

// 第二种:
// 数组的原型对象:
// Array.prototype.push.call(obj, '波哥');  //效果等价于:obj.push('波哥')

// 第三种:
;[].push.call(obj, '波哥', '啦啦', 'kuqi');

conle.log(obj);

注意点:

  1. []。push 简化写法,一定要注意: {} 和 [] 一定要使用 ; 分号隔开,否则语法报错

  2. 数组的push方法不仅有添加功能,内部还会自动的去维护length属性

14-伪数组借用数组的方法

var obj = {
  0: '班班',
  1: '大飞哥',
  2: '哥哥'
  length: 3
};
把obj伪数组里面的每一项拼接成字符串 '班班-大飞哥-哥哥'
var str = [].join.call(obj, '-');
console.log(str);

15-apply方法

call方法和apply方法作用是一样的,写法上有区别

一样的参数:

  1. 可以调用函数
  2. 可以修改this指向
  3. 可以给函数传递实参

apply的语法

apply(thisArg, [实参列表])
  1. thisArg用来修改函数内的this指向
  2. 实参列表:是个数组或者伪数组,里面的就是传递给函数的所有实参

演示:

function fn() {
  console.log(1);
}
fn();
fn.call();
fn.apply();


function fn2() {
  console.log(2);
  console.log(this);
}
fn2.call({name: 'call'});
fn2.apply({name: 'apply'});


function fn3(n1, n2) {
  console.log(this);
  console.log(n1 + n2);
  consloe.log(arguments);
}
fn3.call([], 1, 2);
fn3.apply([10, 20], [30, 50]);

apply的平铺性:apply会把第二个参数里面的每一项取出来作为实参给函数传递过去。
闭包

闭包的基本概念

闭包(closure)是JavaScript语言的一个难点,也是JavaScript的一个特色,很多高级的应用都要依靠闭包来实现。

闭包的概念

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数

在JavaScript中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则产生闭包。

产生闭包的条件

当内部函数访问了外部函数的变量的时候,就会形成闭包。

闭包的作用

保护私有变量不被修改

计数器

需求:统计一个函数的调用次数

    var count = 0;
    function fn(){
      count++;
      console.log("我被调用了,调用次数是"+count);
    }
    fn();
    fn();
    fn();

缺点:count是全局变量,不安全。

使用闭包解决这个问题!!!!

    function outer(){
      var count = 0; // 私有变量, 将count保护起来了
      function add(){
        count++;
        console.log("当前count"+count);
      }
      return add;
    }
    
    var result = outer();
    result();


斐波那契数列优化

缓存(cache):数据的缓冲区,当要读取数据时,先从缓冲中获取数据,如果找到了,直接获取,如果找不到,重新去请求数据。

计算斐波那契数列,会有很大的性能问题,因为重复的计算了很多次,因此我们可以使用缓存来解决这个性能问题。

初级优化:

使用缓存的基本步骤:

  • 如果要获取数据,先查询缓存,如果有就直接使用
  • 如果没有,就进行计算,并且将计算后的结果放到缓存中,方便下次使用。
    //缓存
    var arr = [];
    var fbi = function (n) {
      count++;
      if (n == 1 || n == 2) {
        return 1;
      }
      if (arr[n]) {
        return arr[n];
      } else {
        var temp = fbi(n - 1) + fbi(n - 2);
        arr[n] = temp;//存入缓存
        return temp;
      }
    }

缺点:既然使用缓存,就需要保证缓存的数据的安全,不能被别人修改,因此,需要使用闭包来实现缓存的私有化。

    function outer() {
      //缓存
      var arr = [];
    
      var fbi = function (n) {
        if (n == 1 || n == 2) {
          return 1;
        }
        if (arr[n]) {
          return arr[n];
        } else {
          var temp = fbi(n - 1) + fbi(n - 2);
          arr[n] = temp;//存入缓存
          return temp;
        }
      }
      return fbi;
    }
    var fbi = outer();
    console.log(fbi(40));

闭包经典面试题

打印下标

继承

现实生活中的继承,子承父业, 如儿子继承了父辈的财产,公司等

程序中的继承,一个对象可以使用另一个对象中的方法或属性

继承的目的: 方便代码的复用

    var lw = {
        skill: "翻墙",
    	age: 28
    }
    
    function Person(){}
    var xm = new Person();
    
    // 如何实现让xm可以使用到lw对象上的skill属性???
    xm.skill; // ==> 翻墙

JS常见的几种继承模式

原型链继承

一个对象可以访问构造函数的原型中的属性和方法,那么如果想要让一个对象增加某些属性和方法,只需要把这些属性和方法放到原型对象中即可。这样就实现了继承, 称之为原型链继承

  • 直接给原型增加属性和方法
  • 原型替换(注意:constructor)

借用构造函数继承

从构造函数中继承到构造函数中的成员

    function Person(name, age, gender){
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
    
    function Chinese(name, age, gender, skin){
        this.name = name;
        this.age = age;
        this.gender = gender;
        // 以上代码重复,在构造函数Person中已经给this添加
        this.skin = skin;
    }
    
    // 重写构造函数Chinese
    function Chinese(name, age, gender, skin){
        // 借用Person构造函数,继承到name、age、gender属性
        Person.call(this, name, age, gender);
        this.skin = skin;
    }

组合继承

借用构造函数 + 原型链继承组合在一起使用

    function Person(name, age, gender){
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
    
    // Person原型上的sayHi方法
    Person.prototype.sayHi = function(){
        console.log("hello, 我是" + this.name);
    }
    
    function Chinese(name, age, gender, skin){
        // 借用Person构造函数,继承到name、age、gender属性
        Person.call(this, name, age, gender);
        this.skin = skin;
    }
    
    var xm = new Chinese("xm", 20, "male", "黄色");
    // xm有name/age/gender属性从Person构造函数中继承到的
    
    // 那么如何让Chinese的实例对象xm去继承到Person原型上的sayHi方法???
    xm.sayHi();
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值