(一)概述
1 .两大编程思想
①面向过程POP
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步的实现,使用的时候再一个个调用。面向过程的性能比面向对象要高一些,适合和硬件联系比较紧密的东西,比如单片机。
②面向对象OOP
把事物分解成一个个的对象,然后由对象之间分工与合作。每一个对象都是功能中心,具有明确的分工。面向对象编程具有灵活,代码易复用,容易维护和开发的优点,更适合多人合作的大型软件项目。
2 .面向对象的特性:
①封装性
②继承性
③多态性
(二)对象和类的关系
1 .对象
现实生活中,万物皆对象,对象是一个具体的实物,在JS中,对象是一组无序的,相关属性和方法的集合,所有的事物都是对象,例如字符串、数值、数组、函数等。对象是由属性和方法构成的。
属性:事物的特征,在对象中用属性来表示,常用名词。
方法:事物的行为,在对象中用方法来表示,常用动词。
2 .类
在ES6中增加了类的概念,可以使用class关键字声明一个类,之后以这个类来实例化对象。
类抽象了对象的公共部分,它是泛指的某一大类。而对象是特指的某一事物,通过类实例化产生的具体的对象。
(三)创建自定义类
1 .语法:
class name {
classBody
}
创建实例:类必须使用new关键字来实例化对象
var 实例名称 = new 类名();
2 .类constructor构造函数
constructor()方法是类的构造函数(默认方法),用于传递参数,返回实例对象,通过new生成实例对象时,自动调用该方法,如果没有显示定义,类内部会自动创建一个constructor()。
constructor()可以接收传递过来的参数,并返回实例对象。
class People {
constructor (uname, age) {
this.uname = uname;
this.age = age;
}
}
var rose = new People("rose", 18);
3 .类添加方法
class People {
constructor (uname) { this.uname = uname }
say (message) { document.write(message + this.name)}
}
var rose = new People("rose");
rose.say("Hello");
(四)继承
1 .extends可以使子类继承父类的一些属性和方法。
class Father {
constructor () {}
money () {
document.write("一个小目标");
}
}
class Son extends Father { }
var son = new Son();
son.money();
2 .super()关键字用于访问和调用父类的函数,可以调用父类的构造函数,也可以调用父类的普通函数
// super调用父类构造函数
class Father {
constructor(a, b) {
this.a = a;
this.b = b;
}
sum() { console.log(this.a + this.b); }
}
class Son extends Father {
constructor (a, b) {
super(a, b);
}
}
var son = new Son(1, 2);
son.sum();
// super调用父类普通函数
class F {
log() {
return "父类";
}
}
class S extends F {
log() {
console.log("调用" + super.log());
}
}
var s = new S();
s.log();
3 .继承父类方法的同时扩列自身方法
class Father {
constructor(a, b) {
this.a = a;
this.b = b;
}
sum() { console.log(this.a + this.b); }
}
class Son extends Father {
constructor (a, b) {
// 必须先调用super
super(a, b);
this.a = a;
this.b = b;
}
subtract() { console.log(this.a - this.b); }
}
var son = new Son(1, 2);
son.sum();
son.subtract();
(五) ES6中类的注意点
1 .ES6中是没有变量提升的,所以,必须要先有类,才能通过类来实例化对象。
2 .类里面共有的属性和方法必须使用this。
3 .类里面this的指向,constructor里的this指向的是创建的实例对象,而方法里的this指向方法最终的调用者。
(六)构造函数和原型
概述:
ES6之前没有类的概念,在ES6之前,对象并不是通过类创建的,而是通过构造函数创建的。
1 .构造函数:
构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与new一起使用,我们可以将对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面。
new在执行时,会做四件事情:
①在内存中创建一个新的空对象
②让this指向这个空对象
③执行构造函数里面的代码,给这个新对象添加属性和方法
④返回这个新对象(所以构造函数里不需要return)
2 .静态成员和实例成员
构造函数中可以添加成员,可以在构造函数本身上添加,也可以在构造函数内部的this上添加,通过这两种方式添加的成员,就分别叫做静态成员和实例成员。
静态成员:在构造函数本身上添加的成员叫静态成员,只能由构造函数本身访问。
实例成员:在构造函数内部通过this创建的对象成员称为实例成员,只能由实例化的对象来访问。
3 .构造函数原型 prototype
构造函数通过原型分配的函数是所有对象共享的。
JS规定,每一个构造函数都有一个prototype属性,指向另一个对象,注意这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。我们可以把那些不变的方法,直接定义到原型对象上,这样所有对象的实例,就可以共享这些方法。
function Star (uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype.sing = function(){ console.log("sing") };
Star.prototype.movie = function(){ console.log("movie") };
new Star("name", 18);
4 .对象原型 _ _proto _ _
对象都有一个属性 _ _proto _ _指向构造函数的prototype原型对象,之所以我们对象可以使用构造函数原型对象prototype中的属性和方法,就是因为对象由 _ _proto _ _原型的存在。
_ _proto _ _对象原型和原型对象prototype是等价的。
_ _proto _ _对象原型的意义就是为对象的查找机制提供一个方向,或者说一条路线,但是他是一个非标准属性,因此,实际开发中,不可以使用这个属性,它只是内部指向了原型对象prototype。
5 .constructor构造函数
_ _proto _ _对象原型和构造函数的原型对象prototype内都由一个constructor属性,我们称之为构造函数,因为它指回构造函数本身。
constructor主要用于记录该对象引用哪个构造函数,它可以让原型对象重新指回构造函数。很多情况下需要我们手动利用constructor指回原来的构造函数。
function Star (uname, age) {
this.uname = uname;
this.age = age;
}
// Star.prototype.sing = function(){ console.log("sing") };
// Star.prototype.movie = funciton(){ console.log("movie") };
// 修改了原型对象,并且用对象形式赋值,就会改变构造函数中constructor的指向,这个时候就需要手动指回本来的构造函数
Star.prototype = {
constructor: Star,
sing: function() { console.log("sing") },
movie: function() { console.log("movie") }
}
new Star();
6 .原型链
7.继承
ES6之前并没有提供extends继承,是通过构造函数和原型对象模拟实现继承,称为组合继承
①call方法
call()方法可以用来调用函数,并改变函数内this的指向。
fun.call("当前调用函数的this的指向对象", arg1, arg2...);
组合继承的核心原理就是借用call方法,将父构造函数的this指向子构造函数的this
function Father(uname, age) {
this.uname = uname;
this.age = age
}
Son.prototype = new Father(); /* 继承方法, 将父构造函数的实例对象赋值给子构造函数,子构造函数
可以通过父构造函数的原型对象来间接访问父构造函数的方法,同时,双方方法的改变不会相互影响,
但这种方法会改变子构造函数的congstructor指向 */
Son.prototype.constructor = Son; // 手动指回
function Son (uname, age) {
Father.call(this, uname, age); // 继承属性
}
var s = new Son("rose", 18);
(五)ES5新增方法
1 .数组方法
①迭代方法 ,map() , every()
forEach() 相当于加强版的for循环
var arr = [1, 2, 3];
arr.forEach(function(item, index, array) {
// item: 被遍历数组中的每一个数组元素
// index: 每个元素的索引号
// array: 被遍历数组本身
})
filter(),用来筛选数组,并返回相应的结果(数组)
var arr = [1, 2, 3];
var result = arr.filter(function(item, index, array) {
// item: 被遍历数组中的每一个数组元素
// index: 每个元素的索引号
// array: 被遍历数组本身
return item > 2;
})
console.log(result); // [3]
some()用于查找数组中是否有满足条件的元素,当查找到第一个时,结束循环,它的返回值是true或false
var arr = [1, 2, 3];
var result = arr.some(function(item, index, array) {
// item: 被遍历数组中的每一个数组元素
// index: 每个元素的索引号
// array: 被遍历数组本身
return item > 2;
})
console.log(result); // true
2 .字符串方法
①str.trim();方法用于删除字符串两端的空白字符,并不影响原字符串,返回一个处理后的新字符串。
3 . 对象方法
①Object.keys(obj)用于获取对象自身所有的属性,效果类似for…in,返回一个由属性名组成的数组
②Object.defineProperty();用于定义对象的新属性或者修改原有属性。
// Object.defineProperty(目标对象, "需定义或修改的属性的名字", {特性});
var obj = {
id: 1,
pname: "abc";
price: 999
}
Object.defineProperty(obj, "pname", {
value: "ipone",
writable: false,
enumerable: true,
configurable: false
})
特性:
value: 设定属性的值,默认为undefined
writable: 值是否可以重定义,默认为false
enumerable: 此属性是否可以被枚举/遍历,默认为false
configurable: 目标属性是否可以被删除,或再次修改特性,默认为false
(六)函数进阶
1 .函数的定义和调用
①命名函数:通过function关键字来定义函数
// 定义
function fn() {};
// 调用
fn();
②匿名函数:通过函数表达式来定义函数
// 定义
var fn = function () {};
// 调用
fn();
③利用new Function(“参数1”, “参数2”, “函数体”);来定义函数
// 定义
var fn = new Function("console.log('形参和函数体必须以字符串的方式传递')");
// 调用
fn();
2 .函数内部this的指向
①call()方法
②apply()方法可以调用一个函数,并改变函数内部this的指向,和call方法不同的是,它可以传入一个数组作为参数。
fn.apply('在函数运行时指定的this值', [数组参数]);
apply方法的主要应用:比如借助apply调用Math对象来求一个数组的最大值
var array = [10, 30, 22];
Math.max.apply(null, array);
// 严格模式下是不允许this指向为null的
// Math.max.apply(Math, array);
③bind()方法同样可以改变函数内部的this指向,它最大的区别是,不会调用函数,它返回改造完成后的原函数的拷贝。
var fun = fn.bind('在函数运行时指定的this值', arg1..);
3 . 严格模式
ES5的严格模式(strct mode),是采用具有限制性JS变体的一种方式,即在严格的条件下运行JS代码。
严格模式可以应用到整个脚本或者个别函数中,因此在使用中,我们可以将严格模式分为为脚本开启严格模式和为函数开启严格模式两种情况。
开启严格模式:
"use strict";
4 .高阶函数
高阶函数是对其他函数进行操作的函数,它接收函数作为参数,或将函数作为返回值输出。
5 .闭包
闭包是指有权访问另一个作用域中变量的函数。
闭包的作用就是用来延申变量的作用范围。
利用闭包获取当前元素的索引号:
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
<script>
var lis = document.querySelector("ul").querySelectorAll("li");
lis.forEach(function(items, index) {
(function() {
items.addEventListener("click", function(){
console.log(index);
})
})(index);
})
</script>
</body>
6 .递归
如果一个函数在内部可以调用其本身,那么这个函数就称为递归函数。
递归的效果和循环类似,所以很容易发生栈溢出错误(死循环),一定要加return退出条件。
var n = 1;
function fn() {
console.log(`第${n}次循环`);
if(n === 5) {
return false;
}
n++;
fn();
}
fn();
利用递归求1~n的阶乘
function fn(n) {
if (n == 1) {
return 1;
}
return n * jn(n - 1);
}
fn();
(七)浅拷贝和深拷贝
浅拷贝:复杂数据类型只拷贝地址
var obj = {
id: 1,
uname: "duke",
message: { age: 18 }
}
var newObj = {};
for (var key in obj) {
newObj[key] = obj[k];
}
//ES6新增
Object.assign(newObj, obj);
深拷贝:数据完全复刻,重新开辟内存空间,实现的思路还是函数递归
var obj = {
id: 1,
uname: "duke",
message: { age: 18 },
hobby: ["抽烟", "喝酒", "烫头"],
}
var newObj = {};
function deepCopy(newObj, oldObj) {
for (var k in oldObj) {
var item = oldObj[k];
if(item intanceOf Array) {
newObj[k] = [];
deepCopy(newObj[k], item);
} else if (item intanceOf Object) {
newObj[k] = {};
deepCopy(newObj[k], item);
} else {
newObj[k] = item;
}
}
}
deepCopy(newObj, obj);