单例模式
- 把描述同一个事物特征的信息进行分类归组,放到同一个命名空间下(减少全局变量的污染)
var name = "邢";
var age = 21;
var sex = "男";
var name = "刘";
var age = 21;
var sex = "男";
//
var xing = {
name: "邢",
age: 21,
sex: "男"
};
var liu = {
name: "刘",
age: 21,
sex: "男"
};
//描述同一个对象的所有属性放在对象中,避免了全局变量的干扰,"单例模式"
var timer = 1;
var xing = {
bar: function () {
},
fn: function () {
}
};
// var timer = 1;
var liu = {
bar: function () {
}
};
-----------------------------------------------------
var name = 'erYa';
var age = 18;
var name = 'jinYu';
var age = 22;
let person1 = {
name: 'erYa',
age:18
};
let person2 = {
name: 'jinYu',
age: 22
}
高级单例模式
//高级单例模式
//jquery的源码的外层是采用了高级单例模式
var utils = (function () {
var timer = 1;
var num = 100;
return {
bar: function () {
},
fn: function () {
}
}
})()
//与单例模式相比,想使用同一变量,可以避免污染全局
--------------------------------------------------
let person1 = (function(){
let fn = function(){};
let name= 'erYa';
return {
name:name,
age:18,
fn:fn
}
})()
let person2 = (function(){
let fn = function(){};
let name= 'jinYu';
let age = 18;
return {
name:name,
fn:fn
}
})()
let person3 = function(){
let name= 'jinYu';
let age = 26
return {
name:name,
fn:fn
}
}
console.log(person3) // 这是一个函数,不是person3的个人信息
工厂模式
- 如果用单例模式去写很多个person就会变得很麻烦,就有了工厂模式 特点:批量生产
- 把实现相同功能的代码封装到函数里,以后想运行这个方法,就直接执行这个函数就好了
- 高内聚:提高代码的复用率
- 低耦合:减少页面的重复代码
//工厂模式: 函数的封装
function createStudent(name, age) {
var obj = {}
obj.name = name;
obj.age = age;
return obj;
};
var xing = createStudent('邢', 21);
var liu = createStudent('刘', 21);
对象前言
javaScript
面向对象: 基于对象
对象: 万物皆对象
类: 指具有相同特征的一类事物
实例:具体的一个事物:
面向对象
- 把抽象的对象按照特点进行分类(大类/小类),把类的公共特征进行提取和封装,放到对应的类别中
- 类就是对对象的一种细分,和公共部分的抽取
- 在类中具体派生出来的具体事物就是类的实例,而且实例拥有自己私有的特征,还拥有所属类上的特征
- 我们研究面向对象,其实就是研究对象、类、实例之间的关系和各自的知识点
构造函数模式(构造自定义类)
构造函数解决了实例的私有属性
- var f=new Fn();
- new 操作符——把 new 放在函数的前面,函数执行
- 构造函数就是让我们创建自定义类
- new 函数执行 叫做构造函数运行模式,此时的Fn就是Fn类(构造函数),函数执行之后的返回结果就是一个对象,叫做实例对象(f就是Fn的实例)
- 类就是函数数据类型的
- 实例是对象数据类型
- 构造函数中的this指向当前实例
- 如果这个函数需要参数,那么这个需要有小括号,如果不需要参数,那么小括号可以省略
- new后面的函数就是构造函数,也叫类;那么通过new函数得到的返回值就叫实例;实例是构造函数new出来的;
> 钩子函数:在初始化实例时会默认调用原型上的一些方法,那么这些方法就是钩子函数
运行原理
- 1、在代码执行之前,函数中会首先默认创建一个空对象 {}
- 2、让当前函数里的this指向这个对象
- 3、代码执行
- 4、默认return 这个对象
let f1 = new Fn(‘erYa’, 18);
//原型三句话
//f1就是Fn的实例
//fn就是函数,也是自定义(Fn)类
//f1就是实例化的对象//let f3 = new Fn;
如果构造函数不传实参,可以省略执行小括号
new 函数执行 叫做构造函数运行模式,此时的Fn就是Fn类(构造函数),函数执行之后的返回结果就是一个对象,叫做实例对象(f就是Fn的实例)
function fn() {
// var obj = {};
// this = obj;
this.name = 100;
// return this;
};
var f = new fn;
fn();
console.log(f);
/*
刚才那些都是js的内置类,但是我们也可以自己去自定义一些类
*/
// 构造函数(构造自定义类)
function Fn(name, age) {
/*
形成私有作用域
形参赋值
变量提升
1、默认生成一个空对象 {}
2、让函数里的this指向这个对象
3、代码执行
4、默认return 这个对象
*/
this.name = name; // 给this增加键值对
this.age = age // 给this增加键值对
this.say = function(){}
return {}
}
// new: 他是js里的关键字
Fn() // 普通函数运行
let f = new Fn() // {}
let f1 = new Fn('erYa', 18);
//原型三句话
//f1就是Fn的实例
//fn就是函数,也是自定义(Fn)类
//f1就是实例化的对象
let f2 = new Fn('jinYu', 18);
let f3 = new Fn;//如果构造函数不传实参,可以省略小括号
let f4 = Fn;
console.log(f)
console.log(f1)
console.log(f2)
console.log(f1.age === f2.age)
console.log(f1.say === f2.say)
console.log(f3)
console.log(f4)
// new 函数执行 叫做构造函数运行模式,此时的Fn就是Fn类(构造函数),函数执行之后的返回结果就是一个对象,叫做实例对象(f就是Fn的实例)
构造函数和普通函数的不同
JS为了区分构造函数和普通函数,一般将构造函数首字母大写;
- 运行上的不同
- 普通函数–>形成私有作用域–>形参赋值–>变量提升–>代码执行–>作用域是否销毁
- 构造函数–>形成私有作用域–>形参赋值–>变量提升–>默认生成一个对象–>把this指向这对象–>代码执行–>默认把这个对象return出去–>作用域是否销毁
- 执行上的不同
- 构造函数如果不传实参,可以不加小括号
- 构造函数如果手动return一个基本数据值,不能改变人家的返回值,但是手动return引用数据类型,可以改变构造函数的返回值,此时return的东西已经不是当前类的实例了【所以不要轻易修改构造函数的返回值】
function Fn(n) {
let m = 10;
this.total = m + n;
this.say = function () {
console.log(this.total)
}
}
let f1 = new Fn(10);
let f2 = new Fn(20);
console.log(f1.n)//undefined
console.log(f2.m)//undefined
console.log(f1.total)//20
f2.say()//30
console.log(f1 === f2)//false
创建实例的方式
- 字面量创建实例的方式
let num = 1;
let str = 'w';
- 构造函数创建实例的方式
// 实例创建
var num = new Number(1);
console.log(typeof num);// "object"
console.log(num instanceof Number);//true
console.log(num);//Number {1}
console.log(num+1);//2
console.log(num == 1 )//比较true,绝对比较就是false
// 如果是数字并且只有一个参数,代表数组的length;如果大于等于两个,那么是数组的每一项;
var arr = new Array(100,200);
console.log(arr);//[100, 200]
console.log(typeof Array);// "function"
var arr = new Array("a")
console.log(arr);//["a"]
------------------------------------------------------------------------
let ss = new Number(1)
console.log(ss)
console.log((1).toFixed(2)) // '1.00' 把数字转换为字符串,保留指定位小数
console.log(ss.toFixed(2)) // '1.00'
let w = new String(3) // 创建一个字符串的实例
console.log(w)
console.log(w.substr) // f//.substr字符串里的方法
console.log(w instanceof String) // true
检测当前实例是否属于某个类
- 使用instanceof是检测当前实例是否属于某个类
- 实例 instanceof 类,如果实例是属于这个类,那就返回true,反之就是false
- 通过字面量方式创建的基本数据类型值不是一个标准的实例,不能使用instanceof 进行检测;引用数据类型创建的就是一个标准的实例,可以使用instanceof来进行检测;
局限性
- instanceof不能检测基本数据类型,只能检测引用数据类型的值
// 字面量方式创建变量
// var num = 1;// num 是Number的一个实例吗?是
// console.log(num instanceof Number);// false
// var obj = {};
// console.log(obj instanceof Object);// true
// var ary = [];
// console.log(ary instanceof Array);// true
--------------------------------------------
function Fn(name, age){
this.name = name;
this.age = age;
}
let f1 = new Fn;
console.log(f1 instanceof Fn) // true
console.log(1 instanceof Fn) // false
给实例的类封装公共方法需要注意的几点
- 你自己封装的方法不能与人家内置的方法同名
- 给你自己的方法加前缀
//去重
function myUnique(){
console.log(this)
let obj = {};
for (var i = 0; i < this.length; i++) {
if(obj[this[i]] !== undefined){
this[i] = this[this.length-1];
this.length--;
i--;
continue;
}
obj[this[i]] = this[i]
}
}
Array.prototype.myUnique = myUnique;
console.log(ary.myUnique().sort().reverse().slice(1,3))
// 内置类的链式扩展:如果你想用链式调用,前提是你的方法的返回值必须是当前类的实例
原型模式
- 原型模式: 构造函数解决了对象实例中私有属性的问题,原有模式解决了对象实例中公有属性的问题;如果将实例的私有属性放到了公有属性上,减少了堆内存的开辟;
原型解决了实例的公有属性
- 每一个函数(普通函数,构造函数)都天生自带一个prototype属性,属性值是一个对象,它里面存储的是实例的公有属性(原型)
- 每一个原型都天生自带一个constructor属性,其属性值指向当前原型所属的类
- 每一个对象都天生自带一个__proto__属性,其属性值指向当前实例所属类的原型
原型链
- 在对象里查找-一个属性,先看自己私有的有没有,如果自己没有,就通过__ proto__ 属性找 到当前所属类的原型上,如果原型上有,就直接用,如果没有,继续通过原型的__ proto__ 继续往所属类的原型上找,直到找到Object类的原型上找,如果还没有,就是undefined, 这种级- 一级一级向上查找就会形成原型链
function Fn(x, y) {
this.x = x;
this.y = y;
this.getX = function () {
console.log(this.x);
}
}
Fn.prototype.getX = function () {
console.log(this.x);
}
Fn.prototype.getY = function () {
console.log(this.y);
}
var f1 = new Fn(100, 200);
f1.__proto__.a = 300;
var f2 = new Fn(100, 200);
console.log(f1.getX == f2.getX);//false
console.log(f1.getY == f2.getY);//true
console.log(f1.x == f2.x);//true
console.log(Fn.prototype.getX === f1.getX);//false
console.log(Fn.prototype.getX === f1.__proto__.getX);//true
f1.getX()//100
Fn.prototype.getX();// undefined
f2.__proto__.getX();//undefined
Fn.prototype.getY();//undefined
console.log(f1.a);//300
console.log(f1.toString);//ƒ toString() { [native code] }
原型:
- window 是全局作用域中一个大的对象;window是Object的一个实例;
- Function是所有函数数据类型的基类(包括自己)
- Object是所有对象的基类
- 所有的函数都有prototype和__proto__属性
- Object的原型的__proto__执行的是自己,js认为自己指向自己没有意义,就规定为null
- 所有的函数数据类型(普通的函数、类【内置类,自定义类】)都是Function的一个实例,Function和Object都是Function的一个实例,那Object类的__proto__指向Function的原型
- 所有函数都是Function的实例, 那Function也是函数, 那他的__proto__指向的是自己的原型。它的原型是一个匿名函数,但是也是对象,正常使用即可
- 当函数作为对象时,有length和name属性
- length:代表形参的个数,name:代表函数的名字
- 当函数作为对象时,有length和name属性
- 如果一个对象你不知道谁构出来的, 那他的__proto__就指向Object内置类的原型( 所有类的原型都指向Object内置类的原型)
- 函数的三种角色: 普通函数 构造函数 普通对象
- 在正式场合函数是以函数的身份出场的( 这是人家的主角色)
- 类的特点: 多态、 继承和封装
- 给Fn的原型新增键值对;这就是原型扩展;
原型图
- 原型链:单向不可逆
原型重定向
- 内置类的原型不能被重定向
- 可以覆盖内置类原型上的方法
- 用新的空间地址覆盖Fn原有的空间地址;会导致constructor的丢失;
- 重定向例子
function Fn() {
this.x = 100;
this.y = 100;
}
Fn.prototype.getX = function () {
console.log(this.x)
}
let f1 = new Fn;
Fn.prototype = {
getY: function () {
console.log(this.y)
}
};
let f2 = new Fn;
console.log(f1.getX)
console.log(f2.getX)
console.log(f1.constructor)
console.log(f2.constructor)
console.log(Fn.prototype)
阿里面试题(原型链、运算符优先级)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<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(); //2
getName(); //5//4
Foo().getName(); //2//1
getName(); //4//1
new Foo.getName(); //2
new Foo().getName(); //1//3
new new Foo().getName(); //1//3
/*
1、成员访问:寻找对象里的属性名所对应的属性值就是成员访问(19)
Fn.aa
2、new(带参数列表):就是构造函数执行有括号(19)
3、new(无参数列表):就是构造函数执行没有括号(18)
优先级一样,从左到右运算
*/
</script>
</body>
</html>