1.面向过程与面向对象
1.1面向过程 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了。 面向过程是 实现代码逻辑的 步骤,主要是把重心点放在实现功能上,不太去考虑封装 优点: 效率高,但是维护起来太麻烦 1.2面向对象 面向对象是把事务分解成一个个对象,然后由对象之间分工合作。 主要的重心点,先放在功能模块的划分,然后想着什么功能模块可以去进行复用(可以进行抽取) 优点: 会让我们功能具备模块化开发,一个模块就负责一个功能,后续方便我们的维护 三大特性:继承性、封装性、多态性(抽象性)
2.对象与类
2.1 对象
对象是由属性和方法组成的:是一个无序键值对的集合,指的是一个具体的事物
属性:事物的特征,在对象中用属性来表示(常用名词)
方法:事物的行为,在对象中用方法来表示(常用动词)
创建对象的两种方法
1.字面量创建对象
var ldh = {
name: '刘德华',
age: 18
}
console.log(ldh);
2.构造函数创建对象
function Star(uname,age){this.uname = name; this.age = age; } var ldh = new Star('刘德华',18); //实例化对象 console.log(ldh); 2.2 类 在 ES6 中新增加了类的概念,可以使用 class 关键字声明一个类,之后以这个类来实 例化对象。类抽象了对象的公共部分,它泛指某一大类(class)对象特指某一个, 通过类实例化一个具体的对象 2.2.1创建类 语法: //步骤1 使用class关键字 class name { // class body } //步骤2使用定义的类创建实例 注意new关键字 var xx = new name(); 2.2.2类创建添加属性和方法 // 1. 创建类 class 创建一个类 class Star { // 类的共有属性放到 constructor 里面 constructor是 构造器或者构造函数 constructor(uname, age) { this.uname = uname; this.age = age; }//‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐>注意,方法与方法之间不需要添加逗号 sing(song) { console.log(this.uname + '唱' + song); } } // 2. 利用类创建对象 new 实例化 var ldh = new Star('刘德华', 18); console.log(ldh); // Star {uname: "刘德华", age: 18} ldh.sing('冰雨'); // 刘德华唱冰雨 注意点: 1. 通过class 关键字创建类, 类名我们还是习惯性定义首字母大写 2. 类里面有个constructor 函数,可以接受传递过来的参数,同时返回实例对象 3. constructor 函数 只要 new 生成实例时,就会自动调用这个函数, 如果我们不写这个函数,类也会自动生成这个函数 4. 多个函数方法之间不需要添加逗号分隔 5. 生成实例 new 不能省略 6. 语法规范, 创建类 类名后面不要加小括号,生成实例 类名后面加小括号, 构造函数不需要加function 2.2.3 类的继承 语法 : class Father { }; // 父类 class Son extends Father { } // 子类继承父类 extends 示例 : class Father { constructor(surname) { this.surname= surname; } say() { console.log('你的姓是' + this.surname); } } class Son extends Father{ // 这样子类就继承了父类的属性和方法 } var damao= new Son('刘'); damao.say(); // 输出结果是 你的姓是刘 子类使用super关键字访问父类的方法 //定义了父类 class Father { constructor(x, y) { this.x = x; this.y = y; } sum() { console.log(this.x + this.y); } } //子元素继承父类 class Son extends Father { constructor(x, y) { super(x, y); //使用super调用了父类中的构造函数 } } var son = new Son(1, 2); son.sum(); //结果为3 注意: 1. 继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的 2. 继承中,如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则) 3. 如果子类想要继承父类的方法,同时在自己内部扩展自己的方法,利用super 调用父类的构造函数,super 必须在子类this之前调用 2.2.4 时刻注意this的指向问题 我们在类中,去使用属性或者是调用方法千万不要忘记了用 this, 需要去关注的就是this的指向问题: 默认情况下,类中的this都指向的是当前实例化的对象, 除非 绑定事件之后this指向的就是触发事件的事件源
3.构造函数和原型
3.1 静态成员和实例成员
实例成员 :实例成员就是构造函数内部通过this添加的成员,实例成员只能通过实例化的对象来访问
以下代码里的 uname age sing就是实例成员
function Star(uname, age) { this.uname = uname; this.age = age; this.sing = function() { console.log('我会唱歌'); } } var ldh = new Star('刘德华', 18); console.log(ldh.uname); //实例成员只能通过实例化的对象来访问
静态成员 :在构造函数本身上添加的成员 静态成员只能通过构造函数来访问 在ES5中只要被static修饰的属性和方法都是静态成员
下列代码中 sex 就是静态成员
function Star(uname, age) { this.uname = uname; this.age = age; this.sing = function() { console.log('我会唱歌'); } } Star.sex = '男'; var ldh = new Star('刘德华', 18); console.log(Star.sex); //静态成员只能通过构造函数来访问
3.2构造函数的问题
存在浪费内存的问题
function Star(uname , age) { this.uname = uname; this.age = age; this.sing = function(){ console.log('我会唱歌') } } var ldh = new Star("刘德华",18); var shuji= new Star("书记",22); ldh.sing(); shuji.sing();
以上代码在调用sing方法时都会在内存开辟一个新的空间,如果有多个对象来调用sing方法就会占用过多内存,所以我们就用到了构造函数的原型 prototype。
3.3 构造函数原型prototype
构造函数通过原型分配的函数是所有对象所共享的。
JavaScript 规定,每一个构造函数都有一个prototype 属性,指向另一个对象。注意这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。
我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
构造函数.prototype就可以获取原型对象
function Star(uname, age) { this.uname = uname; this.age = age; } Star.prototype.sing = function() { console.log('我会唱歌'); } var ldh = new Star('刘德华', 18); var zxy = new Star('张学友', 19); ldh.sing(); //我会唱歌 zxy.sing(); //我会唱歌
3.4 对象原型
实例化对象都会有一个__proto__属性指向构造函数的原型对象,而我们的实例化对象可以使用对象原型的属性和方法就是因为有__proto__属性的存在
实例化对象.__proto__ === 构造函数.prototype
它们的三角关系如下图:
__proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象prototype
3.5 constructor构造函数
对象原型( __proto__)和构造函数(prototype)原型对象里面都有一个属性constructor 属性
constructor 我们称为构造函数,因为它指回构造函数本身。
constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数 function Star(uname, age) { this.uname = uname; this.age = age; } // 很多情况下,我们需要手动的利用constructor 这个属性指回 原来的构造函数 Star.prototype = { // 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数 constructor: Star, // 手动设置指回原来的构造函数 sing: function() { console.log('我会唱歌'); }, movie: function() { console.log('我会演电影'); } } var zxy = new Star('张学友', 19); console.log(zxy)
3.6 原型链和查找机制
每一个实例对象又有一个proto属性,指向的构造函数的原型对象,构造函数的原型对象也是一个对象,也有proto属性,这样一层一层往上找就形成了原型链。
查找机制:
当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性,如果自身有,就有自身的属性和方法。
如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)。
如果还没有就查找原型对象的原型(Object的原型对象)。依此类推一直找到 Object 为止(null)。
如果找到尽头(null)都没找到,是属性则返回undefined,是方法则报错
3.7 原型对象中this的指向
构造函数中的this和原型对象的this,都指向我们new出来的实例对象
3.8 通过原型对象为数组扩展内置方法
var arr = [3,6,9,8,5] arr.prototype.sum = function() { var sum = 0; for (var i = 0; i < this.length; i++) { sum += this[i]; } return sum; }; //此时数组对象中已经存在sum()方法了 可以用于数组.sum()进行数据的求和
4.继承
4.1 call()
call()可以调用函数 可以修改this的指向,使用call()的时候,()里的第一个参数是修改的this指向,参数2 参数3 使用逗号隔开
function fn(x, y) { console.log(this); console.log(x + y); } var o = { name: 'andy' }; fn.call(o, 1, 2); //调用了函数此时的this指向了对象o,
4.2 构造函数继承属性
1. 先定义一个父构造函数 2. 再定义一个子构造函数 3. 子构造函数继承父构造函数的属性(使用call方法)// 1. 父构造函数 function Father(uname, age) { // this 指向父构造函数的对象实例 this.uname = uname; this.age = age; } // 2 .子构造函数 function Son(uname, age, score) { // this 指向子构造函数的对象实例 //3.使用call方式实现子继承父的属性 Father.call(this, uname, age); this.score = score; } var son = new Son('刘德华', 18, 100); console.log(son);
4.3 借用原型对象继承方法
(1). 先定义一个父构造函数 (2). 再定义一个子构造函数 (3). 子构造函数继承父构造函数的属性(使用call方法)// 1. 父构造函数 function Father(uname, age) { // this 指向父构造函数的对象实例 this.uname = uname; this.age = age; } Father.prototype.money = function() { console.log(100000); };//子构造函数 function Son(uname, age , score){ // this 指向子构造函数的对象实例 Father.call(this, uname, age); this.score = score; };
// Son.prototype = Father.prototype; 这样直接赋值会有问题,如果修改了子原型对 象,父原型对象也会跟着一起变化 Son.prototype = new Father(); // 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数 Son.prototype.constructor = Son; // 这个是子构造函数专门的方法 Son.prototype.exam = function() { console.log('孩子要考试'); } var son = new Son('刘德华', 18, 100); console.log(son);
5.ES5新增方法
5.1 forEach()遍历数组 没有返回值
array.forEach(function(value , index , array){....})
value是每个数组元素
index是每个元素索引值
array是当前数组
5.2 filter()筛选数组 其实也会遍历数组
array.filter(function(value , index , array){
return 条件表达式
})
返回的是一个新数组 括号跟的参数都是一样的
5.3 some() 查找数组中是否有满足条件的元素 其实也会遍历数组
array.some(function(value , index , array){
return 条件表达式
})
返回值是布尔值,只要查找到第一个满足条件的元素就会终止循环 效率高
5.4 trim方法去除字符串两端的空格
字符串的特性 不可变性
var str = ' hello ' ; console.log(str.trim()) //hello 去除两端空格 var str1 = ' he l l o ' ; console.log(str1.trim()) //he l l o 去除两端空格
5.5 获取对象的属性名
Object.keys(对象) 获取到当前对象中的属性名 ,返回值是一个数组
var obj = { id: 1, pname: '小米', price: 1999, num: 2000 }; var result = Object.keys(obj); console.log(result) //返回的新数组[id,pname,price,num]
5.6 Object.defineProperty设置或修改对象中的属性
Object.defineProperty(对象,修改或新增的属性名,{ value:修改或新增的属性的值, writable:true/false, //如果值为false 不允许修改这个属性值 enumerable: false, //enumerable 如果值为false 则不允许遍历 configurable: false //configurable 如果为false 则不允许删除这个属性 属性是否可以被删除或是否可以再次修改特性 })