对象:任何事物都可以看作是对象。

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

  • 面向过程:凡是自己亲力亲为,自己按部就班的解决现有问题。
  • 面向对象:自己充当一个指挥者的角色,指挥更加专业的对象帮我解决问题。
  • 联系:面向对象仍然离不开面向过程,可以认为它是对面向过程更高一层的封装。

2、创建对象的方式

  • 字面量形式

    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);

3、构造函数

  • 概念

  • 如果一个函数配合new关键字创建对象,那么这个函数也叫构造函数

    1. 构造函数与普通函数本质上是一样的
    2. 编写构造函数时,首字母通常会大写,但不是必须的(类似变量驼峰命名法)
  • 返回值特点

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

4、类与实例的概念

  • 类是对一些具有相同特征与特性事物的抽象描述

    1. 比如动物类的定义是比较抽象的,它抽取了动物与动物之间的相同特征。
    2. 同样植物类、哺乳动物类、人类的定义也都是比较抽象的,也是提取他们的共同特征而形成的定义,这就是类。
    3. 在js中,可以把构造函数看作是类
  • 实例

  • 实实在在的具体事物就是某个类的实例。

    1. 比如我家的旺财,是狗类的实例
    2. 我的弟弟妹妹,是人类的实例
    3. 在js中,通过构造函数创建的对象就是实例

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

  • 对象类型

  • 对象的类型就是其构造函数的名字

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

5、 原型

  • 原型是一个对象,它的属性可以供其他对象共享
    1. js中有很多原型对象,基本每个对象都有属于自己的原型
    2. 原型对象的存在可以大大的节省内存开销
  • 原型的使用

  1. 每个构造函数都有一个prototype属性,可以给其赋值
  2. 然后通过构造函数创建的实例就可以共享其属性与方法

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

  • 面向对象

  • 缺点

    1. 通常比面向过程消耗内存,因为有很多实例要存储
    2. 前期开发比较缓慢,但是复用性强,后期开发与维护进度会逐渐加快

    优点

    1. 变量的管理比较清晰,可读性较高
    2. 因为代码与对象间的职责比较清晰,所以后期可维护性和可扩展性也比较高
    3. 复用性更强
  • 面向过程

  • 缺点

    1. 变量混乱,可读性较差
    2. 通常有新需求出现,代码改动比较大,所以可维护性和可扩展性比较差

    优点:开发迅速,只要能解决当前问题即可

7、面向对象3大特征

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

8、 面向对象的书写过程

  1. 根据需求提取解决该问题所需的对象

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

9、 原型其他

  • 谁有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. 对象的属性赋值只影响自己,不会对其他对象和原型对象造成影响

10、原型常见书写方式

  • 默认原型

    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. 实例成员
    • 添加给实例自己的属性与方法
    • 原型上供实例使用的属性与方法

11、原型链

  • 概念:一个对象继承的所有由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

12、运算符与方法

  • 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(字符串代码)

13、 函数四种调用模式

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

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

这种方式调用,函数运行时内部的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。

14、 作用域

概念:变量的有效范围。

  • 全局变量

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

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

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

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

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

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

  • 作用域链

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

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

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

15、闭包

  • 概念:在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);
    }

16、 预解析

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

  1、变量声明提升:检测到变量声明那就率先进行声明

  2、函数声明提升:检测到函数声明也率先进行声明

  • 变量声明

  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); //
}(100));
  • 函数表达式的名称

    // 函数fnName的名字在外面无法访问,但是可以在函数内访问,
    // 相当于自己的一个局部变量,值为自己的引用。
    var fn = function fnName(){
    console.log(fnName); // 里面可以访问
    };
    console.log(fnName); // 外面访问报错

17、 函数的四种调用模式

  • this的特点

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

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

  • 方法调用模式

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

  • 构造器调用模式

    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);
2、借用Object.prototype.toString获取对象类型
var arr = [];
Object.prototype.toString.call(new Date).slice(8, -1)
3、借用父类构造函数给子类实例添加属性
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)

18、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的对应值组成的新数组

19、 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拥有了单独的作用域

最新文章

  1. ZooKeeper基本原理
  2. 【C语言入门教程】3.3 条件控制语句
  3. PAT乙级 1017. A除以B (20)
  4. 06day2
  5. window下安装composer and yii2
  6. 选项卡 js操作
  7. fetch默认不携带cookie
  8. POJ 3625 最小生成树 Prim C++
  9. vue_实例_组件的生命周期
  10. 第十二周翻译-《Pro SQL Server Internals, 2nd edition》
  11. 宽字符————_T、_TEXT、L、TEXT之间的区别
  12. 后台MemoryStream图片流用ajax接收乱码问题
  13. PYTHON-绑定方法 反射 内置函数
  14. libusb 终于搞好了
  15. 【javascript】原生js更改css样式的两种方式
  16. 字符串以及for循环
  17. JVM进程cpu飙高分析
  18. NIO编程中buffer对象的理解以及API的使用
  19. osgearth将视点绑定到一个节点上
  20. WebGl 二维纹理贴图(矩形)

热门文章

  1. phpstudy2016 redis扩展 windows
  2. 关于手机适配中的rem的学习随笔
  3. Delphi锁定鼠标 模拟左右键 静止一会自动隐藏鼠标
  4. 【转】Winform Socket通信
  5. ipython安装(python3.6.1)
  6. 20145316《Java程序设计》第六周学习总结
  7. iOS 所有设备一览 &amp;&amp; CoreFoundation源码
  8. 企业级hbase HA配置
  9. 【c++ primer, 5e】返回类型和return语句
  10. Python爬虫学习笔记之爬虫基础库