1. 面向对象
1.1 什么是面向对象
面向对象是一种编程思想,JS就是基于这个思想构建出来的一门编程语言,所以JS中存在对象、类、实例的概念。
对象:万物皆对象。
构造函数(类):把具有某一特征的内容可以划分为一类,类的身上会具有某些属性和方法(静态属性和方法)。
使用静态属性和方法的语法:
类名(构造函数名).属性名
类名(构造函数名).方法名
<body><script>let arr = new Array();console.dir(Array);console.dir(Array.name);//Arrayconsole.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); //falseconsole.log(f1.getY === f2.getY); //trueconsole.log(f1.__proto__.getY === Fn.prototype.getY); //trueconsole.log(f1.__proto__.getX === f2.getX); //falseconsole.log(f1.getX === Fn.prototype.getX); //falseconsole.log(f1.constructor); //Fnconsole.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);//trueconsole.log(arr instanceof Number);//falseconsole.log(arr instanceof Object);//truefunction 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.prototypeconsole.log(f instanceof Fn);//trueconsole.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);//falseconsole.log(name in f);//falseconsole.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.prototypeconsole.log("toString" in Object.prototype);//trueconsole.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.prototypeconsole.log(f.hasOwnProperty("age"));//trueconsole.log(f.hasOwnProperty("toString"));//falseconsole.log(f.hasOwnProperty("show"));//falseconsole.log(Fn.hasOwnProperty("age"));//falseconsole.log(Fn.hasOwnProperty("toString"));//falseconsole.log(Fn.hasOwnProperty("show"));//trueconsole.log(Object.hasOwnProperty("age"));//falseconsole.log(Object.hasOwnProperty("toString"));//trueconsole.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"))//trueconsole.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是原型的私有,就返回trueif (proto.hasOwnProperty(attr)) { return true }//一直往上找,直到nullproto = 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")); //falseconsole.log(f1.hasPubProperty("age")); //trueconsole.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==100console.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 没有,找公有//不进入ifif (name) {this.name = name;}}function C2(name) {//私有:name---undefined//this.name = undefinedthis.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);//truef.sum();//f.x+f.y=20Fn.prototype.sum();//Fn.prototype.x+Fn.prototype.y=20+undefined=NaNconsole.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();//0my_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---nullArray.prototype = {};//重定向//重定向后的原型链还是:arr-- Array.prototype---Object.prototype---nullconsole.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); //构造函数执行,传参,优先级更高18let f2 = new Fn;//构造函数执行,没传参,优先级低一点17console.log(f1.sum); //undefinedconsole.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 承诺者模式
学到后期更新