diff --git a/DraftNote.md b/DraftNote.md new file mode 100644 index 0000000..61e69f0 --- /dev/null +++ b/DraftNote.md @@ -0,0 +1,80 @@ + # 变量 + + ## 松散类型语言 + + `JavaScript`使用的是松散类型变量,所谓松散类型变量指的是变量只是作为一个存储位置的占位符,本身不具备任何决定变量类型的功能。变量具体是什么类型由变量的值直接决定。 + + ## 数据类型 + + `JavaScript`只支持下面的六种数据类型`Undefined/Null/Boolean/Number/String/Object`。因为`JavaScript`本身是一种动态语言,参数使用的也是松散类型参数,所以提供了`typeof`关键字来判断当前的变量类型,随后的返回值在下面进行详细的介绍。 + + - undefined + + 如果一个变量被定义之后却没有赋值,那么初始的值就会被设定成为`Undefined` + + - boolean + + 布尔值,只有`true/false`两种取值情况。 + + - string + + 字符串类型,引号或者是双引号内的所有文本内容 + + - number + + `JavaScript`只支持一种类型的数值,可以带小数点 + + - object + + 如果这个参数是个对象或者`Null`,在`JavaScript`中对象是一个无序键值对 + + - function + + 如果这个值是一个参数,在`JavaScript`中,其实函数也是一个对象 + + # 函数 + + ## 参数 + + `JavaScript`的函数参数和其他语言的不同,不会使用声明函数是填写的命名参数作为函数签名的一部分限制函数的使用。所以函数使用时传入参数的个数和定义函数是填写的命名参数的个数并不是相等的。这是因为函数的参数实际上都是保存在函数内部的一个`arguments`的类数组结构中,通过`arguments.lenght`可以知道传入参数的数量,通过下标可以访问里面对应的某个参数。为了保证函数代码的健壮性,可能需要在执行函数的操作代码之前就要去对函数的参数进行验证。 + + ## 运行环境 + + 这里用运行环境进行作用域的讨论。在`JavaScript`中运行环境只有可能是下面的三种类型:全局环境/函数局部环境/try或with块环境。作用域链是将当前运行环境置于栈顶通过包含关系形成的一个栈,当从目前运行环境中退出时,对应的运行环境也会从作用域链中推出。在使用某个变量之前会从作用域链最前端开始搜索该变量直到找到对应的定义,所以以来这个搜索的过程,被包含的运行环境中定义的变量会覆盖掉包含运行环境中定义的变量。在上面列举运行环境的时候注意到`JavaScript`除了最后两个特殊语句之外是没有块运行环境的,在块运行环境中定义的变量在执行完成该环境之后还能够继续访问而没有被销毁。 + + ## 松耦合 + + 在函数的内部提供了的`caller`/`arguments.callee`属性用来解决函数的松耦合问题。同时可以使用函数对象的`apply()/call()`函数来扩充函数的运行环境。 + + # 引用变量 + + 引用变量实际是对象实例,谈及到对象自然而然也就会涉及到类的讨论。`JavaScript`虽然从技术上来说是支持面向对象概念的,但是其本身并不具备类和接口之类的性质。在`JavaScript`中对象实际上是一组无序的属性值,创建一个引用变量有下面的两种方式。 + + ```JavaScript + + // 方式1 + var person = new Object(); + person.name = "name"; + person.sayHi = function() { + console.log(this.name, 'Hi!'); + } + + // 方式2 + var person = { + name:"name", + sayHi:function() { + console.log(this.name, ' Hi!'); + } + } + ``` + + 第一种方式中`Object`是`JavaScript`几个预定义对象中的一个,利用构造函数创建完对象之后可以再给对象添加自定义的属性;第二种方式是采用字面常量的方式来创建 + + # 构造函数 + + 构造函数定义的方式和定义普通的函数没有什么区别,只是在使用`new`操作符调用构造函数的时候函数名首字母需要大写用来区别普通函数。构造函数在执行的时候进行了下面的这些操作。 + + - 创建一个新的对象 + - 将构造函数的作用域赋给新对象 + - 执行构造函数中的代码 + - 返回新对象 diff --git a/Examples/CreatePattern.js b/Examples/CreatePattern.js new file mode 100644 index 0000000..ac124c3 --- /dev/null +++ b/Examples/CreatePattern.js @@ -0,0 +1,15 @@ +function createNewPerson() { + var person = new Object(); + person.name = "name"; + person.age = 10; + person.sayHi = function() { + console.log(this.name + " say Hi!"); + } + return person; +} + +var person = createNewPerson(); +person.sayHi(); + +console.log('person.constrator == Object(): ' , person.constructor == Object); +console.log('person instanceof Object: ' , person instanceof Object); diff --git a/Examples/Objects.js b/Examples/Objects.js new file mode 100644 index 0000000..9491b57 --- /dev/null +++ b/Examples/Objects.js @@ -0,0 +1,38 @@ +// var person = { +// _age:10 +// }; +// Object.defineProperty(person, "age", +// {get:function(){ +// return this._age + "岁"; +// }, +// set:function(newValue) { +// if (newValue < 0) { +// newValue = 0; +// }else if (newValue > 100) { +// newValue = 100; +// } +// this._age = newValue; +// }}); +// +// person.age = 101; + +// 构造函数的使用 + +function Person(name, age) { + this.name = name; + this.age = age; + this.sayHi = function() { + var temp = 'name: ' + this.name + " age: " + age + " say Hi!"; + console.log(temp); + } +} + +var person1 = new Person("name1", 12); +var person2 = new Person("name2", 13); + +person1.sayHi(); +person2.sayHi(); + +console.log('person1.constructor is Person : ' + person1.constructor == Person); +console.log('person2.constructor is Person : ' + person2.constructor == Person); +console.log('person1.constructor: ' + person1.constructor); diff --git a/Examples/Prototypes.js b/Examples/Prototypes.js new file mode 100644 index 0000000..29c6ed7 --- /dev/null +++ b/Examples/Prototypes.js @@ -0,0 +1,6 @@ +function Person() {}; +console.log(Person.prototype); +console.log(Person.prototype.constructor); + +var person1 = new Person(); +console.log(Object.getPrototypeOf(person1)); diff --git a/Examples/RegExp.js b/Examples/RegExp.js new file mode 100644 index 0000000..d81f5a8 --- /dev/null +++ b/Examples/RegExp.js @@ -0,0 +1,9 @@ +var text = "mom and dad and baby adksdkfsjk"; +var re = /mom( and dad( and baby)?)?/gi; + +var matches = re.exec(text); + +console.log(matches.index); +console.log(matches.input); +console.log(matches.length); +console.log(matches.toString()); diff --git a/OOP.md b/OOP.md new file mode 100644 index 0000000..d941feb --- /dev/null +++ b/OOP.md @@ -0,0 +1,230 @@ +> 关于`JavaScript`的面向对象编程设计 + +# 对象 + +> 无序的属性集合,其属性可以是基本值、对象或者函数。 + +上述是`ECMA-262`对于对象的定义,在对象中放置了`JavaScript`支持的所有类型的属性,通过每个属性对应的名字使用`.`操作符进行访问。下面的实例代码是`JavaScript`中创建对象的两种方式。 + +```javascript + // 使用构造函数 + var person = new Object(); + person.name = "name"; + person.age = 12; + person.job = "Software Engineer"; + person.sayName = function() { + console.log(this.name); + }; + + // 使用字面量 + var person = { + name:'name', + age:12, + job:'Software Engineer', + sayName:function() { + console.log(this.name); + } + }; +``` + +## 属性特征 + +对象的属性本身具备有一些控制特征,这些特征会影响该属性在使用过程的中的访问和操纵流程,下面针对这些特征一个个进行介绍: + +- `configurable` + + 控制该属性是否可以使用`Object.defineProperty()`函数进行属性的配置,或者是使用`delete`标识符进行属性删除 + +- `writable` + + 控制该属性是否可写可改,如果将这个特征置为`false`可以将其理解为`java`中的`final` + +- `enumerable` + + 表示该属性是否可以出现在`for-in`语句中 + +- `value` + + 会在这个特征位上置入当前属性对应的属性值 + +- `setter/getter` + + 控制对该属性的访问写入操作,后面直接用代码解释其作用即可 + +上面的这些特征都会影响到对象属性的使用方式,而这些对象的特征都是通过`Object.defineProperty`或者是`Object.defineProperties`函数进行定义。在创建对象的属性是,前面的三个属性都是默认为`true`,可以通过`Object.getOwnPropertyDescriptor`函数 + +```javascript + // 缩进 + var person = new Object(); + person.name = "name"; + console.log(person.name); // output: name + var descriptor = Object.getOwnPropertyDescriptor(person, "name"); + console.log(descriptor); // output:{ value: 'name', writable: true, enumerable: true, configurable: true } + + Object.defineProperty(person, "name", {writable:false}); + person.name = "name2"; + console.log(person.name); // output:name + Object.defineProperty(person, "name", {writable: true}); + person.name = "name2"; + console.log(person.name); // output:name2 + + delete person.name // output:true + console.log(person.name) // output:undefined + person.name = "name" + Object.defineProperty(person, "name", {configurable:false}); + delete person.name + Object.defineProperty(person, "name", {configurable:true}); // TypeError: Cannot redefine property:name + + person._age = 10; + descriptor = Object.getOwnPropertyDescriptor(person, "_age"); + console.log(descriptor); // output:{ value: 10, writable: true, enumerable: true, configurable: true } + Object.defineProperty(person, "age", { + get:function() { + return this._age; + }, + set:function(newValue) { + if (newValue < 0) { + newValue = 0; + }else if (newValue > 100) { + newValue = 100; + } + this._age = newValue; + } + }) + console.log(person.age); // output:10 + person.age = -1; + console.log(person.age); // output:0 + person.age = 101; + console.log(person.age); // output:100 + descriptor = Object.getOwnPropertyDescriptor(person, "age"); + console.log(descriptor); // output:{ get: [Function], set: [Function], enumerable: false, configurable: false } + + // 同时定义多个属性 + Object.defineProperties(person, { + height:{ + configurable:true, + writable:true, + value:170 + }, + weight:{ + configurable:true, + writable:true, + value:50 + } + }) + console.log(person.height); // output:170 + console.log(person.weight); // output:50 +``` + +上面的示例代码除了展示几个属性特征的作用之外,同时要关注的还有三个比较关键的`API`函数的使用 + +```javascript +// 同时为对象定义多个属性 +Object.defineProperties(obj: ?, props: ?) + +// 为对象定义某个对象 +Object.defineProperty(obj: ?, prop: string, desc: ?) + +// 获取某个对象特征的描述 +Object.getOwnPropertyDescriptor(obj: ?, prop: string) +``` + +## 创建方式 + +### 工厂模式 + +粗糙,只是代码的封装 + +### 构造函数模式 + +创建多余的函数对象,拥有一个`constructor`属性来进行类型判断 + +### 原型模式 + +利用构造函数的原型对象解决构造函数模式中的重复定义问题 + +![原型关系图](/images/oop_prototype.png) + +上面的图片描述的是构造函数,对象实例以及构造函数的原型对象之间的关系。这其中蕴含的各种规则下面会一条条进行描述 + +1. 每个函数定义之后都会有一个`prototype`属性指向其对应的原型对象,这个原型对象用于放置该函数构造出来的实例对象所共享的内容,继承自`Object` +2. 函数的原型对象默认会持有一个`constructor`属性指向定义的函数 +3. 实例对象默认会有个`__proto__`属性指向对应构造函数的原型对象,但是这个属性不能够直接访问,只能够通过`Object.getPrototypeOf`接口访问 +4. 在原型对象中放置的属性会被对应的实例对象共享,所以可以直接通过实例对象进行属性的访问;在访问某个实例对象的属性时,首先会查找该实例对象是否定义了这个属性,如果没有再找其原型对象中是否存在;虽然实例对象可以访问原型对象的属性,但是不能够直接对其进行修改,如果使用赋值操作就会变成在实例对象中定义一个相同名称的属性并且会将原型对象中的属性覆盖掉,导致以后访问该实例对象的这个属性时得到的都是实例对象本身的属性而不是原型对象中共享的内容。即使后来将这个属性设置为`Null`也不能改变这种情况,只能通过`delete`操作删除属性后才可以访问到原型对象中的内容。 +5. 关于属性的操作,可以通过`Object.hasOwnProperty`来判断实例对象是否有这个接口,也可以通过`in`操作符判断在能否通过该对象访问到对应的实例,不管是在实例对象中还是在原型对象中,此外,之前提及到的`Object.getOwnPropertyDescriptor`接口只能够获取到实例对象包含属性的描述而不包括原型对象,如果希望获取到原型对象的属性描述,需要直接调用原型对象的引用 + +```javascript +function Person() {}; + +//构造函数、原型对象、实例对象之间的关系 +console.log(Person.prototype); // output:Person{} +console.log(Person.prototype.constructor); // output:[Function:Person] +var person1 = new Person(); +console.log(Object.getPrototypeOf(person1)); // output:Person{} +console.log(Person.prototype.isPrototypeOf(person1)); // output:true + +// 属性的检索方式和覆盖 +console.log(person1.name); // output:undefined +Person.prototype.name = 'prototype'; +console.log(person1.name); // output:prototype +person1.name = 'person1'; +console.log(person1.name); // output:person1 +person1.name = null; +console.log(person1.name); // output:null +delete person1.name; +console.log(person1.name); // output:prototype + +// 属性位置判断 +person1.age = 10; +console.log(person1.hasOwnProperty('name')); // output:false +console.log(person1.hasOwnProperty('age')); // output:true +console.log('name' in person1); // output:true +console.log('age' in person1); // output:true +``` + +上面基本已经介绍完了所有和原型相关的东西,但是最后再提及到的一点是当我们希望去改写某个原型对象的时候,如果仅仅添加或者修改属性是没有问题的,但是如果通过其他的方法重新创建了一个原型对象,那么这个时候就要注意在创建这个新的原型对象之前所创建的实例对象所指向的原型还是之前的旧版本的原型对象,所以此后所有的原型更新都不会在实例上体现出来。 + +### 动态原型模式 + +动态原型模式利用构造函数创建对象本身的独有属性,在通过判断公共属性是否已经存在从而决定需不需要使用原型对象创建公有属性 + +```javascript +function Person() { + this.name = ''; + this.age = 0; + this.job = ''; + if (typeof this.sayName != function) { + Person.prototype.sayName = function(){ + console.log(this.name); + } + } +} +``` + +## 继承 + +为了实现集成,最基本的思想是使用原型链。将父类的实例作为自类型的原型对象,这样子类型中可以拥有所有的父类的属性和方法。 + +```javascript +function SuperType() { + this.label = 'label'; +} +SuperType.prototype.sayLabel = function(){ + console.log(this.label); +} + +var superType = new SuperType(); +superType.sayLabel(); // output:label + +function SubType(){ + this.name = 'SubType'; +} +SubType.prototype = super; +SubType.prototype.sayName = function(){ + console.log(this.name); +} + +var sub = new SubType(); +sub.sayName(); // output:SubType +sub.sayLabel(); // output:label +``` diff --git a/images/oop_prototype.png b/images/oop_prototype.png new file mode 100644 index 0000000..8c1d8ce Binary files /dev/null and b/images/oop_prototype.png differ