javaScript从入门到精通3.md

Posted 东夋壬

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了javaScript从入门到精通3.md相关的知识,希望对你有一定的参考价值。

今日鸡汤:这个世界不会因为你的付出就必须给予回报,也不会因为你以怎样的方式对待别人,就要求他人同等对待你。人活在这世上,最难的就是保持一份谦卑和平和,而这份谦卑,来源于内心的真诚和踏实的努力。

前提摘要

  1. javascript 是什么
  • 解析执行:轻量级解释型的,或是 JIT 编译型(即时编译)的程序设计语言
  • 语言特点:动态,头等函数 (First-class Function) - 函数可以作为别的函数的参数、函数的返回值,赋值给变量或存储在数据结构中
  • 执行环境:在宿主环境(host environment)下运行,浏览器是最常见的 JavaScript 宿主环境
  • 编程范式:基于原型、多范式的动态脚本语言,并且支持面向对象、命令式和声明式(如:函数式编程)编程风格

 

JavaScript 与浏览器关系
JavaScript 与浏览器关系

2. JavaScript 的组成:Ecmascript、DOM、BOM
知乎 - JavaScript 能做什么
GitHub - JavaScript 能做什么
阮一峰 - JavaScript 历史

 

  1. 基础
  • 语法:区分大小写、标识符、注释、严格模式、语句
  • 关键字和保留字:default 等、interface 等
  • 变量
  • 数据类型:Undefined、Null、Boolean、Number、String、Object
    • 基本类型(值类型):Undefined、Null、Boolean、Number、String
    • 复杂类型(引用类型):Object、Array、Date、RegExp、Function、基本包装类型(Boolean、Number、String)、单体内置对象(Global、Math)
    • 类型检测:typeof、instanceof、Object.prototype.toString.call()
  • 操作符:算数操作符、比较操作符、逻辑操作符
  • 流程控制语句:if...else if...else、while、do...while、for、switch
  • 函数:function
  1. 面向对象
  • 对象是单个事物的抽象;对象是一个容器,封装了属性(property)和方法(method)
  • 对象:无序属性的集合,其属性可以包含基本值、对象或者函数
  • 面向对象与面向过程
  • 面向对象的特性:封装性、继承性、[多态性]
    知乎 - 一句话说明什么是面向对象思想
    知乎 - 什么是面向对象编程思想
  • 面向对象的设计思想:抽象出 Class -> 根据 Class 创建 Instance -> 指挥 Instance 得结果。
  • 面向对象的编程思想 :根据需求,抽象出相关的对象,总结对象的特征和行为,把特征变成属性,行为变成方法,然后定义(js)构造函数,实例化对象,通过对象调用属性和方法,完成相应的需求
  • 创建对象:
var person = new Object();
person.name = "Tom";
person.sex = "male";
person.sayHi = function (){  console.log("Hello, I\'am " + this.name);  };

var person = {
  name: "Tom",
  sex: "male",
  sayHi: function (){  console.log("Hello, I\'am " + this.name);  }
}

function Person(name,sex){
  this.name = name;
  this.sex = sex;
  this.sayHi = function (){  console.log("Hello, I\'am " + this.name);  };
}
  1. 疑难点
  • 值类型和引用类型在内存中的存储方式:值类型按值存储、引用类型按引用存储
  • 值类型复制和引用类型复制:值类型按值复制、引用类型按引用复制
  • 值类型和引用类型参数传递:值类型按值传递、引用类型按引用传递
  • JavaScript 执行过程:
    1. 预解析:
      • 全局预解析(所有变量和函数声明都会提前;同名的函数和变量函数的优先级高)
      • 函数内部预解析(所有的变量、函数和形参都会参与预解析):函数、形参、普通变量
    2. 执行:在执行全局代码的过程中遇到函数调用就会先进行函数预解析,然后再执行函数内代码
  • 调用构造函数创建对象会经历 4 个步骤:
    • 创建一个新对象,开辟新空间
    • 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象)
    • 执行构造函数中的代码,初始化对象
    • 返回新对象
  • 构造函数和实例对象的关系
    • 构造函数是根据具体的事物抽象出来的抽象模板
    • 实例对象是根据抽象的构造函数模板得到的具体实例对象
    • 每一个实例对象都具有一个 constructor 属性,指向创建该实例的构造函数
    • 可以通过实例的 constructor 属性判断实例和构造函数之间的关系
  1. 扩展
    知乎 - Web 建站技术

javascript 高级

原型

原型的引用

构造函数为创建对象带来了方便,但是存在浪费内存的问题;比如,构造函数中有一个方法,每次创建对象的时候就会为这个方法申请内存空间,创建多个对象就需要多个内存空间

function Person(name,sex){
this.name = name;
this.sex = sex;
this.sayHello = function () {  console.log("hello");  };
}
var per1 = Person("Tom","male");
var per2 = Person("Jury","famale");
  1. 此时有了第一种解决方法:把函数声明放到外面
function sayHello() {  console.log("hello");  }
function Person(name,sex){
this.name = name;
this.sex = sex;
this.sayHello = sayHello;
}
var per1 = Person("Tom","male");
var per2 = Person("Jury","famale");
console.log(per1.sayHello == per2.sayHello);  // true
  1. 如果对象有多个方法,此时有了第二种解决方法:用一个对象存储方法
var fns = {
  sayHello: function(){  console.log("hello ya");  },
  playGame: funciton(){ console.log("lai wan ya da ye"); }
};
function Person(name,sex){
this.name = name;
this.sex = sex;
this.sayHello = fns.sayHello;
this.playGame = fns.playGame;
}
var per1 = Person("Tom","male");
var per2 = Person("Jury","famale");
console.log(per1.sayHello == per2.sayHello);  // true
console.log(per1.playGame == per2.playGame);  // true
  1. 但是这样的写法并不够优美,所以就有了原型:
    • Javascript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象。
      这个对象的所有属性和方法,都会被构造函数的实例继承
    • 这也就意味着,可以把所有对象实例需要共享的属性和方法直接定义在 prototype 对象上
function Person(name,sex){
this.name = name;
this.sex = sex;
}
Person.prototype.sayHello = function(){  console.log("hello ya");  };
Person.prototype.playGame = funciton(){ console.log("lai wan ya da ye"); };
var per1 = Person("Tom","male");
var per2 = Person("Jury","famale");
console.log(per1.sayHello == per2.sayHello);  // true
console.log(per1.playGame == per2.playGame);  // true

原型的作用之一 : 数据共享,节省内存空间
原型的写法:

  • 构造函数.prototype.属性 = 值
  • 构造函数.prototype.方法=值---->函数.prototype,函数也是对象,也有__proto__
  • 实例对象.prototype-------->实例对象中没有这个属性,只有__proto__(暂时的)
    原型可以为内置对象添加原型的属性或者方法 :
  • 系统的内置对象的属性和方法可能不满足现在需求,所以,可以通过原型的方式加入属性或者方法,为了方便开发
  • 为内置对象的原型添加属性和方法,那么这个内置对象的实例对象就可以直接使用了

构造函数、实例、原型三者之间的关系

 

 构造函数、实例、原型三者之间的关系
构造函数、实例、原型三者之间的关系

(1)任何函数都具有一个 prototype 属性,该属性是一个对象。

 

function F () {}
console.log(F.prototype);// => object

F.prototype.sayHi = function () {
  console.log(\'hi!\');
}

(2)构造函数的 prototype 对象默认都有一个 constructor 属性,指向 prototype 对象所在函数,即构造函数。

console.log(F.constructor === F); // => true

(3)通过构造函数得到的实例对象内部会包含一个指向构造函数的 prototype 对象的指针 __proto__

var instance = new F();
console.log(instance.__proto__ === F.prototype) ;// => true
//   `__proto__` 是非标准属性。

(4)实例对象可以直接访问原型对象成员。

instance.sayHi();  // => hi!

属性成员的搜索原则:原型链

每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性

  • 搜索首先从实例对象本身开始
  • 如果在实例中找到了具有给定名字的属性,则返回该属性的值
  • 如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性
  • 如果在原型对象中找到了这个属性,则返回该属性的值,否则 undefined
function Person(name){
   this.name = name;
}
Person.prototype.sayName = function(){  console.log(this.name);  };
function Student(age){
   this.age = age;
}
Student.prototype = new Person("张三");
var stu = new Student("23");
console.log(stu.age,stu.name);  // 23,张三
//  这正是多个对象实例共享原型所保存的属性和方法的基本原理

var divObj=document.getElementById("dv");
console.dir(divObj);
//divObj.__proto__---->htmlDivElement.prototype的__proto__
//--->HTMLElement.prototype的__proto__---->Element.prototype的__proto__
//---->Node.prototype的__proto__---->EventTarget.prototype的__proto__
//---->Object.prototype没有__proto__,所以,Object.prototype中的__proto__是null

实例对象读写原型对象成员

  1. 读取
      • 先在自己身上找,找到即返回
    • 自己身上找不到,则沿着原型链向上查找,找到即返回
    • 如果一直到原型链的末端还没有找到,则返回 undefined
  2. 值类型成员写入(实例对象.值类型成员 = xx
    • 当实例期望重写原型对象中的某个普通数据成员时实际上会把该成员添加到自己身上
    • 也就是说该行为实际上会屏蔽掉对原型对象成员的访问
  3. 引用类型成员写入(实例对象.引用类型成员 = xx):同上
  4. 复杂类型修改(实例对象.成员.xx = xx):
    • 同样会先在自己身上找该成员,如果自己身上找到则直接修改
    • 如果自己身上找不到,则沿着原型链继续查找,如果找到则修改
    • 如果一直到原型链的末端还没有找到该成员,则报错(实例对象.undefined.xx = xx

更简单的原型语法

前面例子中每添加一个属性和方法就要敲一遍 Person.prototype ,为减少不必要的输入,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象:

function Person(name,age){
    this.name = name;
    this.age = age;
}
Person.prototype = {
    type: "human",
    sayHello: function (){  console.log(this.name+"*****"+this.age);  }
}
//  这样做的好处就是为 `Person.prototype` 添加成员简单了,但是也会带来一个问题,那就是原型对象丢失了 `constructor` 成员。

为了保持 constructor 的指向正确,建议的写法是:

function Person(name,age){
  this.name = name;
  this.age = age;
}
Person.prototype = {
  constructor: Person, //  手动将 constructor 指向正确的构造函数
  type: "human",
  sayHello: fucntion(){  console.log(this.name+"***"+this.age);  }
}

原生对象的原型

所有函数都有 prototype 属性对象:

  • Object.prototype
  • Function.prototype
  • Array.prototype
  • String.prototype
  • Number.prototype
  • Date.prototype ...
    可以通过原型为原生对象扩展原型方法。

原型对象的问题

  • 共享数组
  • 共享对象
    如果真的希望可以被实例对象之间共享和修改这些共享数据那就不是问题。但是如果不希望实例之间共享和修改这些共享数据则就是问题。
    一个更好的建议是,最好不要让实例之间互相共享这些数组或者对象成员,一旦修改的话会导致数据的走向很不明确而且难以维护。

原型对象使用建议

  • 私有成员(一般就是非函数成员)放到构造函数中
  • 共享成员(一般就是函数)放到原型对象中
  • 如果重置了 prototype 记得修正 constructor 的指向

继承

继承是指一个对象直接使用另一对象的属性和方法。
在子类继承父类的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类的原有属性和方法,使其获得与父类不同的功能。同类事物具有共同性,在同类事物中,每个事物又具有其特殊性。
运用抽象的原则舍弃对象的特殊性,抽取其共同性,则得到一个适应于一批对象的类,这便是基类(父类),而把具有特殊性的类称为派生类(子类),派生类的对象拥有其基类的全部或部分属性与方法,称作派生类对基类的继承。

构造函数的属性继承:借用构造函数

function Person (name, age) {
  this.type = \'human\';
  this.name = name;
  this.age = age;
}
Person.prototype.eat = function(){  console.log("吃了吗?");  };
function Student (name, age) {
  Person.call(this, name, age);    //  借用构造函数继承属性成员
}
var s1 = new Student(\'张三\', 18);
console.log(s1.type, s1.name, s1.age); // => human 张三 18
//  这种继承只是属性继承,不继承父类的方法

构造函数的原型方法继承:拷贝继承(for-in)