1. 面向对象
1.1 什么是面向对象
面向对象是一种编程思想,JS就是基于这个思想构建出来的一门编程语言,所以JS中存在对象、类、实例的概念。
对象:万物皆对象。
构造函数(类):把具有某一特征的内容可以划分为一类,类的身上会具有某些属性和方法(静态属性和方法)。
使用静态属性和方法的语法:
类名(构造函数名).属性名
类名(构造函数名).方法名
<body>
<script>
let arr = new Array();
console.dir(Array);
console.dir(Array.name);//Array
console.dir(Array.isArray([10, 20]));//true
</script>
</body>
实例对象:具体的事物,属于某类中的具体成员,每个成员(实例对象)都有自己私有的属性和方法,也有公有的属性和方法。
<body>
<script>
let arr = new Array([10, 100]);
console.log(arr);
</script>
</body>
1.2 JS中的内置类
1.每一个数据类型值都有一个自己所属的内置类:
Number类(每一个数字都是他的实例)、String类、Boolean类、Symbol类、BigInt类、Object类、Array类、RegExp类、Date类、Error类、Function类...
2.每一个元素标签都有一个自己所属的内置类,例如:
div --> HTMLDivElement --> HTMLElement --> Element --> Node --> EventTarget --> Object -->null
其它元素标签同理。
3.每一个类赋予其实例对象的公共属性方法,在类的原型对象上。
var num = new Number(10);
console.log(num)
原型链:num -->Number --> Object -->null
实例对象的私有属性和方法在 [[PrimitiveValue]]:10
实例对象的公有属性和方法在 Number.prototype
2. 原型和原型链
2.1 原型
1. 几乎所有的函数都有一个prototype(原型)属性,指向自己所属类的原型。
注意!!!
箭头函数没有prototype属性
es6的快捷函数也没有prototype属性
2. prototype这个原型对象天生自带一个constructor(构造函数)属性,指的是自己的构造函数。prototype主要给实例对象提供公有的方法。
3. 所有的对象都天生自带一个_ _proto_ _(原型链)属性,它指向实例所属类的原型。
4. 静态的属性和方法放在构造函数的键值对里面。
5.Object 是所有对象类型的“基类”。
【函数】:普通函数(实名或匿名)、构造函数、生成器函数、箭头函数(不具备prototype)、基于ES6给对象某个成员赋值函数值的快捷操作(不具备prototype)。
【对象】:普通的对象、数组、实例对象、prototype(原型对象)等。
<body>
<script>
function Person(name, age) {
//私有属性和方法
this.name = name;
this.age = age;
this.show = function () {
console.log("show");
}
}
//公有属性和方法
Person.prototype.job = "学生";
Person.prototype.fn = function () {
console.log("fn");
};
//静态属性和方法
Person.house = "别墅";
Person.f = function () {
console.log("f")
}
//实例对象
let p = new Person("lili", 18);
</script>
</body>
原型链:
2.2 原型链查找机制
当我们要查找或者操作实例上的某个方法或者属性的时候,我们会先查找实例的私有属性,看看私有上是否有,如果有,停止查找;
如果没有,就会基于_ _proto_ _向上查找,如果找到,就是公有属性;
如果还没有,继续基于_ _proto_ _原型链向上查找,直到Object基类,如果都没有,就是操作方法或者属性不存在。
2.3 案列
<body>
<script>
function Fn() {
this.x = 100;
this.y = 200;
this.getX = function () {
console.log(this.x);
}
}
Fn.prototype.getX = function () {
console.log(this.x);
};
Fn.prototype.getY = function () {
console.log(this.y);
};
let f1 = new Fn;
let f2 = new Fn;
console.log(f1.getX === f2.getX); //false
console.log(f1.getY === f2.getY); //true
console.log(f1.__proto__.getY === Fn.prototype.getY); //true
console.log(f1.__proto__.getX === f2.getX); //false
console.log(f1.getX === Fn.prototype.getX); //false
console.log(f1.constructor); //Fn
console.log(Fn.prototype.__proto__.constructor); //Object
</script>
</body>
2.4 在原型上拓展方法
<body>
<script>
let arr = [100, 200, 345];
let res = arr.push(123);//原型的方法
console.log(res);//返回数组的长度。
//在原型上拓展方法的写法:
Array.prototype.myPush = function myPush() {
//1.this是谁调用push就指向谁
//2.参数使用arguments来接
for (let i = 0; i < arguments.length; i++) {
this[this.length] = arguments[i];
}
return this.length;
}
let arr1 = [1, 2, 3, 4];
let arr2 = arr1.myPush(345, 234);
console.log(arr1);//[1,2,3,4,345,234]
console.log(arr2);//6
</script>
</body>
2.5 链式调用
想要实现一个需求:
var ary=[5,8,3,1] ;想要让这个数组先排序,然后再倒序,然后再往里面添加一个10,然后再删除第一项。
<body>
<script>
let arr = [5, 8, 3, 1];
//原数组:排序后的新数组
arr.sort();
//原数组:倒叙后的新数组
arr.reverse();
//原数组:新增后的新数组的长度
arr.push(10);
//原数组:被删除后的新数组
arr.shift();
console.log(arr);
</script>
</body>
实现链式写法的要求:上一步的返回值必须是同一种类型,才可以继续链式操作。
<body>
<script>
let arr = [5, 8, 3, 1];
//原数组:排序后的新数组
//原数组:倒叙后的新数组
//原数组:新增后的新数组的长度
//到shift()就会报错,因为上一步返回的是长度,和push(10)和shift()的返回值不是同一类型
arr.sort().reverse().push(10).shift();//报错
</script>
</body>
2.6 instanceof (判断实例属于哪个类)
instanceof 判断 某个实例 是否属于那个类(构造函数)。
<body>
<script>
let arr = [1, 2, 3, 4];
console.log(arr instanceof Array);//true
console.log(arr instanceof Number);//false
console.log(arr instanceof Object);//true
function Fn(name, age) {
this.name = 100;
this.age = 200;
}
Fn.prototype.show = function () {
console.log(this.name);
}
let f = new Fn();//f---Fn.prototype---Object.prototype
console.log(f instanceof Fn);//true
console.log(f instanceof Object);//true
</script>
</body>
2.7 in (判断对象是否有某个属性(公有+私有))
in 检测当前对象是否存在某个属性。不论是共有属性,还是私有属性,只要是对象的属性,通过in进行检测返回值都是true。
是自己身上的属性的情况:
1. 本身就是自己身上的属性。
2. 原型链查找机制也是属于自己身上的属性。
注意!!!
坑点一 : name没有加引号,所以是一个变量。单纯输出name不会报错,因为构造函数有一个全局的name属性,所以单纯输出name是“”空引
坑点二: 构造函数身上的键值对里面存放着一个name属性。所以当判断name属性是否属于构造函数,它是属于的(true),这时的name指的是构造函数的静态属性。
<body>
<script>
function Fn(name, age) {
this.name = name;
this.age = age;
}
Fn.prototype.show = function () {
console.log(this.name);
}
//1. 判断是否属于自己身上的属性
let f = new Fn("lili", 18);
console.log("age" in Fn);//false Fn是构造函数,它身上的属性是静态属性。
console.log("age" in f);//true f是实例对象
console.log("age" in Fn.prototype);//false Fn.prototype是原型对象
console.log("show" in Fn.prototype);//true
//坑点一:这里的name没有加引号,所以是一个变量
//重点:单纯输出name不会报错,因为构造函数有一个全局的name属性,所以单纯输出name是“”空引
console.log(name);//""
console.log(name in Fn);//false
console.log(name in f);//false
console.log(name in Fn.prototype);//false
//坑点二:构造函数身上的键值对里面存放着一个name属性。
console.log("name");//"name"
console.log("name" in Fn);//true Fn是构造函数,它身上的属性是静态属性。
console.log("name" in f);//true f是实例对象
console.log("name" in Fn.prototype);//false Fn.prototype是原型对象
//2.原型链查找机制也是属于自己身上的属性
console.log(f);//原型链: f -- Fn.prototype -- Object.prototype
console.log("toString" in Object.prototype);//true
console.log("toString" in f);//true
</script>
</body>
2.8 hasOwnProperty方法(判断对象是否有某个私有属性)
hasOwnProperty 是Object 类原型(Object.prototype)上的方法,主要是用来检测某个属性是不是某个对象的私有属性。
- 如果是私有返回true,如果不是返回false。
- 如果没有此属性返回的也是false。
- 公有还是私有,是相对来说的,主要看针对的主体是谁。
语法:对象.hasOwnProperty("属性名");
<body>
<script>
function Fn(name, age) {
//私有
this.name = name;
this.age = age;
}
//公有
Fn.prototype.show = function show() {
console.log(this.name);
}
let f = new Fn("lili", 18);
console.log(f);// f -- Fn.prototype -- Object.prototype
console.log(f.hasOwnProperty("age"));//true
console.log(f.hasOwnProperty("toString"));//false
console.log(f.hasOwnProperty("show"));//false
console.log(Fn.hasOwnProperty("age"));//false
console.log(Fn.hasOwnProperty("toString"));//false
console.log(Fn.hasOwnProperty("show"));//true
console.log(Object.hasOwnProperty("age"));//false
console.log(Object.hasOwnProperty("toString"));//true
console.log(Object.hasOwnProperty("show"));//false
</script>
</body>
【思考题】编写一个hasPubProperty方法,检测一个属性是不是公有的。
<body>
<script>
function Fn(name, age) {
this.name = name;
this.age = age;
}
var f1 = new Fn("lili", 18);
Object.prototype.hasPubProperty = function (attr) {
//如果是true,说明不论是公有还是私有,起码是
if (attr in this) {
if (!this.hasOwnProperty(attr)) {
// 不是私有的说明就是公有的
return true;
}
return false;
}
return false;
}
console.log(f1.hasPubProperty("toString"))//true
console.log(f1.hasPubProperty("name"))//false
</script>
</body>
进阶版:解决某个属性即是公有属性,也是私有属性。
使用静态方法Object.getPrototypeOf()获取到原型对象。
<body>
<script>
Object.prototype.hasPubProperty = function (attr) {
//查找f1的原型
//let proto = this.__proto__;找到实例对象的原型对象,浏览器不支持该写法,
//使用Object.getPrototypeOf(this)代替
let proto = Object.getPrototypeOf(this);
while (proto) {
//如果attr是原型的私有,就返回true
if (proto.hasOwnProperty(attr)) { return true }
//一直往上找,直到null
proto = Object.getPrototypeOf(proto);
}
return false;
}
function Fn(name, age) {
this.name = name;
this.age = age;
}
Object.prototype.age = 180;
let f1 = new Fn("lili", 18);
console.log(f1.hasPubProperty("name")); //false
console.log(f1.hasPubProperty("age")); //true
console.log(f1.hasPubProperty("toString")); //true
</script>
</body>
2.9 构造函数原型重定向
2.9.1 原型重定向的类型
手动重定向的原型是没有constructor的,我们需要自己手动添加一个。
语法:constructor:构造函数名
1. 类型一:正常new了一个实例对象。
<body>
<script>
function Fn() {
this.x = 100;
}
Fn.prototype.getX = function () {
return this.x;
}
var f1 = new Fn();
Fn.prototype = {
//constructor:Fn,
getY: function () {
return this.x
}
};
var f2 = new Fn();
console.log(f1.getX());//f.x==100
console.log(f2.getX()); // 报错
console.log(f1.constructor);//Fn构造函数
console.log(f2.constructor); // Object内置类
</script>
</body>
2. 类型二:构造函数的原型(prototype) new了一个
<body>
<script>
function Fn() {
let a = 1;
this.a = a;
}
Fn.prototype.say = function () {
this.a = 2;
}
Fn.prototype = new Fn;
let f1 = new Fn;
Fn.prototype.b = function () {
this.a = 3;
}
console.log(f1.a);
console.log(f1.prototype);
console.log(f1.b);
console.log(f1.hasOwnProperty("b"));
console.log("b" in f1);
console.log(f1.constructor == Fn);
</script>
</body>
2.9.2 原型重定向的练习
练习1:
<body>
<script>
function C1(name) {
//私有:name---undefined 没有,找公有
//不进入if
if (name) {
this.name = name;
}
}
function C2(name) {
//私有:name---undefined
//this.name = undefined
this.name = name;
}
function C3(name) {
//私有:name---undefined---false
//this.name = "join"
this.name = name || "join";
}
C1.prototype.name = "Tom";//c1公有
C2.prototype.name = "Tom";//c2公有
C3.prototype.name = "Tom";//c3公有
alert(new C1().name + new C2().name + new C3().name);//“Tomundefinedjoin”
</script>
</body>
练习2:
<body>
<script>
function Fn(num) {
this.x = this.y = num;
}
Fn.prototype = {
x: 20,
sum: function () {
console.log(this.x + this.y);
}
};
let f = new Fn(10);
console.log(f.sum === Fn.prototype.sum);//true
f.sum();//f.x+f.y=20
Fn.prototype.sum();//Fn.prototype.x+Fn.prototype.y=20+undefined=NaN
console.log(f.constructor);//Object内置类
</script>
</body>
练习3:
<body>
<script>
function fun() {
this.a = 0;
this.b = function () {
alert(this.a);
}
}
fun.prototype = {
//constructor:fun,
b: function () {
this.a = 20;
alert(this.a);
},
c: function () {
this.a = 30;
alert(this.a)
}
}
var my_fun = new fun();
my_fun.b();//0
my_fun.c();//30
</script>
</body>
练习4:补全下面代码,使最后控制台输出的内容为15(10+10-5)。
<body>
<script>
let n = 10;
Number.prototype.plus = function (num = 0) { };
Number.prototype.minus = function (num = 0) { };
let m = n.plus(10).minus(5);
console.log(m);
</script>
</body>
2.10 内置类的原型重定向
内置类的原型是不允许重定向的,即使重定向也没有作用。
但是原型上的方法可以重写,把原来的方法给覆盖。
<body>
<script>
var arr = [1, 2, 3];
//重定向前的原型链:arr-- Array.prototype---Object.prototype---null
Array.prototype = {};//重定向
//重定向后的原型链还是:arr-- Array.prototype---Object.prototype---null
console.log(arr);
//方法重写:
Array.prototype.push = function push() {
return this.length;
};
console.log(arr.push());
console.log(arr);
</script>
</body>
3. 函数的三种角色
1.普通函数。
2.普通对象。
数组/Object...内置类的键值对当做普通对象,这里放的属性方法(静态私有属性)是工具类方法,和它的实例没有关系---》Array.form()/Array.isArray()/Object.create()...。
3.类(构造函数)。
function Fn(x,y){
var total=x+y;
this.a=x;
this.b=y;
this.total=total;
}
Fn(); // 当成普通函数执行
var f1=new Fn(1,2); // 构造函数
Fn.myName="lili"; // 普通的对象 console.log(Fn);
4.JS中的优先级
优先级 | 运算符类型 | 结合性 | 运算符 |
---|---|---|---|
19 | 分组 | n/a(不相关) | ( … ) |
18 | 成员访问 | 从左到右 | … . … |
需计算的成员访问 | 从左到右 | … [ … ] | |
new(带参数列表) | n/a | new … ( … ) | |
函数调用 | 从左到右 | … ( … ) | |
可选链(Optional chaining) | 从左到右 | ?. | |
17 | new(无参数列表) | 从右到左 | new … |
16 | 后置递增 | n/a | … ++ |
后置递减 | … -- | ||
15 | 逻辑非 (!) | 从右到左 | ! … |
按位非 (~) | ~ … | ||
一元加法 (+) | + … | ||
一元减法 (-) | - … | ||
前置递增 | ++ … | ||
前置递减 | -- … | ||
typeof | typeof … | ||
void | void … | ||
delete | delete … | ||
await | await … | ||
14 | 幂 (**) | 从右到左 | … ** … |
13 | 乘法 (*) | 从左到右 | … * … |
除法 (/) | … / … | ||
取余 (%) | … % … | ||
12 | 加法 (+) | 从左到右 | … + … |
减法 (-) | … - … | ||
11 | 按位左移 (<<) | 从左到右 | … << … |
按位右移 (>>) | … >> … | ||
无符号右移 (>>>) | … >>> … | ||
10 | 小于 (<) | 从左到右 | … < … |
小于等于 (<=) | … <= … | ||
大于 (>) | … > … | ||
大于等于 (>=) | … >= … | ||
in | … in … | ||
instanceof | … instanceof … | ||
9 | 相等 (==) | 从左到右 | … == … |
不相等 (!=) | … != … | ||
一致/严格相等 (===) | … === … | ||
不一致/严格不相等 (!==) | … !== … | ||
8 | 按位与 (&) | 从左到右 | … & … |
7 | 按位异或 (^) | 从左到右 | … ^ … |
6 | 按位或 (|) | 从左到右 | … | … |
5 | 逻辑与 (&&) | 从左到右 | … && … |
4 | 逻辑或 (||) | 从左到右 | … || … |
空值合并 (??) | 从左到右 | … ?? … | |
3 | 条件(三元)运算符 | 从右到左 | … ? … : … |
2 | 赋值 | 从右到左 | … = … |
… += … | |||
… -= … | |||
… **= … | |||
… *= … | |||
… /= … | |||
… %= … | |||
… <<= … | |||
… >>= … | |||
… >>>= … | |||
… &= … | |||
… ^= … | |||
… |= … | |||
… &&= … | |||
… ||= … | |||
… ??= … | |||
1 | 逗号 / 序列 | 从左到右 | … , … |
5. 完整的原型图
结论: Function 和 Object 内置类的关系
几乎所有的函数身上都有一个prototype 和_ _proto_ _。
-------------------------------------------------------------------------------------------------------------------------
结论一:
1. Function.__proto__.__proto__ === Object.prototype ,Function是一个函数,但是函数也是对象,函数最终也是Object 的一个实例。
2. Function.prototype.__proto__ === Object.prototype ,Function.prototype(函数的原型对象)是Object的一个实例。
3. Object.__proto__.__proto__ === Object.prototype ,Object是Object的一个实例。
4. 构造函数.__proto__.__proto__ === Object.prototype ,构造函数是Object的一个实例。
5. 构造函数.prototype.__proto__ === Object.prototype ,构造函数的原型对象是Object的一个实例。
6. 实例对象.__proto__.__proto__ === Object.prototype ,实例对象是Object的一个实例。
总结:万物皆对象,都可以使用对象原型(Object.prototype)上的方法和属性。
-------------------------------------------------------------------------------------------------------------------------
结论二:
1. Function.__proto__ === Function.prototype ,Function是Function的一个实例。
2. Object.__proto__ === Function.prototype ,Object本身就是一个函数 ,Object是Function的一个实例。
3. 构造函数.__proto__ === Function.prototype ,构造函数是Function的一个实例。
总结:所有的函数沿着原型链都能找到Function原型上方法和属性(call,bind,apply),为所有函数实例提供公有的属性和方法。
重点注意:Function.prototype 是一个匿名空函数,但是作用跟原型对象一样。
-------------------------------------------------------------------------------------------------------------------------
Object 是所有类的基类。
Object的内置对象堆 , 其实是一个函数。
Object的内置对象堆内存放这仅他自己可以调用的静态方法(工具类方法),与实例没有关系。
Function内置类对象堆(函数),所有函数都是它的一个实例。
前置知识:
new Foo().getName();等价于
var a = new Foo()
a.getName()
因为成员访问(.)的优先级高于new 函数名。
new new Foo().getName();等价于
var a = new Foo();
var b = a.getName;
var c = new b();
因为new 函数名()的优先级高于成员访问(.),成员访问(.)的优先级高于new 函数名。
阿里超难面试题(JS 中运算符优先级)
<body>
<script>
function Foo() {
getName = function () {
console.log(1);
};
return this;
}
Foo.getName = function () {
console.log(2);
};
Foo.prototype.getName = function () {
console.log(3);
};
var getName = function () {
console.log(4);
};
function getName() {
console.log(5);
}
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();
</script>
</body>
6. JS中的设计模式
6.1 工厂模式
把实现相同功能的代码进行封装,后期在使用的时候,只用调用这个函数即可,方便后期的“批量生产”。减少了页面中冗余的代码,实现了“高耦合低内聚”。
工厂模式总结:批量生成,函数封装,实现了“高耦合低内聚”。
<body>
<script>
function createObj(name, age) {
return {
name,
age
}
}
let obj1 = createObj("小米", 18);
console.log(obj1);//obj{name:"小米",age=18}
let obj2 = createObj("小王", 19);//obj{name:"小王",age=19}
console.log(obj2);
</script>
</body>
6.2 单例模式
可以把描述一个事物的所有属性放到一个对象中,这样避免了相互的干扰,这种设计模式就是单例设计。
单例模式总结:每个都是一个单独的个体,不会冲突。
简单单例模式:对象
高级单例模式:闭包
简单单例模式:
<body>
<script>
let obj1 = {
name: "小王",
age: 18
}
let obj2 = {
name: "小米",
age: 19
}
console.log(obj1.name, obj2.name);
</script>
</body>
高级单例模式:
<body>
<script>
let lunBox = (function () {
var a = 10;
function show() { }
return {};
})();
let pubuBox = (function () {
var a = 100;
function show() { }
return {};
})();
let nameSpace = (function () {
function fn() { }
var a = 2; //... 在这个作用域中还有很多的变量和方法,
return { // 想要把谁暴露出去,就放到这对象中
fn: fn
}
})();
console.log(nameSpace.fn)
</script>
</body>
6.3 构造函数模式
6.3.1 什么是构造函数模式?
自己创建一个函数,执行的时候用“new”来执行,这样这个函数就是类(构造函数),返回的结果是这个类的一个实例对象。
构造函数的语法:
有参:new 函数名();,优先级更高18无参:new 函数名;,优先级低一点17
<body>
<script>
function Fn(x, y) {
let sum = 10;
this.total = x + y;
this.say = function () {
console.log(`我计算的和是:${this.total}`);
};
// return {name:'哈哈'};
}
let res = Fn(10, 20); //普通函数执行
let f1 = new Fn(10, 20); //构造函数执行,传参,优先级更高18
let f2 = new Fn;//构造函数执行,没传参,优先级低一点17
console.log(f1.sum); //undefined
console.log(f1.total); //30
console.log(f1.say === f2.say);//false
</script>
</body>
6.3.2 普通函数的执行上下文:
6.3.3 构造函数的执行上下文(也会像普通函数一样创建一个私有上下文)
1.私有上下文产生后,首先创建这个类的实例对象。
2. this指向这个创建的实例对象。
3. this.xxx=xxx; 给实例对象设置私有属性和方法。
4.如果函数没有写返回值 或者 返回的是原始类型的值,默认返回值是创建的实例对象;如果函数有返回值,是对象类型的值。
6.4 观察者模式
学到后期更新
6.5 发布订阅模式
学到后期更新
6.6 承诺者模式
学到后期更新