JavaScript高级

 

 

1.面向过程与面向对象

 1.1面向过程
  面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了。
  面向过程是 实现代码逻辑的 步骤,主要是把重心点放在实现功能上,不太去考虑封装
  优点: 效率高,但是维护起来太麻烦  
  1.2面向对象
  面向对象是把事务分解成一个个对象,然后由对象之间分工合作。
  主要的重心点,先放在功能模块的划分,然后想着什么功能模块可以去进行复用(可以进行抽取)
  优点: 会让我们功能具备模块化开发,一个模块就负责一个功能,后续方便我们的维护
  三大特性:继承性、封装性、多态性(抽象性)
 

2.对象与类

  2.1 对象 
  对象是由属性和方法组成的:是一个无序键值对的集合,指的是一个具体的事物
  属性:事物的特征,在对象中用属性来表示(常用名词)
  方法:事物的行为,在对象中用方法来表示(常用动词)
  创建对象的两种方法
    1.字面量创建对象    
      var ldh = {
      name: '刘德华',
      age: 18
      }
      console.log(ldh);
 
 
    2.构造函数创建对象
      function Star(uname,age){
        this.uname = name;
        this.age = age;
      }
      var ldh = new Star('刘德华',18); //实例化对象
      console.log(ldh);
 2.2 类 
  在 ES6 中新增加了类的概念,可以使用 class 关键字声明一个类,之后以这个类来实
  例化对象。类抽象了对象的公共部分,它泛指某一大类(class)对象特指某一个,
  通过类实例化一个具体的对象
   
  2.2.1创建类
     语法:  
       //步骤1 使用class关键字
    class name {
    // class body
    }
    //步骤2使用定义的类创建实例 注意new关键字
    var xx = new name();
  2.2.2类创建添加属性和方法  
    // 1. 创建类 class 创建一个类
    class Star {
      // 类的共有属性放到 constructor 里面 constructor是 构造器或者构造函数
      constructor(uname, age) {
      this.uname = uname;
      this.age = age;
      }//‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐>注意,方法与方法之间不需要添加逗号
      sing(song) {
      console.log(this.uname + '唱' + song);
      }
    }
  // 2. 利用类创建对象 new 实例化
  var ldh = new Star('刘德华', 18);
  console.log(ldh);   // Star {uname: "刘德华", age: 18}
  ldh.sing('冰雨');     // 刘德华唱冰雨
 
  注意点:
    1. 通过class 关键字创建类, 类名我们还是习惯性定义首字母大写
    2. 类里面有个constructor 函数,可以接受传递过来的参数,同时返回实例对象
    3. constructor 函数 只要 new 生成实例时,就会自动调用这个函数, 如果我们不写这个函数,类也会自动生成这个函数
    4. 多个函数方法之间不需要添加逗号分隔
    5. 生成实例 new 不能省略
    6. 语法规范, 创建类 类名后面不要加小括号,生成实例 类名后面加小括号, 构造函数不需要加function
 
 2.2.3 类的继承
  语法 :   class Father { }; // 父类
      class Son extends Father { } // 子类继承父类 extends
  示例 :   
      class Father {
        constructor(surname) {
        this.surname= surname;
        }
        say() {
          console.log('你的姓是' + this.surname);
        }
      }
      class Son extends Father{     // 这样子类就继承了父类的属性和方法
      }
      var damao= new Son('刘');
      damao.say(); // 输出结果是   你的姓是刘
  
  子类使用super关键字访问父类的方法
   
    //定义了父类
    class Father {
      constructor(x, y) {
      this.x = x;
      this.y = y;
      }
      sum() {
        console.log(this.x + this.y);
      }
    }
    //子元素继承父类
    class Son extends Father {
      constructor(x, y) {
        super(x, y); //使用super调用了父类中的构造函数
      }
    }
    var son = new Son(1, 2);
    son.sum();     //结果为3
 
  注意:
    1. 继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的
    2. 继承中,如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则)
    3. 如果子类想要继承父类的方法,同时在自己内部扩展自己的方法,利用super 调用父类的构造函数,super 必须在子类this之前调用
 
 2.2.4 时刻注意this的指向问题
    我们在类中,去使用属性或者是调用方法千万不要忘记了用 this,
    需要去关注的就是this的指向问题:
        默认情况下,类中的this都指向的是当前实例化的对象,
        除非 绑定事件之后this指向的就是触发事件的事件源
 

3.构造函数和原型

  3.1 静态成员和实例成员

    实例成员 :实例成员就是构造函数内部通过this添加的成员,实例成员只能通过实例化的对象来访问

     以下代码里的 uname  age sing就是实例成员

function Star(uname, age) {
     this.uname = uname; 
     this.age = age; 
     this.sing = function() { 
            console.log('我会唱歌'); 
      } 
}
var ldh = new Star('刘德华', 18); 
console.log(ldh.uname);    //实例成员只能通过实例化的对象来访问    

 

     静态成员 :在构造函数本身上添加的成员 静态成员只能通过构造函数来访问    在ES5中只要被static修饰的属性和方法都是静态成员

     下列代码中 sex 就是静态成员

   

function Star(uname, age) { 
         this.uname = uname; 
         this.age = age; 
         this.sing = function() { 
               console.log('我会唱歌'); 
         } 
}
Star.sex = '男'; 
var ldh = new Star('刘德华', 18);
 console.log(Star.sex);          //静态成员只能通过构造函数来访问

  3.2构造函数的问题

    存在浪费内存的问题

    

function Star(uname , age) {
      this.uname = uname;
      this.age = age;        
      this.sing = function(){
             console.log('我会唱歌')
      }
}
var ldh = new Star("刘德华",18);
var shuji= new Star("书记",22);
ldh.sing();
shuji.sing();

   以上代码在调用sing方法时都会在内存开辟一个新的空间,如果有多个对象来调用sing方法就会占用过多内存,所以我们就用到了构造函数的原型 prototype。

  3.3 构造函数原型prototype

    构造函数通过原型分配的函数是所有对象所共享的。

     JavaScript 规定,每一个构造函数都有一个prototype 属性,指向另一个对象。注意这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。

     我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。

     构造函数.prototype就可以获取原型对象

function Star(uname, age) { 
         this.uname = uname; 
         this.age = age;
 }
Star.prototype.sing = function() {
         console.log('我会唱歌'); 
}
var ldh = new Star('刘德华', 18); 
var zxy = new Star('张学友', 19); 
ldh.sing();  //我会唱歌 
zxy.sing();  //我会唱歌

 

   3.4 对象原型

    实例化对象都会有一个__proto__属性指向构造函数的原型对象,而我们的实例化对象可以使用对象原型的属性和方法就是因为有__proto__属性的存在

    实例化对象.__proto__ ===  构造函数.prototype

    它们的三角关系如下图:

      

    __proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象prototype

   3.5 constructor构造函数

     对象原型( __proto__)和构造函数(prototype)原型对象里面都有一个属性constructor 属性

     constructor 我们称为构造函数,因为它指回构造函数本身。

     constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。

    

    如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
function Star(uname, age) {
  this.uname = uname;
  this.age = age;
}
// 很多情况下,我们需要手动的利用constructor 这个属性指回 原来的构造函数
Star.prototype = {
// 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
  constructor: Star, // 手动设置指回原来的构造函数
  sing: function() {
    console.log('我会唱歌');
  },
  movie: function() {
    console.log('我会演电影');
  }
}
var zxy = new Star('张学友', 19);
console.log(zxy)

   3.6 原型链和查找机制

    每一个实例对象又有一个proto属性,指向的构造函数的原型对象,构造函数的原型对象也是一个对象,也有proto属性,这样一层一层往上找就形成了原型链。

    

  

     查找机制:

      当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性,如果自身有,就有自身的属性和方法。

      如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)。

      如果还没有就查找原型对象的原型(Object的原型对象)。依此类推一直找到 Object 为止(null)。

      如果找到尽头(null)都没找到,是属性则返回undefined,是方法则报错

   

  3.7 原型对象中this的指向

    构造函数中的this和原型对象的this,都指向我们new出来的实例对象

   3.8 通过原型对象为数组扩展内置方法

    

var arr = [3,6,9,8,5]
arr.prototype.sum = function() { 
        var sum = 0; 
        for (var i = 0; i < this.length; i++) {
                  sum += this[i]; 
        }
        return sum; 
};              //此时数组对象中已经存在sum()方法了 可以用于数组.sum()进行数据的求和

  

4.继承

  4.1 call()

    call()可以调用函数   可以修改this的指向,使用call()的时候,()里的第一个参数是修改的this指向,参数2 参数3 使用逗号隔开

    

function fn(x, y) {
     console.log(this); 
     console.log(x + y); 
} 
var o = { 
    name: 'andy' 
};
fn.call(o, 1, 2); //调用了函数此时的this指向了对象o, 

    4.2 构造函数继承属性   

    1. 先定义一个父构造函数
    2. 再定义一个子构造函数
    3. 子构造函数继承父构造函数的属性(使用call方法)
// 1. 父构造函数 
function Father(uname, age) { 
// this 指向父构造函数的对象实例
      this.uname = uname; 
      this.age = age; 
}
// 2 .子构造函数 
function Son(uname, age, score) {
     // this 指向子构造函数的对象实例 
//3.使用call方式实现子继承父的属性 
      Father.call(this, uname, age);
      this.score = score; 
}
var son = new Son('刘德华', 18, 100); 
console.log(son);

   

   4.3 借用原型对象继承方法

    (1). 先定义一个父构造函数
    (2). 再定义一个子构造函数
    (3). 子构造函数继承父构造函数的属性(使用call方法)
// 1. 父构造函数 
function Father(uname, age) { 
// this 指向父构造函数的对象实例 
  this.uname = uname; 
  this.age = age; 
}
Father.prototype.money = function() { 
  console.log(100000); 
};
 //子构造函数
  function Son(uname, age , score){ 
    // this 指向子构造函数的对象实例
    Father.call(this, uname, age);
    this.score = score;
  };
// Son.prototype = Father.prototype; 这样直接赋值会有问题,如果修改了子原型对 象,父原型对象也会跟着一起变化 
Son.prototype = new Father(); 
// 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数 
Son.prototype.constructor = Son; 
// 这个是子构造函数专门的方法 
Son.prototype.exam = function() { 
  console.log('孩子要考试'); 
}
var son = new Son('刘德华', 18, 100); 
console.log(son);

  

 5.ES5新增方法

  5.1 forEach()遍历数组  没有返回值

    array.forEach(function(value , index , array){....})

    value是每个数组元素

    index是每个元素索引值

    array是当前数组

  5.2 filter()筛选数组  其实也会遍历数组

    array.filter(function(value , index , array){

      return  条件表达式

    })

    返回的是一个新数组  括号跟的参数都是一样的

  5.3 some()   查找数组中是否有满足条件的元素  其实也会遍历数组

    array.some(function(value , index , array){

      return  条件表达式

    })

     返回值是布尔值,只要查找到第一个满足条件的元素就会终止循环 效率高  

 

   5.4 trim方法去除字符串两端的空格

    字符串的特性 不可变性

var str = ' hello ' ;
console.log(str.trim())  //hello 去除两端空格 
var str1 = ' he l l o ' ;
console.log(str1.trim())  //he l l o 去除两端空格

  5.5 获取对象的属性名 

    Object.keys(对象) 获取到当前对象中的属性名 ,返回值是一个数组

var obj = { 
    id: 1, 
    pname: '小米', 
    price: 1999,
    num: 2000 
};
var result = Object.keys(obj);
console.log(result)   //返回的新数组[id,pname,price,num]

  5.6 Object.defineProperty设置或修改对象中的属性

Object.defineProperty(对象,修改或新增的属性名,{ 
  value:修改或新增的属性的值, 
  writable:true/false,  //如果值为false 不允许修改这个属性值 
  enumerable: false,    //enumerable 如果值为false 则不允许遍历 
  configurable: false   //configurable 如果为false 则不允许删除这个属性 属性是否可以被删除或是否可以再次修改特性 
})

 

 6.函数的定义和调用

  6.1 函数的定义方式

    (1) function关键字(命名函数)

      function fn(){......}

    (2) 函数表达式(匿名函数)

       var fn = function(){......}

    (3) 利用new function('参数1','参数2','函数体')

      var f = new Function('a','b','console.log(a +b)');   //参数都必须是字符串格式

      f( 3, 2);   //输出结果是5 

  所有函数都是Function的实例,函数也属于对象

  6.2 函数的调用方式

 1     // 1.普通函数
 2 function fn(){
 3     console.log('hello world')    
 4 }
 5 fn();
 6 
 7 // 2.对象的方法
 8 var o = {
 9     sayHi : function(){
10         console.log('hello world')
11      }
12 }
13 o.sayHi();
14 
15 // 3.构造函数
16 function Star(){};
17 new Star();
18 
19 
20 // 4.绑定函数事件
21 btn.onclick = function(){}  // 点击了按钮就可以调用这个函数
22 
23 // 5.定时器函数
24 setInterval(function(){},1000);  // 这个函数定时器自动一秒钟调用一次
25 
26 //6.立即执行函数(自调用函数)
27 (function(){
28      console.log('hello world')
29 })();

 

7.this

  7.1 函数内部的this指向

    一般情况下,this指向函数的调用者

    

  7.2 改变函数内部this的指向

    1.call()方法

      可以直接调用函数,也可以改变this的指向     

      var o = {
        name: 'andy'
      }
      function fn(a, b) {
        console.log(this);
        console.log(a+b)
      };
      fn(1,2)            // 此时的this指向的是window 运行结果为3
      fn.call(o,1,2)  //此时的this指向的是对象o,参数使用逗号隔开,运行结果为3

        应用:经常做继承

    2.apply()方法

      可以直接调用函数,也可以改变this的指向  

      var o = {
        name: 'andy'
      }
      function fn(a, b) {
        console.log(this);
        console.log(a+b)
      };
      fn()                      // 此时的this指向的是window 运行结果为3
      fn.apply(o,[1,2])  //此时的this指向的是对象o,参数使用数组传递 运行结果为3

       应用:跟数组有关

// 利用apply借助数学内置对象求最大值等
var arr = [1,56,55,4,33]; var max = Math.max.apply(Math,arr); console.log(max) // 输出结果应该是56

    3.  bind()方法

       不会调用函数,但是可以改边this的指向,返回的是一个新函数    

      var o = {
        name: 'andy';
      };
      function fn(a, b) {
        console.log(this);
        console.log(a + b);
      };
      var f = fn.bind(o, 1, 2);    //此处的f是bind返回的新函数
      f();                                   //调用新函数 this指向的是对象o 参数使用逗号隔开

      应用:不调用函数,但是还想改变this指向

     4. call、apply、bind三者区别

      call和apply都是直接调用函数,可以改变this的指向

         call后面跟的参数是列表形式,apply跟的是数组形式

      bind不会直接调用函数,也可以改变ths的指向

8. 严格模式(strict mode)

    ES5 的严格模式是采用具有限制性 JavaScript变体的一种方式,即在严格的条件下运行 JS 代码。

    8.1 为脚本开启严格模式


    (function (){
 //在当前的这个自调用函数中有开启严格模式,当前函数之外还是普通模式 
         "use strict"; 
          var num = 10; 
          function fn() {
          } 
     })(); 
//或者 
<script> 
    "use strict";
      //当前script标签开启了严格模式 
</script>
<script> 
     //当前script标签未开启严格模式
 </script>   

    8.2 为函数开启严格模式

      要给某个函数开启严格模式,需要把“use strict”; (或 'use strict'; ) 声明放在函数体所有语句之前。

function fn(){
     "use strict";
      return "123"; 
}
//当前fn函数开启了严格模式

    8.3 严格模式中的变化

'use strict';
 num = 10 ;
console.log(num)//严格模式后使用未声明的变量 
‐‐‐ ‐‐‐‐‐‐‐ ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
var num2 = 1;
delete num2;//严格模式不允许删除变量 
‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ‐‐‐‐‐‐‐ 
function fn() { 
    console.log(this); // 严格模式下全局作用域中函数中的 this 是 undefined 
}
fn();
 ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ‐‐‐‐‐‐‐‐
 function Star() {
 this.sex = '男'; 
}
// Star();严格模式下,如果 构造函数不加new调用, this 指向的是undefined 如果给 他赋值则 会报错. 
var ldh = new Star(); 
console.log(ldh.sex); 
‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ‐‐‐‐‐‐‐‐‐ 
setTimeout(function() { 
console.log(this); //严格模式下,定时器 this 还是指向 window
 }, 2000);

 

9.高阶函数

  高阶函数是对其他函数进行操作的函数,可以接收函数作为参数或者将函数作为返回值输出。

  函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用。最典型的就是作为回调函数。同理函数也可以作为返回值传递回来

10.闭包(clsure)

  闭包是指有权访问另一个函数作用域中变量的函数。简单理解就是一个作用域可以访问另外一个函数内部的局部变量。

  作用: 延伸变量的作用范围,增长了变量的生命周期。

  弊端: 造成内存泄漏。(内存泄漏,就是本该销毁的数据没有销毁,造成内存浪费)

  闭包里的经典面试题:

    1. 遍历注册li的点击事件,利用闭包的实行,点击每一个li输出对应的索引

for (var i = 0; i < lis.length; i++) { 
// 利用for循环创建了4个立即执行函数 
// 立即执行函数也成为小闭包因为立即执行函数里面的任何一个函数都可以使用它的i这个变量        
  (function(i) {
    lis[i].onclick = function() {        console.log(i);      }   })(i);
}

    2. 遍历 1 ~ 5 5个数值,利用闭包,在setTimeout里面输入 1 ~ 5

  

for (var i = 0; i < 5; i++) {
     (function(i) { 
         setTimeout(function() { 
        console.log(lis[i].innerHTML);
     }, 3000)
   })(i);
}

  

11.递归

   如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。

   简单理解:函数内部自己调用自己, 这个函数就是递归函数

  递归函数的作用和循环效果一样,很容易发生‘栈溢出’错误(stack overflow),所以必须要加退出条件return。

  1. 利用递归求1~n的阶乘

function fn(n){
    if (n == 1) {
         return 1;
    }
    return n * fn(n -1)
}
console.log(fn(3))

   2.  利用递归求斐波那契数列(斐波那契数列就是前两个数的和等于第三个数字,也叫兔子数)

// 利用递归函数求斐波那契数列(兔子序列) 1、1、2、3、5、8、13、21... 
// 用户输入一个数字 n 就可以求出 这个数字对应的兔子序列值
// 我们只需要知道用户输入的n 的前面两项(n‐1 n‐2)就可以计算出n 对应的序列值

function fb(n){
    if ( n== 2 || n== 1) {
         return 1;
    }
    return fb( n -1) + fb (n-2);
} 
console.log(fb(3))

  

12.正则表达式

  12.1 什么是正则表达式?

    正则表达式(Regular Expression)是用于匹配字符串中字符组合的模式。在JavaScript中,正则表达式也是对象

  12.2 作用

     1.匹配  2. 替换 3.提取

13.正则表达式在js中的使用

  1. 正则表达式的创建 

    方式一 :通过调用RegExp对象的构造函数创建

      var reg = new RegExp(/123/);

      console.log(reg);    // 输出结果 /123/

    方式二 : 利用字面量创建

      var rg = /123/;

  2. 测试正则表达式

    test()正则对象方法,用于检测字符串是否符合规则,该方法返回值是布尔类型,true或false,括号里的参数是字符串

    var rg = /123/;

    console.log(rg.test(123)); // 返回值是true

    console.log(rg.test('abc')); // 返回值是false

  3. 正则表达式中的特殊字符

    3.1边界符(正则表达式中的边界符(位置符)用来提示字符所处的位置)

      

      如果 ^和 $ 在一起,表示必须是精确匹配

    3.2 字符类 

        [ ]方括号,表示有一系列字符可供选择,只要匹配到其中一个就可以了

         

         

    3.3 量词符

      量词符用来设定某个模式出现的次数,都是写在某个模式的后面

      

      逗号前后都不能有空格

 

    3.4 括号总结

      大括号{}  量词符,里面表示重复次数

      中括号[]  字符集合,匹配中括号里面的任意字符

      小括号() 表示优先级

    3.5 预定义类

      指的是某些常见模式的简写方式

      

    3.6 正则替换和正则提取

      replace()方法可以实现替换字符串操作,用来替换的参数可以是一个字符串,也可以是一个正则表达式

      注意:replace()是字符串的方法,并不是正则对象的方法

      语法: obj.replace(参数1,参数2)

       obj是要选择的目标字符串,参数1是要被替换的字符串,可以是字符串也可以是正则表达式,参数2是替换之后的字符串

var str = "abcd";
// 把字符串里的a替换成 哈哈
var str1 = str.replace("a","哈哈");
var str2 = str.replace(/a/,"哈哈");
console.log(str1);   //输出 哈哈bcd  
console.log(str2);   //输出 哈哈bcd

      match()可以进行提取,返回值是一个数组

      语法 : 字符串.match(正则表达式)

      str.match(/\w+@\w+\.\w+/g)  可以提取字符串里的所有邮箱

 

     3.7 正则表达式参数

        后面可以跟三种形式的参数

        /表达式/g     表示全局匹配,就是可以将所有的某个字符都进行转换

        /表达式i/    忽略大小写

        /表达式/gi  全局匹配+忽略大小写

        

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2019-09-03 21:56  少年e  阅读(1025)  评论(0编辑  收藏  举报