JavaScript基础学习——面向对象编程

一、全局作用域

在函数外部定义的变量或函数,叫全局变量或全局函数。它们可以在当前程序的任意位置使用。在全局中定义变量可以用var,也可以直接添加window的属性。

生命周期:它们会一直占用内存,只能在当前文件中使用。如果想在多个文件中使用变量,需要用到cookie或本地存储。

例1 测试全局作用域

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

</body>
<script>
  var x = 100
  function fn() {
    x += 10
    console.log(x);//1010
  }

  function fn1() {
    x *= 10
    fn()
    console.log(x);//1010
  }
  
  fn1()
  console.log(x);//1010

</script>

</html>

二、局部作用域

在函数内部定义的变量或函数,叫局部变量或局部函数。它们只能在函数内部使用。在函数内部用var定义局部变量,如果用window定义的变量,则为全局变量。

生命周期:从调用时开始创建,到函数执行完后立即销毁。

例2 测试局部作用域

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

</body>
<script>
  function fn() {
    var x = 'hello'
    window.y = 'world'
    function show(str) {
      return str + ',hello'
    }
    console.log(show('tom'));//tom,hello
  }
  fn()

  //console.log(x);//报错
  //console.log(show('tom'));//报错
  console.log(y);// 如果在函数内部用window定义变量,该变量为全局变量
</script>

</html>

三、块级作用域
在ES5中没有块级作用域,ES6中有。一对{ }为一个块,块级作用域就是变量或函数只在当前这组{}中有效。

在ES5中可以用IIFE实现块级作用域,ES6中用let实现。

四、IIFE

1、什么是IIFE?

IIFE(Immediately Invokeed Function Expression:立即执行函数表达式),声明函数的同时立即执行调用这个函数。定义的函数没有函数名,只能执行一次,无法在其它地方被调用,也不会造成全局污染。

2、IIFE的作用

可以用于解决运算中产生的全局污染问题(实际上它是一个闭包的写法)。它也是ES5中实现块级作用域的方式。

3、IIFE和特点
(1)将所有运算代码都放在匿名函数中;
(2)函数是一个表达式,没有函数名,只能被执行一次;
(3)执行一次后所产生的数据和变量就立即销毁,不会造成全局污染。

4、IIFE语法

(function(params){
  ...
})(exprs)

将函数的定义和调用写在一起。

5、IIFE的用法(推荐用前面两种写法)

(function(形参列表){
    ...
    window.变量 = 表达式;
})(实参列表);

例3 测试第一种写法 计算1+2+3+...+100

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

</body>
<script>
  (function () {
    for (var i = 0, sum = 0; i <= 100; i++) {
      sum += i
    }
    window.rs=sum
  })()
  console.log(rs);
</script>

</html>

var 变量名 = (function(形参列表){
    ...
    return 表达式;
})(实参列表);

例4 测试第二种写法 计算1+2+3+...+100

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

</body>
<script>
  var rs = (function () {
    for (var i = 0, sum = 0; i <= 100; i++) {
      sum += i
    }
    return sum
  })()
  console.log(rs);
</script>

</html>

!(function(形参列表){
    ...
    return 表达式;
})(实参列表);

+(function(形参列表){
    ...
    return 表达式;
})(实参列表);   

五、闭包

1、什么是闭包?

闭包就是一种作用域的体现,函数外部可以访问函数内部的数据。正常情况下,函数外部不能访问函数内部的变量,但是通过这种特殊的写法,将函数内的子函数暴露在全局上,可以在外部调用且可以访问到函数内部的数据,即可以让全局访问局部的数据。

简单地说:就是在函数外部获取函数内部的数据。

2、闭包的作用

(1)实现函数外部可以访问函数内部的数据;

(2)减少了全局变量的使用,避免全局变量的污染。

3、闭包的特点
(1)闭包一定是函数内嵌套函数;
(2)闭包是一种作用域的体现,函数外可以访问函数内的数据;
(3)闭包采用IIFE写法,由于内部数据被全局所调用,将延缓资源的回收(即闭包中的变量的值用完后不会立即销毁,会驻留内存一段时间)。

4、闭包的缺点
(1)闭包中的变量会占用更多的内存空间;
(2)可能会导致内存泄露。

5、闭包的写法

第1种写法:
      (function(){
        ...
        window.函数名 = function(){
          ...
          return 变量;
        }
      })()

例5 测试第一种写法

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

</body>
<script>
  (function(){
    var i=10;
    window.result=function(){
      ++i
      return i
    }
  })()

  console.log(result());//11
  console.log(result());//12 


</script>

</html>

第2种写法:
      var 变量 = (function(){
        ...
        return function(){
          ...
          return 变量;
        }
      })()

例6 测试第二种写法

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

</body>
<script>

  var rs = (function () {
    var i = 10;
    return function () {
      ++i;
      return i;
    }
  })()
  console.log(rs());//11
  console.log(rs());//12
  console.log(rs());//13

</script>

</html>

六、内存管理

在闭包中调用局部变量,会导致这个局部变量无法及时被销毁,相当于全局变量一样会一直占用着内存。如果需要回收这些变量占用的内存,可以手动将变量设置为null。然而在使用闭包的过程中,比较容易形成 JavaScript 对象和 DOM 对象的循环引用,就有可能造成内存泄露。这是因为浏览器的垃圾回收机制中,如果两个对象之间形成了循环引用,那么它们都无法被回收。

解决方案:
(1)把循环引用中的变量设为 null 即可;
(2)把闭包写法改造成一个引用外部函数写法。

例7 测试内存管理

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div class="box">box</div>
</body>
<script>
  (function () {
    var box = document.getElementsByClassName('box')[0]
    box.onclick = txt
    box = null
  })()

  function txt() {
    console.log(document.getElementsByClassName('box')[0].innerHTML)
  }
</script>

</html>

七、JavaScript对象

对象分为内置对象(构造函数/方法)和用户自定义对象。

1、内置对象:String Date Math RegExp Function Object Array Boolean Number...
var str1 = new String('abc');// 类型为object
var str2 = 'abc'  // 类型为string

2、用户自定义对象

(1)字面量
      var obj1 = {
        name: 'aaa',
        sex: 'male'
      }

(2)用new构建
      var obj2 = new Object({
        name: 'aaa',
        sex: 'male'
      })

(3)构造函数(相当于ES6中类)

      a.创建方法
        function 构造函数名/类名(形参列表){
          // 类的特征-属性
          this.属性名 = 参数;
          ...

          // 类的形为-方法
          this.方法名 = function(){
            ...
          }
          ...
        }
      b.使用构造函数实例化对象
        var 对象名 = new 构造函数名/类名(实参列表)
      c.构造函数特征
        i)构造函数名(类名)首字母要大写(W3C的规定)(要使用大驼峰写法);

          补充:
            小驼峰写法:studentClassicScore
            大驼峰写法:StudentClassicScore
        ii)属性和方法都要挂载到this上;
        iii)没有return语句。
      d.实例化机制
        i)创建一个对象;
        ii)将构造函数中的this指向新对象;
        iii)执行构造函数中的语句,将所有的属性和方法挂载到新的对象上;
        iv)把新的对象的内存地址赋值给变量。
      e.问题
        如果构造函数实例化的对象中的属性和方法的内容一样,因为它们会各自占用独立的空间,会造成内存的浪费。

例8 构造函数

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

</body>
<script>
  //创建构造函数
  function Person(name, sex, age) {
    this.name = name
    this.sex = sex
    this.age = age
    this.classic = 'student'
    this.show = function () {
      console.log('message:' + this.name + ',' + this.sex + ',' + this.age);
    }
    this.play = function () {
      console.log('play()');
    }
  }

  //实例化对象
  var zs = new Person('zs', 'man', 19)
  console.log(zs);
  zs.show()
  zs.play()

  var ls = new Person('ls', 'woman', 19)
  console.log(ls);
  ls.show()
  ls.play()

</script>

</html>

4、原型
      a.什么上原型?
        原型就是prototype,所有function定义的函数都拥有这个属性。
        prototype这个属性是一个对象,也是一个指针,是用来添加所有实例共享属性和方法的。
      b.原型的作用
        i)解决方法过载(为每个对象都单独创建一个方法,浪费了大量的内存资源,这种情况叫方法过载);
        ii)扩展构造函数的属性和方法(功能)。
      c.原型的写法
        i)扩展属性
          构造函数名.prototype.属性名 = 表达式;
        ii)扩展方法
          构造函数名.prototype.方法名 = function(){
            ...
          }
        
        注意:在开发过程中,私有属性和方法放在构造函数中,而共享属性和方法用原型添加。

例9 原型

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

</body>
<script>
  // 创建构造函数
  function Person(name, sex, age) {
    this.name = name;
    this.sex = sex;
    this.age = age;

    this.show = function () {
      console.log('您的信息为:' + this.name + ',' + this.sex + ',' + this.age)
    }
  }

  Person.prototype.classic = { type: 'student' } // 为Person构造函数添加了一个共享属性classic

  Person.prototype.play = function () { // 为Person构造函数添加了一个共享方法play()
    console.log('喜欢打篮球!')
  }

  //实例化对象
  var zs=new Person('zs','man',20)
  console.log(zs);
  zs.play()

  var ls=new Person('ls','woman',20)
  console.log(ls);
  ls.play()

  console.log(zs.classic === ls.classic);//true
  console.log(zs.play === ls.play);//true 说明play()是共用了一段内存空间
</script>

</html>


5、混合模式

混合模式 = 构造函数 + 原型 
在构造函数中定义私有的属性和方法,通过原型添加共享的属性和方法。

例10 混合模式

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

</body>
<script>
  //混合模式
  function CreatePerson(name, age) {
    this.name = name
    this.age = age
  }

  CreatePerson.prototype.showName = function () {
    return ('name is:' + this.name)
  }

  CreatePerson.prototype.showAge = function () {
    return ('age is:' + this.age)
  }

  var per1 = new CreatePerson('zs', 20)
  var per2 = new CreatePerson('ls', 20)

  console.log(per1.showName());
  console.log(per1.showAge());
  console.log(per2.showName());
  console.log(per2.showAge());
</script>

</html>

6、基本模式(JSON语法格式)
      var 对象 = {
        属性:属性值,
        ...,
        方法:function(){...},
        ...
      }

这样做的目的是为了减少全局变量和全局方法的使用,从而避免全局污染。

例11 基本模式

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="box">id</div>
  <div class="box">class</div>
  <p>pppppp</p>
  <p>pppppp</p>
  <p>pppppp</p>
</body>
<script>
  //封装获取DOM
  var $ = {
    id: function (id) {
      return document.getElementById(id)
    },
    cls: function (className) {
      return document.getElementsByClassName(className)
    },
    tag: function (tagName) {
      return document.getElementsByTagName(tagName)
    }
  }

  $.id('box').innerHTML = 'this is id'
  $.cls('box')[0].style.color = 'red'
  $.tag('p')[1].style.cssText = 'color:blue;border:1px solid #eee'
</script>

</html>

7、工厂模式

一般用于大批量创建对象(这批对象有相同的属性和方法)。给定一些值,创建一个结果。

      a.创建方法:
        创建一个函数,在函数中创建对象,最后再返回这个对象。

      b.缺陷
        让不同对象有不同属性或方法,工厂模式就很难实现了。

      c.优点
        减少了重复性代码。

  混合模式(构造函数+原型)在ECMAScript中是使用最广泛、认同度最高的一种创建自定义对象的方法。

例12 工厂模式

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

</body>
<script>
  function fn(name, sex) {
    var obj = new Object()
    obj.name = name
    obj.sex = sex
    obj.show = function () {
      console.log(obj.name);
    }
    return obj
  }

  var s1 = fn('zs', 'man')
  var s2 = fn('ls', 'woman')

  s1.show()
  s2.show()

  console.log(s1 instanceof fn) // false 说明s1不是fn的实例
  console.log(s1 instanceof Object) //true
</script>

</html>

八、this(面试核心点)

this是一个地址指针。

1、全局中的this,指向window对象。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

</body>
<script>
  var x = 100
  console.log(this);//window
  console.log(window.x, this.x, x);//100,100,100
  console.log(window === this);//true
</script>

</html>

2、函数中的this
原则:谁调用就指向谁。一般情况下是指向window对象的。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

</body>
<script>
  var name = 'tom'
  function fn() {
    var name = 'mike'
    console.log(this.name);//tom
    console.log(name);//mike
  }
  fn()
</script>

</html>

如果开启了"use strict;"严格代码格式,this指向的是window。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

</body>
<script>
  'use strict'
  var name = 'tom'
  function fn() {
    var name = 'mike'
    //console.log(this.name);//在严格代码格式下,this是没有指向的
    console.log(this);//undefined
    console.log(name);//mike
  }
  fn()
</script>

</html>

3、对象方法中的this,指向当前对象。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

</body>
<script>
  var obj = {
    id: '001',
    goodsName: 'hello',
    price: 9999,
    salePrice: function () {
      console.log(this.goodsName + ":" + this.price);
      console.log(this);//指向当前对象
    }
  }
  obj.salePrice()
</script>

</html>

4、构造函数中的this,指向当前实例化的对象。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

</body>
<script>
  function Car(name, type, color) {
    this.name = name;
    this.type = type;
    this.color = color;

    this.show = function () {
      console.log(this.name + this.type + this.color);
      console.log(this)
    }
  }
  Car.prototype.price = 190000;
  Car.prototype.run = function (time) {
    console.log(this.name + '百公里加速时间为:' + time + '秒!')
  }

  var car1 = new Car('大众', '家用版', '白色');
  var car2 = new Car('卡宴', '豪华版', '土豪金');

  car1.show()
  car2.show()
  car1.run(15)
  car2.run(5)
</script>

</html>

5、借来的this,所有函数都有三个方法(call、apply和bind),可以用它们实现函数中this指向新的对象(修改函数中this的指向)。

(1)call:主动式将函数中的this指向新的对象,调用一次就立即执行一次(谁调用就指向谁)。

语法:被调用对象的方法.call(当前对象,参数1,参数2,...)

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    var p1 = {
      name: 'zs'
    }
    p1.tel=function(){
      console.log(this.name+" call for ls");
    }

    var p2={
      name:'ww'
    }

    p1.tel.call(p2)// p2借p1的tel方法  this指向了p2这个新的对象

    console.log(p1);
    console.log(p2);
  </script>
</body>

</html>

(2)apply

用法与call一样,只是传参格式不一样。传参必须以伪数组的方式传递,不管有几个参数,都必须以这种格式进行传递。

语法:被调用对象的方法.apply(当前对象,[参数1,参数2,...])

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    var p1 = {
      name: 'zs'
    }
    p1.tel = function (callName, time) {
      console.log(this.name + '给' + callName + "打了" + time + "分钟电话");
    }

    var p2 = {
      name: 'ls'
    }

    // p1.tel.call(p2, 'ww', 30)//传参用列表
    p1.tel.apply(p2, ['ww', 30])//传参用伪数组
  </script>
</body>

</html>

(3)bind(ES5新增)

被动式改变this指向,实现对原对象的拷贝。

语法:回调函数.bind(对象,参数1,参数2,...)

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    var name = '这是全局的name'
    var obj = {}
    obj.name = 'zs'
    obj.show = function (str) {
      console.log(this);
      setTimeout(function (str1, str2) {
        console.log(this.name + str + str1 + str2);
      }.bind(this, 'aaa', 'bbb'), 1000);
    }

    obj.show('is a good people')
  </script>
</body>

</html>

6、call、apply和bind的区别

相同点:
    i)都是函数原型的方法,可以改变函数中this的指向;
    ii)第一个参数都是this要修改的目标对象;
    iii)都可以传递参数。

不同点:
    i)call和apply是主动式的,修改this指向时立即调用1次;
    ii)bind是被动式的,只修改this的指向,不执行;
    iii)call的参数是直接列举在对象的后面,而apply是以伪数组的形式传参;
    iv)bind一般用在回调函数中。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值