JavaScript基础知识与面向对象编程

@eyasliu 2016-06-11 07:51:45发表于 eyasliu/blog 前端草稿

目标:熟练掌握JavaScript基础知识与面向对象概念

大纲

  • 基本概念
    • 语法
    • 基本数据类型
    • 操作符
    • 引用类型
      • Object
      • Array
      • Function
      • 包装类型
  • 执行环境
    • 闭包
    • this
  • 面向对象
    • 创建对象
      • 工厂模式
      • 构造函数
      • 原型模式
    • 继承
      • 原型链继承
      • 借用构造函数
      • 组合式继承

基本概念

语法

JavaScript的语法借鉴C语言语法,与php基本一致

  • 区分大小写
  • 标识符
  • 注释
  • 语句
  • 关键字

基本数据类型

一共5种基本数据类型

  • Undefined
  • Null
  • Boolean
  • Number
  • String

使用 typeof 可以查看数据类型

Undefined && Null

Undefined 与 Null 类型都是只有一个值

  • Undefined -> undefined
  • Null -> null

undefined 表示变量未定义,或者定义了但是未赋值
null 表示空对象指针,不指向任何对象,本质上他是Object类型

Boolean

Boolean 类型共两个值 truefalse,表示真和假

基本类型转换时,会将以下值转换为false

  • 空字符串 ''
  • 数值 0 和 NaN
  • null
  • undefined

其他都会转为 true

Number

  • 整数
  • 浮点数:由于计算会有误差,不要将浮点数用于判断
  • NaN:非数值(Not a Number),用于本来要返回数值的操作数
    • NaN 自己不等于自己: NaN == NaN // false

数值转换

Number() 可以把任何值转换为数值,parseInt()parseFloat()将字符串转换为数值。

转换规则

  • Boolean值, true -> 1 , false -> 0
  • Number值,原样输出
  • null -> 0
  • undefined -> NaN
  • 字符串
    • 只有数字:字符串变数值 '123', '12.3', '-123', '0xf'
    • 空字符串:0
    • 其他:NaN

String

  • 用单引号或者引号,两者无区别
  • 任何数据类型都有 toString() 方法,可转换为字符串

操作符

  • + - * / ++ -- += -= 等操作符与PHP完全一致
  • ! < > <= >= || && 等于PHP完全一致

tips

  • =====:
    • == 会先将左右两边先转换为相同数据类型后,再比较 2 == [2] // true
    • === 原样比较 2 === '2' // false
  • a || b 当a为true时,完全忽略b,可用于设置默认值 var x = x || 100
  • a && b 当a为true时,执行b,当a为false时,忽略b,可用于判断 x && alert('hello')
  • !!a 用于类型转换为Boolean

引用类型

引用类型是一种数据结构,用于将数据和功能组织在一起。

Object 类型

创建一个Object

// 使用 new
var person = new Object();
person.name = 'Eyas'

// 使用字面量
var person = {
  name: 'Eyas'
}

以面向对象概念理解的话,Object类型是所有引用类型的基类,Array,Function,包装类型等都属于Object的子类

Array 类型

创建Array

// 使用new
var persons = new Array

// 使用字面量
var persons = []

栈方法

push : 将元素添加到数组末尾,返回数组长度
pop: 返回最后一项元素并删除

队列方法

shift: 返回第一项并删除
unshift: 将元素添加带数组第一项,返回数组长度

迭代方法

  • every() 对所有元素运行给定函数,如果所有都返回true,则返回true
  • some() 对所有元素运行给定函数,如果有一项返回true,则返回true
  • filter() 对所有元素运行给定函数,返回所有返回true的项组成数组
  • forEach() 对所有元素运行给定函数,不返回
  • map() 对所有元素运行给定函数,每次的返回结果组成的数组

tips

  • 判断是否为数组,使用 Array.isArray()
  • 转换字符串:默认会以逗号分隔元素 '' + [1,2,3]
  • 排序:reverse() 元素顺序反转,sort()对元素调用toString方法然后确定顺序
  • 合并数组concat() [1,2,3].concat([4,5,6])
  • 查找元素,判断是否有该元素 indexOf() [1,2,3].indexOf(4)

Function

定义

// 函数声明
function say(str){}

// 函数表达式
var say = function(str){}

注意事项

  • 不执行return 以后的语句
  • 没有重载
  • arguments 是参数集合,类数组,但没有push,pop等操作
  • 函数名只是一个指向函数对象的变量

包装类型

为了便于操作基本类型数据,每当读取一个基本类型值的时候,后台会创建一个对应的基本包装类型的对象,让我们能够调用一些方法来操作这些数据

  • Boolean
    • toString 返回字符串 'true' 和 'false'
  • Number
    • toString 将数值转换为字符串
    • toFixed 指定小数位数,以字符串返回
  • String
    • length 字符串长度
    • concat 拼接字符串
    • substr, substring, slice 切割字符串
    • trim 去掉前后空格
    • ...
  • Null 和 Undefined 没有任何可操作方法,他们访问属性会报错

执行环境(作用域)

执行环境定义了变量或函数有权访问的其他数据,决定了他们的各自行为。每个执行环境都有一个与之关联的变量对象,环境中的所有变量和函数都保存在这个变量中,但是我们无法访问这个变量,在解析器执行时会在后台使用

作用域链

每个函数都有自己的执行环境,当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问

var Name = 'Tenmic';
var Version = '1.0.0'

function appName(){
  var fullName = function(name, version){
    return name + ' V' + version
  }

  return fullName(Name, Version)
}

appName() // Tenmic V1.0.0

闭包

闭包是指有权访问另一个函数作用域中的变量的函数,创建方式就是在一个函数内部创建另一个函数

var addNum = function(num){
  return function(num2){
    return num + num2
  }
}

var addOne = addNum(1);
var addTen = addNum(10);

addOne(3)  // 4
addTen(15) // 25

this

this对象是在运行时基于函数的作用域绑定的,在全局函数中,this等于window,当函数被某个对象的方法调用时,this等于那个对象。在编写闭包中,this依情况而定

function say(){
  // this === window
}

// jQuery 
$('body').show()  // show 里面的this指向 $('body')

var obj = {
  name: 'Tenmic',
  getNameFun: function(){
    // this == obj
    return this.name
  }
}

绑定this

在运行中,this变量可以显式的绑定

  • bind通常在定义之后绑定this,暂时不执行
  • call 与 apply在绑定this后立刻执行

面向对象编程

创建对象

工厂模式

// 工厂模式
function createPerson(name, age){
  var o = {
    name: name,
    age: age
  }

  return o;
}
var person = createPerson('Eyas', 24)

构造函数模式

// 构造函数
function Person(name, age){
  this.name = name;
  this.age = age;
  this.getInfo = function(){
    return this.name + '-' + this.age
  }
}

var peroson = new Person('Eyas', 24)
  • 构造函数没有显式创建对象
  • 没有return

new 关键字的执行过程

  1. 创建一个新对象
  2. 将构造函数的作用域赋给新对象(this 指向新对象)
  3. 执行代码
  4. 返回新对象

构造函数就是一个普通的函数,与一般函数没有什么不同,任何函数都可以用 new 来调用。

缺点
每个函数都要创建一次

原型模式

我们创建的每个函数都有一个prototype,这是一个指向一个对象的指针,这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。

prototype 是通过调用构造函数而创建的那个对象实例的原型对象

function Person(){}

Person.prototype.name = 'Eyas';
Person.prototype.age = 24;
Person.prototype.getInfo = function(){
  return this.name + '-' + this.age
}

var person = new Person()

person.name // Eyas
person.getInfo() // Eyas-24
理解原型对象

无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个 prototype 属性,这个属性指向函数的原型对象。所有原型对象自动获得一个constructor属性,constructor指向函数本身

function hello(){}
hello.prototype
hello.prototype.constructor === hello  // true

在查找对象属性时,首先获取对象内部属性,如果获取不到,则沿着原型链逐步往上查找,最终仍未找到则返回undefined

继承

JavaScript是基于原型链的继承。利用原型让一个引用类型继承另一个引用类型的属性和方法。

原型继承

每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。如果我们手动的让原型对象等于另一个原型的实例,此时的原型对象将包含另一个原型的实例。如此层层递进,构成实例与原型的链条,实现了继承

function Person(name){
  this.name = name
  this.getName = function(){
    return this.name
  }
}

function Student(){}
Student.prototype = new Peroson()

var student = new Student
student.getName()  // getName 来自于 Person

借用构造函数继承

很明显的,上述原型继承无法控制Person的传入参数,我们使用借用构造函数方式继承

function Person(name, age){
  this.name = name;
  this.age = age;
}
function Student(){
  Person.apply(this, arguments);
}
Student.prototype = new Person

这种方式,虽然解决了参数传递,但是依然解决得不彻底,参数的限制太大了

组合式继承

function Person(name){
  this.name = name;
  this.age = age;
}
function Student(name, age){
  Person.call(this, name);
  this.age = age
}
Student.prototype = new Person

这种继承方式综合了上述两者继承方式,可以近乎完美实现继承