1. JavaScript 基础介绍
1.1 JavaScript 是什么?
- 解释执行:轻量级解释下的,是 JIT 编译型的程序设计语言
- 语言特点:动态,头等函数(First-class Function)
- 又称函数是 JavaScript 中的一等公民
- 执行环境:在宿主环境下(host environment)下运行,浏览器是最常见的 JavaScript 宿主环境
- 但是在很多非浏览器环境中也是用 JavaScript,例如 node.js
- 编程范式:基于原型,多范式的动态脚本语言,并且支持面向对象、命令式和声明式(如:函数式编程)编程风格
JS 分为三个部分
组成部分 | 说明 |
---|---|
Ecmascript | 描述了该语言的语法和基本对象 |
DOM | 描述了处理网页内容的方法和接口 |
BOM | 描述了与浏览器进行交互的方法和接口 |
1.2 基本概念
- 语法
- 区分大小写
- 标识符
- 注释
- 严格模式
- 语句
- 关键字和保留字
- 变量
- 数据类型
- typeof 操作符 -- 获取数据类型
- Undefined
- Null
- Boolean
- Number
- String
- Object
- 操作符
- 流程控制语句
- 函数
1.3 JavaScript 中的数据类型
JavaScript 有 5 种简单数据类型:Undefined、Null、Boolean、Number、String
和 1 种复杂数据类型 Object
。
基本类型(值类型)
- Undefined
- Null
- Boolean
- Number
- String
复杂类型(引用类型)
- Object
- Array
- Date
- RegExp
- Function
- 基本包装类型
- Boolean
- Number
- String
- 单体内置对象
- Global
- Math
类型检测
typeof
instanceof
Object.prototype.toString.call()
值类型和引用类型在内存中的存储方式
- 值类型按值存储:值类型在栈
- 引用类型按引用存储:引用类型地址在栈,对象在堆
值类型复制和引用类型复制
- 值类型按值复制
- 引用类型按引用复制
值类型和引用类型参数传递
- 值类型按值传递
- 引用类型按引用传递
值类型与引用类型的差别
- 基本类型在内存中占据固定大小的空间,因此被保存在栈内存中
- 从一个变量向另一个变量复制基本类型的值,复制的是值的副本
- 引用类型的值是对象,保存在堆内存
- 包含引用类型值的变量实际上包含的并不是对象本身,而是一个指向该对象的指针
- 从一个变量向另一个变量复制引用类型的值的时候,复制是引用指针,因此两个变量最终都指向同一个对象
JavaScript 执行过程
JavaScript 运行分为两个阶段:
- 预解析
- 全局预解析(所有变量和函数声明都会提前;同名的函数和变量函数的优先级高)
- 函数内部预解析(所有的变量、函数和形参都会参与预解析)
- 函数
- 形参
- 普通变量
- 执行
先预解析全局作用域,然后执行全局作用域中的代码。
在执行全局代码的过程中遇到函数调用就会先进行函数预解析,然后再执行函数内代码。
2. JavaScript 面向对象编程
2.1 编程思想
- 面向过程:所有的事情都是亲力亲为,注重的是过程。
- 面向对象:提出需求,找对象,对象解决,注重的是结果。
- 面向对象的编程思想:根据需求,抽象出相关的对象,总结对象的特征和行为,把特征变成属性,把行为变成方法。然后定义(js)构造函数,实例化对象,通过对象调用属性和方法,完成相应的需求。
- js 不是一门面向对象的语言,是基于对象的语言,js可以模拟面向对象。
2.2 面向对象的特性
- 封装:就是代码的封装,把一些特征和行为封装在对象中
- 继承:类与类之间的关系。js中没有类的概念,js有构造函数的概念,是可以有继承的,js的继承是基于原型的
- 多态:同一个行为,针对不同的对象,产生了不同的效果
2.3 创建对象的三种方式
- 字面量的方式
var per1 = {
name:"小张",
age:20,
sex:"男",
readBook:function(){
console.log("西游记")
}
}
- 调用系统的构造函数方法
var per2 = new Object()
per2.name="小张"
per2.age=20
per2.sex="男"
per2.readBook=function(){
console.log("西游记")
}
以上两种方式都是不能确定创建出来的对象是什么类型的
- 自定义构造函数
function Person(name,age,sex){
this.name = name
this.age = age
this.sex =sex
this.readBook= function () {
console.log("西游记")
}
}
var per = new Person("小张",20,"男")
console.log(per instanceof Person)
结果输出 true
- 工厂模式创建对象
function createObejct(name, age, sex) {
var obj = new Object()
obj.name = name
obj.age = age
obj.sex = sex
obj.readBook = function(){
console.log("西游记")
}
return obj
}
var per1=createObejct("小明",20,"男")
自定义构造函数与工厂模式的共同点与不同点:
- 共同点:都是函数,都可以创建对象,都可以传入参数
- 不同点:
- 工厂模式函数名是小写,自定义构造函数函数名是大写
- 工厂模式函数内部 new 对象并返回该对象,自定义构造函数内部没有new对象返回
- 工厂模式 new 之后的对象是当前的对象,自定义构造函数 this 是当前的对象
- 工厂模式是直接调用函数就可以创建对象,自定义构造函数通过 new 的方式来创建对象
2.4 构造函数与实例对象之间的关系
面向对象的思想是:抽象的过程 --> 实例化的过程
自定义构造函数 --> 使用构造函数实例化对象
function Animal(name) {
this.name = name
}
var dog = new Animal("大黄")
console.dir(dog)
console.dir(Animal)
执行结果
console.log(dog instanceof Animal) // true
console.log(dog.constructor == Animal) // true
console.log(dog.__proto__.constructor == Animal) // true
console.log(dog.__proto__.constructor == Animal.prototype.constructor) // true
总结:
- 实例对象是通过构造函数构建的 --- 创建的过程叫做实例化
- 如何判断对象是不是这个数据类型?
- 通过构造器的方式:
实例对象.构造器 == 构造函数名字
- 对象 instanceof 构造函数名字
尽可能用第二种方式来识别,为什么?原型讲完再说
2.5 原型的引入
首先我们来看构造函数创建对象带来的问题:
function Person(name,age,sex){
this.name = name
this.age = age
this.sex = sex
this.readBook = function () {
console.log("西游记")
}
}
var per1 = new Person("小张",20,"男")
var per2 = new Person("小杨",25,"女")
per1.readBook()
per2.readBook()
console.log(per1.readBook == per2.readBook)
执行结果
为什么 per1 和 per2 的 readBook 不相等呢?是因为使用构造函数每创建一个对象就会开辟一块内存空间,而这个对象指向自己对应的那块内存空间。如果我new了100个对象,那么对应的就有一百块内存空间中有一样的 readBook 方法。这样就开辟了大量的控件,浪费内存。那么我们通过原型来解决这个问题。
function Person(name,age,sex){
this.name = name
this.age = age
this.sex = sex
}
Person.prototype.readBook = function(){
console.log("西游记")
}
var per1 = new Person("小张",20,"男")
var per2 = new Person("小杨",25,"女")
per1.readBook()
per2.readBook()
console.log(per1.readBook == per2.readBook)
image.png
-
__proto__
和prototype
都是原型对象,对象的引用指向相同。 - 实例对象中的属性
__proto__
,不是标准的属性,是浏览器使用的。有的浏览器支持,有的浏览器不支持该属性,例如IE8不支持,而火狐和谷歌是支持的。 - 构造函数中的属性
prototype
,是标准属性,程序员使用的。 - 原型的作用:解决数据共享,节省内存空间。
2.6 构造函数、实例对象和原型对象的关系
通过下面这张图来解释这三者的关系:
构造函数、实例对象和原型对象的关系
- 构造函数可以实例化对象。
- 构造函数中有一个属性叫prototype,是构造函数的原型对象。
- 构造函数的原型对象(prototype)中有一个 constructor 构造器,这个构造器指向的就是自己所在的原型对象所在的构造函数。
- 实例对象的原型对象(
__proto__
)指向的是该构造函数的原型对象。 - 构造函数的原型对象中的方法是可以被实例对象直接访问的。
2.7 利用原型共享数据
不需要共享的数据写在构造函数中,需要共享的数据写在原型中。
function Student(name,age,sex){
this.name = name
this.age = age
this.sex = sex
}
Student.prototype.major = "计算机"
Student.prototype.teacher = "王老师"
Student.prototype.study = function(){
console.log("学Javascript")
}
Student.prototype.readBook = function(){
console.log("看编程书")
}
var stu = new Student("小张",20,"男")
console.dir(Student)
console.dir(stu)
执行结果
上面的代码还可以将 prototype 写成一个对象,但是需要在原型对象中需要手动添加构造器为当前的构造函数:
function Student(name,age,sex){
this.name = name
this.age = age
this.sex = sex
}
Student.prototype = {
constructor: Student, //需要手动添加构造器
major: "计算机",
teacher: "王老师",
study: function(){
console.log("学Javascript")
},
readBook: function(){
console.log("看编程书")
}
}
var stu = new Student("小张",20,"男")
console.dir(Student)
console.dir(stu)
2.8 原型中的方法是可以相互调用的
我们知道实例对象的方法是可以相互调用的,像这样:
function Person(name,age,sex){
this.name = name
this.age = age
this.sex = sex
this.readBook = function () {
console.log("西游记")
this.writeBlog()
}
this.writeBlog = function() {
console.log("写观后感")
}
}
var per = new Person("小张",20,"男")
per.readBook()
执行结果
如果把方法定义在原型中,能不能相互调用呢?
原型中的方法是可以相互调用的。
function Animal(name,age) {
this.name=name
this.age=age
}
//原型中添加方法
Animal.prototype.eat=function () {
console.log("动物吃东西")
this.play()
}
Animal.prototype.play=function () {
console.log("玩球")
this.sleep()
}
Animal.prototype.sleep=function () {
console.log("睡觉了")
}
var dog=new Animal("小苏",20)
dog.eat()
执行结果
2.8 实例对象使用属性和方法层层的搜索
实例对象的用到的属性或者方法先在实例对象中找,找到了则直接使用,如果在实例对象中找不到就会去实例对象的__proto__
指向的构造函数的原型对象 prototype 中找,找到了则使用,如果原型对象也没有就会报错不存在。
2.9 为内置对象添加原型方法
我们能否为系统的对象的原型添加方法,这相当于在改变源码。
比如,我希望字符串中有一个倒序字符串的方法,就可以直接在 String 内置对象的原型对象上添加自己的 myReverse 方法:
String.prototype.myReverse=function () {
for(var i=this.length-1;i>=0;i--){
console.log(this[i]);
}
}
var str="abcdefg"
str.myReverse()
或者是为 Array 内置对象增加自己的冒泡排序方法:
Array.prototype.mySort=function () {
for(var i=0;i<this.length-1;i++){
for(var j=0;j<this.length-1-i;j++){
if(this[j]<this[j+1]){
var temp=this[j]
this[j]=this[j+1]
this[j+1]=temp
}
}
}
}
var arr=[100,3,56,78,23,10]
arr.mySort()
console.log(arr)
系统内置对象的属性和方法可能不满足现在需求,所以,为了方便开发,可以通过原型的方式加入属性或者方法。
网友评论