面向对象
对象----是由属性和方法组成的:是一个无序键值对的集合,指的是一个具体的事物
- 属性:事物的特征,在对象中用属性来表示(常用名词)
- 方法:事物的行为,在对象中用方法来表示(常用动词)
面向对象---- 把具体事务分解成一个个对象,然后由对象之间分工合作.易维护、易复用、易扩展,由于面向对象有 封装、继承、多态性的特性,可以设计出低 耦合的系统,使系统 更加灵活、更加易于维 护,但是性能比面向过程低.
面向过程---- 就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现, 使用的时候再一个一个的依次调用就可以了.性能比面向对象高,适合跟 硬件联系很紧密的东西,例 如单片机就采用的面向过程 编程。但是不易维护,不易服用,不易扩展
类
在 ES6 中新增加了类的概念,可以使用 class 关键字声明一个类,之后以这个类来实 例化对象。类抽象了对象的公共部分,它泛指某一大类(class)对象特指某一个, 通过类实例化一个具体的对象
语法:
//步骤1 使用class关键字
class name { // class body }
//步骤2使用定义的类创建实例 注意new关键字
var xx = new name();
创建类并添加属性和方法
// 1. 创建类 class 创建一个 明星类
class Star { // 类的共有属性放到 constructor 里面
constructor(name, age) {
this.name = name;
this.age = age;
}
//‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐>注意,方法与方法之间不需 要添加逗号
sing(song) {
console.log(this.name + '唱' + song);
}
}
// 2. 利用类创建对象 new
var ldh = new Star('刘德华', 18);
console.log(ldh);// Star {uname: "刘德华", age: 18}
ldh.sing('冰雨'); // 刘德华唱冰雨
注意:
- 通过class 关键字创建类, 类名我们还是习惯性定义首字母大写
- 类里面有个constructor 函数,可以接受传递过来的参数,同时返回实例对象
- constructor 函数 只要 new 生成实例时,就会自动调用这个函数, 如果我们不写这个 函数,类也会自动生成这个函数
- 多个函数方法之间不需要添加逗号分隔
- 生成实例 new 不能省略
- 语法规范, 创建类 类名后面不要加小括号,生成实例 类名后面加小括号, 构造函数不需 要加function
类的继承
// 父类
class Father{ }
// 子类继承父类
class Son extends Father { }
子类继承父类的属性和方法
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
注意:
-
继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行 子类的
-
继承中,如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类的这 个方法(就近原则)
-
如果子类想要继承父类的方法,同时在自己内部扩展自己的方法,利用super 调用 父类的构造函数,super 必须在子类this之前调用
-
时刻注意this的指向问题,类里面的共有的属性和方法一定要加this使用.
- constructor中的this指向的是new出来的实例对象
- 自定义的方法,一般也指向的new出来的实例对象
- 绑定事件之后this指向的就是触发事件的事件源
-
在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象
静态成员和实例成员
实例成员就是构造函数内部通过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); //实例成员只能通过实例化的对象来访问
静态成员 在构造函数本身上添加的成员 如下列代码中 sex 就是静态成员,静态成员只能 通过构造函数来访问
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function () {
console.log('我会唱歌');
}
}
var ldh = new Star('刘德华', 18);
Star.sex = '男';
console.log(Star.sex);//静态成员只能通过构造函数来访问
console.log(ldh.uname); //实例成员只能通过实例化的对象来访问
构造函数
构造函数原型prototype,原型对象
构造函数的函数方法每次new一个对象的时候都会在内存中分配内存空间,非常浪费内存.
通过构造函数原型分配的函数方法是所有对象所共享的.
在javascript中规定,每一个构造函数都有一个prototype属性,指向一个对象,这个对象的所有属性和方法都会被构造函数所拥有.
可以把一些不变的方法,定义在prototype对象上,这样所有的实例对象就可以共享这些方法.
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype.sing = function () {
console.log(this.uname + '会唱歌');
}
var ldh = new Star('刘德华', 18);
ldh.sing(); //刘德华会唱歌
对象原型
对象都会有一个属性__proto__指向构造函数的prototype原型对象,我们可以使用构造函数prototype原型对象的属性和方法,就是因为对象有__proto__原型的存在.
__proto__对象原型和prototype原型对象是等价的.
__proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是 它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype
constructor构造函数
- 对象原型( __ proto __)和构造函数(prototype)原型对象里面都有一个属性 constructor 属性 ,constructor 我们–称为构造函数,因为它指回构造函数本身。
- constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。 一般情况下,对象的方法都在构造函数的原型对象中设置。如果有多个对象的方法,我们可 以给原型对象采取对象形式赋值,但是这样就会覆盖构造函数原型对象原来的内容,这样修 改后的原型对象 constructor 就不再指向当前构造函数了。此时,我们可以在修改后的 原型对象中,添加一个 constructor 指向原来的构造函数
如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用 constructor指回原来的构造函数如
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype = {
constructor: Star,
sing: function () {
console.log(this.uname + '唱歌');
},
movie: function () {
console.log(this.uname + '演电影');
}
}
var ldh = new Star('刘德华', 18);
var zxy = new Star('学友', 18);
ldh.sing(); //刘德华唱歌
zxy.movie();//学友演电影
原型链
每一个实例对象都有一个__proto__属性,指向构造函数的原型对象,构造函数的原型对象也是一个对象,也有__proto__属性,这样通过__proto__一层一层往上查找就形成了原型链.
原型链和成员的查找机制 (就近原则)
当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)。
如果还没有就查找原型对象的原型(Object的原型对象)。
依此类推一直找到 Object 为止(null)。
__proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
构造函数实例和原型对象三角关系
1.构造函数的prototype属性指向了构造函数原型对象
2.实例对象是由构造函数创建的,实例对象的__proto__属性指向了构造函数的原型对象
3.构造函数的原型对象的constructor属性指向了构造函数,实例对象的原型的 constructor属性也指向了构造函数
原型对象中this指向
构造函数中的this和原型对象中的this都指向new出来的实例对象.
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
var that;
Star.prototype.sing = function () {
that = this;
}
var ldh = new Star('刘德华', 18);
ldh.sing();
console.log(that === ldh);
子构造函数继承父构造函数中的属性
- 先定义一个父构造函数
- 再定义一个子构造函数
- 子构造函数继承父构造函数的属性(使用call方法)
function Father(x, y) {
this.x = x;
this.y = y;
}
function Sun(x, y, z) {
Father.call(this, x, y);
this.z = z;
}
var s = new Sun(1, 2, 3);
console.log(s.x);//1
借用原型对象继承方法
// 1. 父构造函数
function Father(uname, age) {
// this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
Father.prototype.money = function () { console.log(100000); };
// 2 .子构造函数
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);
ES5新增方法
forEach()
forEach() 方法用于遍历数组的每个元素,并将元素传递给回调函数。
注意: forEach() 对于空数组是不会执行回调函数的。
var numbers = [4, 9, 16, 25];
numbers.forEach((item, index) => {
console.log('item:' + item + '--index:' + index);
});
// item: 4--index: 0
// item: 9--index: 1
// item: 16--index: 2
// item: 25--index: 3
filter()
filter() 方法返回一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
注意: filter() 不会对空数组进行检测。
注意: filter() 不会改变原始数组。
var ages = [32, 33, 16, 40];
var newArr = ages.filter((age) => {
return age >= 18;
});
console.log(newArr);//[32, 33, 40]
find()
find() 方法返回通过测试(函数内判断)的数组的第一个元素的值。
find() 方法为数组中的每个元素都调用一次函数执行:
当数组中的元素在测试条件时返回 true 时, find() 返回符合条件的元素,之后的值不会再调用执行函数。
如果没有符合条件的元素返回 undefined
注意: find() 对于空数组,函数是不会执行的。
注意: find() 并没有改变数组的原始值。
var ages = [3, 10, 18, 20];
var age = ages.find((item) => {
return item >= 18;
});
console.log(age);//18
findIndex()
findIndex() 方法返回传入一个测试条件(函数)符合条件的数组第一个元素位置。
findIndex() 方法为数组中的每个元素都调用一次函数执行:
当数组中的元素在测试条件时返回 true 时, findIndex() 返回符合条件的元素的索引位置,之后的值不会再调用执行函数。
如果没有符合条件的元素返回 -1
注意: findIndex() 对于空数组,函数是不会执行的。
注意: findIndex() 并没有改变数组的原始值。
var ages = [3, 10, 18, 20];
var age = ages.findIndex((item) => {
return item >= 18;
});
console.log(age);//2
from()
定义和用法
from() 方法用于通过拥有 length 属性的对象或可迭代的对象来返回一个数组。
如果对象是数组返回 true,否则返回 false。
var myArr = Array.from("RUNOOB");
console.log(myArr);//["R", "U", "N", "O", "O", "B"]
map()
定义和用法
map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。
map() 方法按照原始数组元素顺序依次处理元素。
注意: map() 不会对空数组进行检测。
注意: map() 不会改变原始数组。
var numbers = [4, 9, 16, 25];
var newArr = numbers.map((item) => {
return item * 2;
});
console.log(newArr);//[8, 18, 32, 50]
reduce()
定义和用法
reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
reduce() 可以作为一个高阶函数,用于函数的 compose。
注意: reduce() 对于空数组是不会执行回调函数的。
var numbers = [1, 2, 3, 4];
var sum = numbers.reduce((total, num) => {
return total + num
});
console.log(sum);//10
some()
some() 方法用于检测数组中的元素是否满足指定条件(函数提供)。
some() 方法会依次执行数组的每个元素:
如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测。
如果没有满足条件的元素,则返回false。
注意: some() 不会对空数组进行检测。
注意: some() 不会改变原始数组。
var ages = [3, 10, 18, 20];
var flag = ages.some((item) => {
return item >= 30;
});
console.log(flag);//false
trim()
定义和用法
trim() 方法用于删除字符串的头尾空格。
trim() 方法不会改变原始字符串。
var str = " Runoob ";
console.log(str.trim());//Runoob
Object.keys()
Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和使用 for…in 循环遍历该对象时返回的顺序一致 。如果对象的键-值都不可枚举,那么将返回由键组成的数组。
// simple array
var arr = ['a', 'b', 'c'];
console.log(Object.keys(arr)); // console: ['0', '1', '2']
// array like object
var obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.keys(obj)); // console: ['0', '1', '2']
// array like object with random key ordering
var anObj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.keys(anObj)); // console: ['2', '7', '100']
// getFoo is a property which isn't enumerable
var myObj = Object.create({}, {
getFoo: {
value: function () { return this.foo; }
}
});
myObj.foo = 1;
console.log(Object.keys(myObj)); // console: ['foo']
Object.defineProperty
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
该方法允许精确添加或修改对象的属性。通过赋值操作添加的普通属性是可枚举的,能够在属性枚举期间呈现出来(for…in 或 Object.keys 方法), 这些属性的值可以被改变,也可以被删除。这个方法允许修改默认的额外选项(或配置)。默认情况下,使用 Object.defineProperty() 添加的属性值是不可修改的。
var obj = {
name: 'zs',
age: 18
}
Object.defineProperty(obj, 'sex', {
enumerable: true,
configurable: false,
writable: false,
value: "男"
});
var arr = Object.keys(obj);
console.log(arr);//["name", "age"],enumerable改为true时结果为["name", "age", "sex"]
函数内部的this指向
改变函数内部this指向
call()方法-------apply()方法--------bind()方法
1.三个方法都可以改变函数的this指向.
以上出了 bind 方法后面多了个 () 外 ,结果返回都一致!
由此得出结论,bind 返回的是一个新的函数,你必须调用它才会被执行。
2,对比call 、bind 、 apply 传参情况下
call 、bind 、 apply 这三个函数的第一个参数都是 this 的指向对象,第二个参数差别就来了:
call 的参数是直接放进去的,第二第三第 n 个参数全都用逗号分隔,直接放到后面 obj.myFun.call(db,‘成都’, … ,‘string’ )。-------应用场景: 经常在子构造函数继承父构造函数时使用
apply 的所有参数都必须放在一个数组里面传进去 obj.myFun.apply(db,[‘成都’, …, ‘string’ ])。------应用场景: 经常跟数组有关系
bind 不会调用函数,但是能改变函数内部this 指向,返回的是原函数改变this之后产 生的新函数,它 的参数和 call 一样。-----应用场景:不调用函数,但是还想改变this指向,比如改变定时器内部的this指向
闭包
闭包直观来说就是形成一个不销毁的栈环境,延伸变量的作用范围.
是一种保护私有变量的机制,在函数执行时形成私有的作用域,保护里面的私有变量不受外部干扰.
自调用函数就是一种闭包现象
var add = (function () {
var counter = 0;
return function () {return counter += 1;}
})();
add();
add();
add();
// 计数器为 3
变量 add 指定了函数自我调用的返回字值。
自我调用函数只执行一次。设置计数器为 0。并返回函数表达式。
add变量可以作为一个函数使用。非常棒的部分是它可以访问函数上一层作用域的计数器。
这个叫作 JavaScript 闭包。它使得函数拥有私有变量变成可能。
计数器受匿名函数的作用域保护,只能通过 add 方法修改。
递归
递归:一个函数在函数内部调用其本身,那么这个函数就是递归函数.简单来说就是函数内部自己调用自己就是递归函数.
注意:递归函数的作用跟循环一样,如果函数内部不加return退出条件,很容易发生"栈溢出"错误(stack overflow)
//利用递归函数求1~n的阶乘 1 * 2 * 3 * 4 * ..n
function fn(n){
//结束条件
if (n === 1) {
return n;
}
return n * fn(n - 1);
}
console.log(fn(4));//24
/*利用递归函数求斐波那契数列(兔子序列) 1、1、2、3、5、8、13、21... 用户输入一个数字 n 就可以求出 这个数字对应的兔子序列值
我们只需要知道用户输入的n 的前面两项(n‐1 n‐2)就可以计算出n 对应的序列值*/
function fn(n){
//结束条件
if (n===1 || n===2) {
return 1;
}
return fn(n - 1) + fn(n - 2);
}
console.log(fn(5));//5
正则表达式
正则表达式用于对字符串进行模式匹配及检索替换,是对字符串执行模式匹配的强大工具。
ES6新增语法
动态计算属性
let 和 const
这两个关键字都是用来声明变量的.
let 声明的变量只在 let 命令所在的代码块内有效。
const 声明一个只读的常量,一旦声明,常量的值就不能改变。
在 ES6 之前,是没有块级作用域的概念的。
ES6 可以使用 let 关键字来实现块级作用域。
let 声明的变量只在 let 命令所在的代码块 {} 内有效,在 {} 之外不能访问。
{
let x = 2;
}
console.log(x)// 这里不能使用 x 变量
const 关键字
const 用于声明一个或多个常量,声明时必须进行初始化,且初始化后值不可再修改
const PI = 3.141592653589793;
PI = 3.14; // 报错
PI = PI + 10; // 报错
const定义常量与使用let 定义的变量相似:
二者都是块级作用域
都不能和它所在作用域内的其他变量或函数拥有相同的名称
两者还有以下两点区别:
const声明的常量必须初始化,而let声明的变量不用
const 定义常量的值不能通过再赋值修改,也不能再次声明。而 let 定义的变量值可以修改。
// 错误写法
const PI;
PI = 3.14159265359;
// 正确写法
const PI = 3.14159265359;
并非真正的常量
const 的本质: const 定义的变量并非常量,并非不可变,它定义了一个常量引用一个值。使用 const 定义的对象或者数组,其实是可变的。下面的代码并不会报错:
// 创建常量对象
const car = {type:"Fiat", model:"500", color:"white"};
// 修改属性:
car.color = "red";
// 添加属性
car.owner = "Johnson";
// 创建常量数组
const cars = ["Saab", "Volvo", "BMW"];
// 修改元素
cars[0] = "Toyota";
// 添加元素
cars.push("Audi");
但是我们不能对常量对象/数组重新赋值:
const car = {type:"Fiat", model:"500", color:"white"};
car = {type:"Volvo", model:"EX60", color:"red"}; // 错误
const cars = ["Saab", "Volvo", "BMW"];
cars = ["Toyota", "Volvo", "Audi"]; // 错误
解构赋值
解构赋值是对赋值运算符的扩展.可以针对对象或数组进行模式匹配,分解之后对其中的变量进行赋值.
对象模型的解构(Object)
let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
// foo = 'aaa'
// bar = 'bbb'
let { baz : foo } = { baz : 'ddd' };//foo是别名
// foo = 'ddd'
箭头函数
ES6中新增了箭头函数,就是函数的一种简写形式,箭头函数表达式的语法比普通函数表达式更简洁。
(参数1, 参数2, …, 参数N) => { 函数声明 }
(参数1, 参数2, …, 参数N) => 表达式(单一)
相当于:(参数1, 参数2, …, 参数N) =>{ return 表达式; }
当只有一个参数时,圆括号是可选的:
(单一参数) => {函数声明}
单一参数 => {函数声明}
没有参数的函数应该写成一对圆括号:
() => {函数声明}
箭头函数不仅仅让代码更加简洁,当我们使用箭头函数的时候,箭头函数会默认绑定外层this的值,在箭头函数中this的指向和和外层的this指向是一样的.
箭头函数是不能提升的,所以需要在使用之前定义。
使用 const 比使用 var 更安全,因为函数表达式始终是一个常量。
剩余运算符
剩余运算符允许我们将一个不定数量的参数表示为一个数组,不定参数定义方式,这 种方式很方便的去声明不知道参数情况下的一个函数
function sum (first, ...args) {
console.log(first); // 10
console.log(args); // [[20,30], {name:'40',age:50}]
}
sum(10, [20,30], {name:'40',age:50});
剩余运算符和解构配合使用
let students = ['wangwu', 'zhangsan', 'lisi'];
let [s1, ...s2] = students;
console.log(s1); // 'wangwu'
console.log(s2); // ['zhangsan', 'lisi']
可以将数组或者对象转为用逗号分隔的参数序列
let ary = [1, 2, 3];
...ary // 1, 2, 3
console.log(...ary); // 1 2 3,相当于下面的代码 console.log(1,2,3);
可以合并数组
// 方法一
let ary1 = [1, 2, 3];
let ary2 = [3, 4, 5];
let ary3 = [...ary1, ...ary2];
// 方法二
ary1.push(...ary2);
将类数组或可遍历对象转换为真正的数组
let oDivs = document.getElementsByTagName(‘div’);
oDivs = […oDivs];
set数据结构
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的 值。
Set本身是一个构造函数,用来生成 Set 数据结构
let set = new Set()
Set函数可以接受一个数组作为参数,用来初始化。
const set = new Set([1, 2, 3, 4, 4]);//{1, 2, 3, 4}
add(value):添加某个值,返回 Set 结构本身
delete(value):删除某个值,返回一个布尔值,表示删除是否成功
has(value):返回一个布尔值,表示该值是否为 Set 的成员
clear():清除所有成员,没有返回值