JavaScript高级与面向对象

对象:任何事物均可以看做是对象。

一、面向对象与面向过程的概念

  • 面向过程:凡是本身亲力亲为,本身循序渐进的解决现有问题。
  • 面向对象:本身充当一个指挥者的角色,指挥更加专业的对象帮我解决问题。
  • 联系:面向对象仍然离不开面向过程,能够认为它是对面向过程更高一层的封装。

二、建立对象的方式

  •  字面量形式

    var p = {}; p.name = '中国人'; p.age = '500';
  •  构造函数形式 ==> 复用性更强

    function Person(name, age) { this.name = name; this.age = age; } var p = new Person('中国人', 500); var p2 = new Person('中国人2', 500);

     

 

三、构造函数

  • 概念

  • 若是一个函数配合new关键字建立对象,那么这个函数也叫构造函数
    1. 构造函数与普通函数本质上是同样的
    2. 编写构造函数时,首字母一般会大写,但不是必须的(相似变量驼峰命名法)
  •  返回值特色

  1. 若是构造函数没有return语句,那么new它,获得一个新实例
  2. 若是构造函数return了一些基本类型数据,那么new它,获得一个新实例
  3. 若是构造函数return了一个对象,那么new它,获得return的对象

四、类与实例的概念

  • 类是对一些具备相同特征与特性事物的抽象描述
    1. 好比动物类的定义是比较抽象的,它抽取了动物与动物之间的相同特征。
    2. 一样植物类、哺乳动物类、人类的定义也都是比较抽象的,也是提取他们的共同特征而造成的定义,这就是类。
    3. 在js中,能够把构造函数看做是类
  • 实例

  • 实实在在的具体事物就是某个类的实例。
    1. 好比我家的旺财,是狗类的实例
    2. 个人弟弟妹妹,是人类的实例
    3. 在js中,经过构造函数建立的对象就是实例

联系:若是把类看做是模子,实例则是模子印出来的东西。

  •  对象类型

  • 对象的类型就是其构造函数的名字
    1. 好比数组是Array类型的对象,日期是Date类型的对象
    2. Array和Date就是其构造函数的名字
    3. 那么经过Person建立一个实例,那么这个实例就是Person类型的对象。

五、 原型

  • 原型是一个对象,它的属性能够供其余对象共享
    1. js中有不少原型对象,基本每一个对象都有属于本身的原型
    2. 原型对象的存在能够大大的节省内存开销
  •  原型的使用

  1. 每一个构造函数都有一个prototype属性,能够给其赋值
  2. 而后经过构造函数建立的实例就能够共享其属性与方法

六、面向对象与面向过程优缺点

  • 面向对象

  • 缺点
    1. 一般比面向过程消耗内存,由于有不少实例要存储
    2. 前期开发比较缓慢,可是复用性强,后期开发与维护进度会逐渐加快
    优势
    1. 变量的管理比较清晰,可读性较高
    2. 由于代码与对象间的职责比较清晰,因此后期可维护性和可扩展性也比较高
    3. 复用性更强
  •  面向过程

  • 缺点
    1. 变量混乱,可读性较差
    2. 一般有新需求出现,代码改动比较大,因此可维护性和可扩展性比较差
    优势:开发迅速,只要能解决当前问题便可

七、面向对象3大特征

  1. 封装性:对象能够把不少属性与方法集中在一块儿管理,就是js的封装性。
  2. 继承性:对象可使用其原型对象的属性与方法,就是js的继承性。
  3. 多态性:js没有多态。若是非要说,那么对象形态、继承关系能够随时被改变,能够认为是js的多态性。

八、 面向对象的书写过程

  1. 根据需求提取解决该问题所需的对象
    • 好比我要逛街,须要一个导购,须要一个保镖,须要一个女友
  2. 编写每个对象所对应的构造函数
    • 构造函数能够重复性建立实例,由于我可能须要多个保镖 function Person() {}
  3. 抽取对象所需的属性
    • 就是该对象应该拥有的特征,好比人有名称、年龄、性别、四肢、双眼。 function Person(name) { this.name = name; }
  4. 抽取对象所需的方法
    • 就是该对象应该拥有的特性,好比人会学习创造,狗会看门逗你笑 Person.prototype.study = function(){};
  5. 根据写好的构造函数建立实例,调用属性方法解决实际需求
    • 就是调度实例干事 var p = new Person(); p.study(); 

九、 原型其余

  • 谁有prototype与proto

  1. 每一个函数都有prototype属性
  2. 每一个对象都有proto属性
  3. 函数比较特殊,便是函数又是对象,因此prototype与proto都有
  • prototype与proto联系

  1. 经过构造函数建立的实例
  2. 当前构造函数的prototype属性指向谁,实例的proto属性就指向谁
  • 如何获得一个对象继承的原型

  1. 经过proto属性(可是它是非标准属性,不建议开发中使用)
  2. 经过constructor属性获得对象的构造函数,再访问其prototype获得原型
  • 建立对象时内在的4个步骤

  1. 建立一个新实例(本质上就是开辟了一块内存空间)
  2. 设置新对象的原型执行构造函数,执行时设置其this指向新实例
    • 给新实例设置proto属性值
    • 这个值与构造函数的prototype属性有关
    • 赋值过程至关于这样:新实例.proto = 构造函数.prototype
  3. 返回新实例的地址
  • 对象的属性访问规则

  1. 优先从自身查找
  2. 找不到就去原型找
  3. 还找不到继续去原型的原型找
  4. 直到终点,终点也没有返回undefined
  •  对象的属性赋值

  1. 给一个对象的属性赋值
  2. 若是以前没有该属性那么就是新增,有就是修改
  3. 对象的属性赋值只影响本身,不会对其余对象和原型对象形成影响

十、原型常见书写方式

  • 默认原型

    function P() {} P.prototype.fun = function(){}; var p = new P();
  • 置换原型

    function P() {} P.prototype = { constructor: P, fun: function(){} }; var p = new P();
  • extend复制扩展

    function P() {} extend(P.prototype, {}, { fun: function(){} }, { fun2: function(){} }); var p = new P();
  • Object.create

    var proObj = { fun: function(){} }; var p = Object.create(proObj);
  • 实现属性复制函数封装

    function extend() { var target = arguments[0]; for(var i = 1, len = arguments.length; i < len; i++) { for(var key in arguments[i]) { target[key] = arguments[i][key]; } } return target; }
  • 类成员与实例成员

  1. 类成员(静态成员):添加给类本身的属性与方法
  2. 实例成员
    • 添加给实例本身的属性与方法
    • 原型上供实例使用的属性与方法

 

十一、原型链

 

  • 概念:一个对象继承的全部由proto属性串联在一块儿的对象,称为该对象的原型链。

  • 对象原型链的研究方案

  1. 先经过proto获得对象的原型
  2. 而后访问这个原型的constructor属性,肯定该原型的身份
  3. 而后继续按照上诉两个步骤,往上研究原型,最终就获得了对象的原型链。
  •   规律与常见对象原型链结构

  1. 原型链的终点统一是Object.prototype
  2. 对象的原型和该对象的类型有关
    1. 好比Person的实例,原型是Person.prototype
    2. 好比Animal的实例,原型是Animal.prototype
    3. []的原型链结构 [] ==> Array.prototype ==> Object.prototype ==> null 
    4. {}的原型链结构 {} ==> Object.prototype ==> null 
    5. /abc/的原型链结构 /abc/ ==> RegExp.prototype ==> Object.prototype ==> null 
    6. Person的原型链结构 Person ==> Function.prototype ==> Object.prototype ==> null 
    7. Function的原型链结构 Function ==> Function.prototype ==> Object.prototype ==> null 
    8. Object的原型链结构 Object ==> Function.prototype ==> Object.prototype ==> null 
  3. 构造函数默认的prototype,它统一都继承Object.prototype
    • 好比Person.prototype,原型是Object.prototype
    • 好比Animal.prototype,原型是Object.prototype
  4. 经过这个规则,能够自由猜测出任意一个实例全部的原型
    • 好比Book的实例,其原型结构为: Book实例 ==> Book.protoype ==> Object.prototype ==> null

十二、运算符与方法

  • in -- 运算符

  1. 做用:判断可否使用某个属性(包含继承的属性)
  2. 语法:属性名 in 对象
  3. 返回值:boolean
  •  hasOwnProperty -- 方法

  1. 做用:判断一个属性是否是本身的(不包含继承的属性)
  2. 语法:对象.hasOwnProperty(属性名)
  3. 返回值:boolean
  • 关于for in遍历的补充:for in能够遍历对象继承的属性,不过一些内置的属性是不可遍历的。

  • delete -- 运算符

  1. 做用:删除对象的属性
  2. 语法:delete 对象.属性名 || delete 对象[属性名]
  3. 返回值:boolean
  • instanceof -- 运算符

  1. 做用:判断一个对象的原型链中是否含有某个构造函数的prototype
  2. 语法:对象 instanceof 构造函数
  3. 返回值:boolean
  •  Function -- 内置构造函数

  1. 做用:建立函数实例
  2. 语法:new Function(形参1,形参2,...,代码体)
  3. 返回值:新建立的函数实例
  4. 特色:可以把字符串当作js脚本执行
  • eval -- 内置的全局函数

  1. 做用:执行字符串代码
  2. 语法:eval(字符串代码)

1三、 函数四种调用模式

谨记:函数调用时,内部的this的值和这个函数定义无关,和运行(调用)有关。api

  • 函数调用模式 ==> 函数名() || 自调

这种方式调用,函数运行时内部的this指向全局对象window。数组

  • 方法调用模式 ==> 对象.方法名() || 对象['方法名'] || 祖对象.父对象.子对象.方法名()

这种方式调用,函数运行时内部的this指向宿主对象。 (dom中事件绑定的函数,就是这种调用方式,因此this指向对应的dom对象)缓存

  • 构造函数调用模式 ==> new 构造函数() || new 对象.构造函数()

这种方式调用,函数运行时内部的this指向新建立的实例对象。闭包

  • 上下文调用模式(间接调用模式)

  1. 函数名.call(this, arg1, arg2);
  2. 函数名.apply(this, [arg1, arg2]); 这种方式调用,函数运行时内部的this指向call或apply传入的第一个参数; 若是没有传第一个参数,或者第一个参数为null、undefined,那么this统一指向window。

1四、 做用域

 概念:变量的有效范围。

  • 全局变量

  1. 在全局都有效的变量。
  2. 定义方式:函数外定义。
  3. 生命周期:从定义开始,到页面被卸载结束。
  • 局部变量

  1. 只在局部有效的变量。
  2. 定义方式:函数内定义。
  3. 生命周期:通常状况下,是从定义开始,到函数执行完毕结束。
  • 函数做用域

  1. 只有函数才能够产生新的做用域
  2. 只有函数能够限定变量的有效范围
  •  块级做用域 ==> js没有

  1. 凡是代码块就能够产生新的做用域
  2. 凡是代码块就能够限定变量的有效范围
  • 词法做用域(静态做用域)

  说的是变量的查找规则,特色是变量查找与函数定义有关,与调用无关app

  1. 先在当前做用域查找
  2. 找不到就去定义该函数的做用域找
  3. 一直找到全局做用域为止,全局也没有则报错
  • 做用域的产生:函数能够被屡次重复调用,调用一次就会产生一个新的做用域。

  • 做用域链

  1. 函数在定义的时候,未来它执行时的上级做用域就被肯定好了,上级做用域可能还有上级,函数全部的上级做用域称之为做用域链。
  2. 一个函数做用域能够访问的全部上级做用域,称为它的做用域链。
  •  垃圾回收机制原则

  1. 一个对象没有被其余变量或者属性引用,那么就会被释放。 同时还要保证该对象可以被使用,对于那些没法使用,又存在循环引用的对象,也会被释放。
  2. 一个局部变量没有被其余函数引用,那么就会被释放。
  • 注意:有一个容易搞混,又没有什么联系的知识点,这里强调一下

  • 函数内的this,与函数的定义无关,与调用有关。
function fn() { console.log(a); // 报错,本身找不到,去定义fn的全局找,因此这里和fn的定义有关,与fn的调用无关。
} (function() { var a = 10; fn(); })();

 

1五、闭包

  • 概念:在js中访问了自由变量的函数就是闭包

  • 自由变量:函数可访问的外部局部变量,称之为该函数的自由变量

  • 特色:闭包的自由变量生命周期会被拉长,与闭包的生命周期进行了捆绑

  • 计数器案例

    function getCounter() { var total = 0; return { add: function() { total++; }, get: function() { return total; } }; }; var counter = getCounter(); counter.add(); counter.get(); var counter2 = getCounter(); counter2.add(); counter2.get();

     

  •  缓存操做

    var cache = (function() { var cache = {}; return { set: function(key, val) { cache[key] = val; }, get: function(key) { return cache[key]; } }; }()); cache.set('张锐', '中国人'); cache.get('张锐');

     

  •  for循环练习

    var arr = ['第一句话', '第二句话', '第三句话']; for(var i = 0, len = arr.length; i < len; i++) { setTimeout(function(i) { return function() { console.log(arr[i]); } }(i), 1000 * i + 1000); }

     

1六、 预解析

  1. 能够理解为js解析引擎在逐行执行代码前,对一些特殊代码的预先执行。
  2. 预解析事后代码才会从上到下逐行执行,可是预解析时已经定义的变量与函数,是不会重复定义的。
  3. 预解析的本质就是变量对象初始化。
  • 预解析作了两件事情

  一、变量声明提高:检测到变量声明那就率先进行声明dom

  二、函数声明提高:检测到函数声明也率先进行声明函数

  • 变量声明

  1. 使用经过var定义的变量,才属于变量声明
    var a; //属于变量声明。
    b = 10;// 不属于变量声明。
  2. var关键字能够经过逗号连续声明多个变量
    var a, b, c = 20, d = 30; //a,b,c,d所有属于声明。
  3. var关键字在声明变量的时候,能够给其赋值,若是赋值表达式中含有一些变量,这些变量不属于变量声明。
    var a = b = 10; //其中a属于变量声明,b不属于。
  • 函数声明

  • 在js中,函数声明式写法比较单一,好区分。
    1. 要么定义在全局
    2. 要么定义在另外一个函数主体内
  • 预解析的特色

  1. 在变量声明以前访问它不会报错
  2. 在函数声明以前调用它不会报错
  • 预解析相关细节

  1. js预解析分全局预解析与局部预解析,区别在于局部预解析在函数调用时发生。
  2. 变量声明重名 -- 最终只留一个
      console.log(a); // 预解析后值保留一个变量a,值为undefined
      var a = 1; var a = 2;
  3. 函数声明重名 -- 保留后面的函数
     console.log(test); // 预解析后test为打印2的函数
      function test(){ console.log(1) } function test(){ console.log(2) }
  4. 变量与函数重名 -- 保留函数
    console.log(test); // 预解析后test值为函数
      var test = 10; function test(){} var test = 20;
  • 函数执行时形参会优先执行

    形参定义与赋值优先于变量与函数声明。学习

(function(a) { console.log(a); // a函数
  var a = 200; function a(){} console.log(a); // 200
}(100));
  • 函数表达式的名称

    // 函数fnName的名字在外面没法访问,可是能够在函数内访问,
        // 至关于本身的一个局部变量,值为本身的引用。
        var fn = function fnName(){ console.log(fnName); // 里面能够访问
     }; console.log(fnName); // 外面访问报错

1七、 函数的四种调用模式

  • this的特色

  1. 函数中的this,调用方式不一样,指向不一样
  2. this与调用有关,与定义无关
  • 1.17.1.1. 函数调用模式

    函数名() || (function(){}()) ==> windowthis

  • 方法调用模式

    对象.方法名() || 对象方法名 || 祖对象.父对象.子对象.方法名() ==> 宿主对象spa

  • 构造器调用模式

    new 构造函数() || new 对象.构造函数() ==> new出来的新实例

  • 间接调用模式(上下文调用模式)

  1. call
    • 函数.call(指定的this,实参1,实参2,...)
    • 对象.方法.call(指定的this,实参1,实参2,...)
  2. apply
    • 函数.apply(指定的this,[实参1,实参2,...])
    • 函数.apply(指定的this,{0: 实参1, 1:实参2, length: 2})
    • 对象.方法.apply(指定的this,[实参1,实参2,...])
  3. 异同
    • call与apply都来自Function.prototype,因此全部的函数均可以使用。
    • 均可以改变函数this的指向
    • 不一样之处在于传参的方式上
  4. 补充
    • 函数调用call和apply,其实是间接调用本身
    • 例如fn.call(),表面上看是调用call方法
    • 实际上连fn本身也被调用了,和fn()直接调用的区别是,this变了
  •  call和apply方法借用的原理

  1. 若是一个方法内部操做的是this,那么咱们就能够经过call或apply指定该方法this, this改变成谁,那么该方法最终操做的就是谁。
  2. 若是一个方法内部没有操做this,那么是没法借用的。
  • call和apply常见的使用场景

 1.借用数组方法操做伪数组
// 给伪数组添加数据
var obj = {}; Array.protype.push.call(obj, '要添加的第一个值', '要添加的第二个值') // 经过伪数组获取对应的真数据(获取后原伪数组不会被改变,只是获得了新数组)
var argArr = [].slice.call(arguments);
二、借用Object.prototype.toString获取对象类型
var arr = []; Object.prototype.toString.call(new Date).slice(8, -1)
三、借用父类构造函数给子类实例添加属性
function Parent(name, age) { this.name = name; this.age = age; } function Son() { Parent.apply(this, arguments); } var p = new Son('火星人', 999); // apply拆分数组或伪数组值依次传递给函数
var arr = [1, 10, 20, 40]; Math.max.apply(null, arr)

1八、ES5数组新增的3个方法

  • forEach

  1. 做用:帮咱们遍历数组,每遍历到一个值,就会调用一次回调,把这个值与它的下标传递过去
  2. 语法:数组.forEach(function(v, i){ console.log('使用forEach帮咱们遍历好的值与下标') })
  3. 返回值:undefined
  • map

  1. 做用:能够用来代替forEach,可是map能够接收回调的返回值,最终经过一组数据映射为回调返回的另一组数据
  2. 语法:var mapArr = 数组.map(function(v, i){ return v * v })
  3. 返回值:回调全部的返回值组成的新数组
  •  filter

  1. 做用:能够用来代替forEach,可是还能够过滤数组中的值
  2. 语法:var filterArr = 数组.filter(function(v, i){ if(v % 2 ==0){ return true; } })
  3. 返回值:全部返回回调返回true的对应值组成的新数组

1九、 call&apply的补充

  1. 若是不传参 ==> this指向window
  2. 传null ==> this指向window
  3. 传undefined ==> this指向window
  4. 传123 ==> this指向123的包装类型对象(Number对象)
  5. 传'abc' ==> this指向'abc'的包装类型对象(String对象)
  6. 传true ==> this指向true的包装类型对象(Boolean对象)
  7. 传对象 ==> this指向传入的对象

20、 严格模式

  1. ES5新增的一个特性,使用该特性可让js以一种新的模式运行js脚本。
  2. 该模式下能够强制咱们抛弃那些不推荐不友好的写法
  3. 该模式下可让js以前的一些设计不太合理的api表现的合理一些
  4. 该模式下可让js拥有一些新的特性,好比ES6/ES7规范中定义的某些语法,必须在严格模式下才有效
  •  严格模式的分类

    全局模式

    1. 在全局代码的最上面书写一句话'use strict';
    2. 使用该模式,全部的代码都按照严格模式执行
  1. 局部模式
    1. 在函数内部的最上面书写一句话'use strict';
    2. 使用该模式,只有该函数内的代码才会按照严格模式执行
  • 须要记住的几条严格模式规则

  1. 定义变量必须使用var
  2. 函数调用模式this为undefined
  3. 真正实现了call谁this就为谁

 其余

  1. 不能使用with语句
  2. 废除函数.caller与arguments.callee
  3. eval拥有了单独的做用域
相关文章
相关标签/搜索