面向对象(OOP)基本介绍
面向对象特性:
- 封装,此特性可隐藏对象内部的实现细节,对外提供一致的访问接口
- 继承,简单的代码复用机制,使子类拥有父类的特性。
- 多态,以一致的方式使用不用的实现,实现接口不变性。
- 抽象。
JS中如何体现OOP
- 只有对象,没有类(ES5)对象继承对象,而不是类继承类
- 原型对象是基于原型语言的核心概念,原型对象是新对象的模板,它将自身的属性共享给新对象。
- 顶层对象为Object
- 对象的构成:
- 属性:属性描述了对象的状态。
- 方法:是对象具有可实施的动作。
对象的创建和使用
// 对象的创建,new Object()返回一个对象
let obj1 = new Object()
obj1.eat = function(){
console.log(this.name+'oooooo');
}
let obj2 = {}
let obj3 = obj1
obj3.name = 'xxx'
obj3.eat()
{
obj4 = obj3
}
console.log(obj4);//demo.html:25 {name: "xxx", eat: ƒ}
// {
// var obj4 = obj3
// }
// console.log(obj4);//demo.html:25 {name: "xxx", eat: ƒ}
// {
// let obj4 = obj3
// }
// console.log(obj4);//报错
构造函数
构造函数就是ES5中的原型对象
function Person(name,age){
this.name = name
this.age = age
this.eat = function(){
console.log(this.name+'吃吃吃');
}
}
let p = new Person('大曾','18')
console.log(p);
原型对象
- 实例化对象,通过__proto__获取原型对象
- 构造函数,通过prototype获取原型对象
- 实例化对象的隐式原型等于构造函数的显式原型(指向同一个原型对象)
//构造函数就是ES5中的原型对象
function Person(name,age){
this.name = name
this.age = age
this.eat = function(){
return (this.name+'吃吃吃');
}
}
let p = new Person('大曾','18')
console.log(p);//实例化对象
console.log(p.__proto__);//指向Pereson原型对象
console.log(p.__proto__.__proto__);//指向Object原型对象
// 实例化对象的隐式原型等于构造函数的显式原型(指向同一个原型对象)
console.log(p.__proto__==Person.prototype);//true
console.log(Person.prototype.__proto__ === Object.prototype);//true
//1.实例化对象,通过__proto__获取原型对象
//2.构造函数,通过prototype获取原型对象
实例化对象上动态添加属性会不会影响其他的实例化对象?
let p1 = new Person('aaa','10')
p1.sex = '男'
let p2 = new Person('bbb','10')
console.log(p1);
console.log(p2);
实例化对象上动态添加属性====>不会影响其他的实例化对象
在原型对象上添加属性呢?以及原型链查找?
let p1 = new Person('aaa','10')
p1.__proto__.sex = '男'
let p2 = new Person('bbb','10')
console.log(p1);
console.log(p2);
console.log(p2.sex,p2.__proto__.sex,p2.eat(),p2);
在原型对象上添加属性会影响,因为两个实例化对象都指向同一个原型对象。(涉及原型链)
p2先看自己自身有没有属性方法,没有就沿着原型链找。
在Person增加属性呢?
//构造函数也是一个对象
Person.height = 100;
let p = new Person('大曾','18')
console.log(Person,Person.height,p);
构造函数也是一个对象,所以不影响其他的实例化对象。在放蜀山,扩展的属性,只有函数对象可以调用,其他的实例化对象是不能调用这个属性或方法。
console.log(Person);//函数还是函数
console.log(Person.height);//把函数当做对象来使用
怎样让实例化对象有新的方法?
//1.添加在自身
p2.say=function(){
console.log(this.name,'说加个关注吧');
}
//2.添加在原型对象
p2.__proto__.say=function(){
console.log(this.name,'说加个关注吧');
}
//3.构造函数原型对象添加
Person.prototype.say=function(){
console.log(this.name,'说加个关注吧');
}
//4.添加在原型对象的原型对象上
p2.__proto__.__proto__.say=function(){
console.log(this.name,'说加个关注吧');
}
//5.直接加载顶层Object中
Object.prototype.say=function(){
console.log(this.name,'说加个关注吧');
}
p2.say()
属性方法的删除
p2.say=function(){
console.log(this.name,'说加个关注吧');
}
p2.new = 'new'
console.log(p2);
执行删除:
let p1 = new Person('aaa','10')
p1.__proto__.sex = '男'
let p2 = new Person('bbb','10')
delete p2.new //删除自身添加的属性
delete p2.say//删除自身添加的方法
delete p2.sex//删除原型链属性==》失败
console.log(p2);
delete p2.__proto__.sex//删除原型链属性==》成功(p1的sex也没有了)
delete p2.age //删除p2的age方法
console.log(p2);
console.log(p1);
对象的销毁
let p3 = p2
console.log(p3);//{name: "bbb", eat: ƒ}
p2 = null
console.log(p2);//null
console.log(p3);//{name: "bbb", eat: ƒ}
instanceof
instanceof : 返回一个对象 是否是通过另一个原型对象实例化出来的
function Student(){
console.log();
}
let stu = new Student()
let arr1 = new Array()
let arr2 = []
console.log(p1 instanceof Person); //true
console.log(p3 instanceof Person); //true
console.log(p2 instanceof Object); //true
console.log(stu instanceof Student); //true
console.log(stu instanceof Person); //false
console.log(stu instanceof Object); //true
console.log(arr1 instanceof Object); //true
console.log(arr1 instanceof Array); //true
console.log(arr2 instanceof Object); //true
console.log(arr2 instanceof Array); //true
hasOwnProperty()
hasOwnProperty() 判断一个属性是否是对象自己的属性
console.log(p2.hasOwnProperty('sex'));//false
console.log(p2.hasOwnProperty('say'));//true
console.log(p2.hasOwnProperty('new'));//true
for in
for in 会遍历对象自己的属性和它原型链上的所有属性
for(let key in p2){
console.log(key,'===>',p2[key]);
}
对象的get和set方法
调用对象分别输出1,2,3
//方法一
let obj = {
a:1,
get(){return this.a+=1},
set(val){this.a = val}
}
console.log(obj.a);
for(let i = 0;i<2;i++){
obj.set(obj.get())
console.log(obj.a);
}
//方法二
let obj = {
x:10,
get a(){console.log('get'); return this.x+=1},
set a(v){console.log('set',v); this.x = v}
}
obj.a = 0//调用set() v = 0
console.log(obj.a);//调用get()
console.log(obj.a);
console.log(obj.a);
console.log('x',obj.x);
//另一种写法,注意和上一种方法的区分
let obj = {
get a(){console.log('get'); return a+=1},
set a(v){console.log('set',v); a=v}
}
obj.a = 0
console.log(obj.a);//调用get
console.log(obj.a);
console.log(obj.a);
console.log('a',obj.a);
函数中变量的作用域
let a = 10
function test(){
console.log(a);
}
test()//10
function test(){
console.log(a);
let a = 10
}
test()//报错
function test(){
console.log(a);
var a = 10
}
test()//undefined
function test(){
console.log(a);
a = 10
}
test()//报错
function test(){
a = 10
console.log(a)
}
test()//10
console.log(a);//10
------------------------------------------------------------
var a = 10;
function test1(){
console.log(a); //如果函数内部,找不到变量,就向函数的上一层作用域空间去找;
var b = 20;
console.log(b); //内部变量的作用域,就只在声明的环境空间。
c = 30; //一个变量,如果没有使用var,let,const关键字来声明它,这个变量就会被声明成一个全局的变量。因此它的作用域就是全局环境空间。
console.log(c);
function test2(){
var d = "hello";
}
test2()
// console.log(d);//报错
}
test1();
// console.log(b); //b is not defined, 在函数外层,是不能调用函数内部声明的变量的。
console.log(c); // 30
var,let,const作用域比较
var
var i = 1;
for(; i< 10; i++){
setTimeout(function(){
console.log("i=",i); // i = 10, 共9次;
},10);
}
console.log(i); // 10
let
//let 声明 的变量,只在声明的环境中,或者是 {}里有效;
for(let i = 1; i< 10; i++){
setTimeout(function(){
console.log("i=",i); // i = 1,i=2,....,i=9;
},0);
}
console.log(i) // i is not defined
const
function test3() {
const SEX = "男"
if (true) {
console.log("aaaa");//aaa
}
else {
const PI = 3.14;
console.log(PI);
}
console.log(SEX); //男
// console.log(PI);//PI is not defined
}
test3();
console.log(SEX); // SEX is not defined
封装
为什么要封装?
隐藏内部的实现,只暴露出少量的接口供使用;
示例:
function myAjax (obj){
console.log(obj);
}
myAjax = ""//目标被全局污染
myAjax(); // myAjax is not a function
目标被全局污染了(被篡改)
解决方法
改变函数的作用域
方法一:
function test(obj) {
function myAjax(obj) {
console.log(obj);
}
return myAjax;
}
let _ajax = test()
_ajax(123)//123
myAjax = 2342;
myAjax() //demo1.html:27 Uncaught ReferenceError:
方法二:
函数表达式赋值
const fn1 = function(){
console.log('执行');
}
fn1() //执行
方法三(推荐)
使用一个匿名函数,再使用函数自执行的方法,把函数包起来,利用函数内部变量、内部 的函数,只能在函数内部使用这个特性,来实现一个封装避免目标变量被全局污染。
//函数自执行之前,必须要有一个";"
(function(){
function my1(){
console.log(111);
}
function my2(){
console.log(222);
}
function my3(){
console.log(333);
}
// my()//111 公开接口
window._my1 = my1
window._my2 = my2
window._my3 = my3
})();
_my1()
_my2()
_my3()
闭包
闭包是什么?
函数内部的变量,是不能在函数外使用的!
如果让函数内部的变量,可以在函数外面使用呢?解决方法就是使用闭包这个技术 。
闭包就是让函数内部的局部变量,一直被释放或销毁,一直存储于内存中。
闭包要注意什么?
不要直接返回闭包中的值;这样让外面的变量,直接引用此值,如果对外面的变量进行不合规则的操作,就会导致内部要隐藏的变量中数据错误!!
当闭包中,使用的是引用类型数据时,如数组时,应当避免直接把闭包的变量返回给用户。
一般的做法,就是重新声明一个引用类型的变量;只接收闭包变量中的数据,而不是通过赋值来直接引用闭包变量;
当要使用闭包的变量,是一个引用类型时,一定要注意,防止修改到函数内部的这个变量.
实现闭包的方式1
window.xxx = aaa;
暴露一个API;就可使用函数内部的方法了,同时这个方法,又要使用test1中的局部 变量,导致age变量,一直存储于内存中。
这样就是一个闭包的实现 。
function test1() {
let age = 30;
function getAge() {
return age;
}
function setAge(value) {
age = value;
}
window.get = getAge;
window.set = setAge;
}
test1();
set(1000);
let age = get();
console.log("age=", age);//1000
实现闭包的方式2
return xxx
不使用暴露的方法,使用return ,返回变量,对象,函数,数组和基本数据类型的字面量;
function test(){
let uname = "xiao黄ren"
function getName (){
return uname;
}
function setName (v){
uname = v;
}
return {getName, setName}
}
// 全局的变量,引用了函数内部的东西:如function,object,array;
let obj = test()
obj.setName("小黄")
let n = obj.getName();
console.log(n); //小黄
实现闭包的方式3
类似于bind()(),在函数中,返回函数内部 的变量、对象、函数,都会形成一个闭包
function test1(){
function test2(){
console.log("test2");
function test3(){
console.log("test3");
function test4(){
console.log("test4");
}
return test4;
}
return test3;
}
return test2;
}
// 函数的连续调用语法:
test1()()()();
// 分步理解:
// let test2 = test1();
// let test3 = test2();
// let test4 = test3();
// test4();
闭包的问题
由于长驻内存,导致内存占用过多,机器变慢;
IE中,会导致内存泄露的这个BUG.
在不需要使用闭包中的对象时,一定手动释放对象
obj = null;
只要不再引用闭包中的变量,对象,那系统就通过垃圾回收机制,自动销毁它们。
关于函数的声明和调用
普通的函数声明 ,可以在声明之前调用函数,也可以声明之后调用函数
因为: 函数声明 ,就和变量的声明一样,都会前置声明;
声明函数,不会执行函数体中的代码;调用函数时,才被执行
var test;
function test(){console.log(1)}
test()//1
函数表达式,只有代码执行到此处时,才进行赋值。
如果写的是函数表达式,则只能在表达式之后调用函数。
test()//test is not a function
test = function(){
console.log(2)
}
test()//2
总之,函数应该坚持 先声明,再调用的书写习惯!!
变量也是,应该先正确的声明 ,再使用变量.
构造函数的继承及多态
call,apply,bind
把当前的实例化对象,传给父类中的this
语法:父类.call(子类的实例化对象,参数1,参数2,参数3,…)
注意,要先写继承的call(),apply(),再在它们后面写子类自己的属性。、
在子类中重新定义一个方法,实现一个多态的技术的表现;
同一个接口,不同的实现,就是多态。
function Person(name,age){
console.log('Person',this);
this.name = name
this.age = age
//多态,同一接口不同实现
this.eat = function(){
console.log('吃饭1');
}
}
function Student(name,age,sname,sage,sex){
console.log('Student',this);
Person.call(this,name,age) //this在Person中指向Student
// Person.apply(this,[name,age])
// Person.bind(this,name,age)()
this.sname = sname
this.sage = sage
this.sex = sex
//多态,同一接口不同实现,覆盖call内的eat
this.eat = function(){
console.log('吃饭2');
}
}
let s1 = new Student('老师','22','学生','8','男')
console.log(s1);
s1.eat()
Person.prototype.color = 'red'
let p = new Person('曾曾','20')
console.log(s1,p,Person.prototype);
call(),apply(),只继承了父类自己的属性,父类的原型链的属性是没有继承下的。
封装ajax
核心判断type类型,url拼接,data转换,以及原生ajax使用
// 封装一个自己的ajax函数
(function(){
function myAjax(obj) {
// 1. 语法检查:
if (typeof obj !== "object")
throw new Error("参数必须是一个对象");
if (typeof obj.url !== "string")
throw new Error("参数url必须是一个string");
if (typeof obj.type !== "string")
throw new Error("参数type必须是一个string");
if (typeof obj.async !== "boolean")
throw new Error("参数async必须是一个boolean");
if (typeof obj.success !== "function")
throw new Error("参数success必须是一个function");
if (typeof obj.data !== "object" && typeof obj.data !== "string")
throw new Error("参数data必须是一个 object or string.");
// 2 创建一个xhr对象
let xhr = null;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject("Microsoft.XMLHTTP")
}
// 3. 判断obj.type,是GET,还是POST?
// console.log(obj.type.toUpperCase()); //'GET'
if (obj.type.toUpperCase() === 'GET') {
// GET时,把参数拼接到url后;
obj.url = obj.url + "?" + formatData(obj.data);
xhr.open(obj.type, obj.url, obj.async);
xhr.send();
} else if (obj.type.toUpperCase() === 'POST') {
xhr.open(obj.type, obj.url, obj.async);
// post,增加设置请求头代码,参数放在send()中;
xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded");
xhr.send(formatData(obj.data));
} else {
throw new Error(" 请求的type,必须get,post,put,delete,option几种类型之一")
}
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
let response = xhr.responseText;
// 调用回调函数,把后端数据做参数传入success.
obj.success(response)
}
}
// 把请求的参数,转换成key=value这种格式的字符串。
// 参数的序列化,把对象序列化成字符串;JAVA后台,只能接收字符串格式 的参数;
function formatData(param) {
let str = ""
if(typeof param === "object"){
// // {a:1,b:2,c:3} ==> "a=1&b=2&c=3&"; ===> "a=1&b=2&c=3";
// for(let key in param){
// str += `${key}=${param[key]}&`;
// }
// // str = str.replace(/&$/,"") //正则来处理
// str = str.substr(0, str.length-1) // 截取子串三种也可以。
// // console.log(str); //a=1&b=2&c=3
//深层{a:1,b:2,c:3,d:[1,2,3,{name:'user',pass:111},5],e:5}===>a=1&b=2&c=3&d=[1&2&3&{name=user&pass=111}&5]&e=5
let str = JSON.stringify(data)
str = str.slice(1,str.length-1)
str = str.split(',').join('&').split(':').join('=').split('"').join('')
console.log(str);
}else if(typeof param === "string"){
str = param.trim();
}
return str
}
}
// 公开一个接口
window._ajax = myAjax;
})();
使用myajax
let url = "http://127.0.0.1:8000/test";
let type = "GET";
let data = {a:1,b:2,c:3};
let async = true;
let success = (data)=>{
console.log(data);
data = JSON.parse(data);
if(data.status === 200){
alert("请求成功")
}
}
// 调用ajax();
_ajax({
url,
type,
data,
async,
success
})
手写call,apply,bind
核心:
let p = new Person('曾曾','20')
let s1 = new Student('老师','22','学生','8','男')
for(key in p){
console.log(key);
console.log(p.hasOwnProperty(key));
if(p.hasOwnProperty(key)==true){
s1[key] = p[key]
}
}
mycall
Father.myCall = function(obj,...args){
let father = new this(...args)
console.log(father);
for(i in father){
if(father.hasOwnProperty(i)){
obj[i] = father[i]
}
}
}
function Father(money,money2){
console.log(money,money2);
this.money = money
this.money2 = money2
this.add = function(){
console.log('积蓄'+this.money+'积蓄加一'+this.money2);
}
}
Father.prototype.sfmoney = '私房钱'
function Son(money,money2,name,age){
Father.myCall(this,money,money2)
this.name = name
this.age = age
this.over = function(){
console.log(this.name+'在'+this.age+'交女朋友花完了');
}
}
let baba = new Father('20w','30w')
let son = new Son('10块钱','20块钱','儿子','18岁')//调用mycall方法
console.log(baba);
console.log(son);
son.add()
myapply
Father.myApply = function(obj,...args){
let father = new Father(...args[0])
console.log(father);
for(i in father){
if(father.hasOwnProperty(i)){
obj[i] = father[i]
}
}
}
function Father(money,money2){
this.money = money
this.money2 = money2
this.add = function(){
console.log('积蓄'+this.money+'积蓄加一'+this.money2);
}
}
Father.prototype.sfmoney = '私房钱'
function Son(money,money2,name,age){
Father.myApply(this,[money,money2])
this.name = name
this.age = age
this.over = function(){
console.log(this.name+'在'+this.age+'交女朋友花完了');
}
}
let baba = new Father('20w','30w')
let son = new Son('10块钱','20块钱','儿子','18岁')//调用mycall方法
console.log(baba);
console.log(son);
son.add()
mybind
Father.myBind = function(obj,...args){
let father = new this(...args)
console.log(father);
for(i in father){
if(father.hasOwnProperty(i)){
obj[i] = father[i]
}
}
return function(name,age){
console.log('1111',name,age);
}
}
function Father(money,money2){
console.log(money,money2);
this.money = money
this.money2 = money2
this.add = function(){
console.log('积蓄'+this.money+'积蓄加一'+this.money2);
}
}
Father.prototype.sfmoney = '私房钱'
function Son(money,money2,name,age){
Father.myBind(this,money,money2)(name,age)
this.name = name
this.age = age
this.over = function(){
console.log(this.name+'在'+this.age+'交女朋友花完了');
}
}
let baba = new Father('20w','30w')
let son = new Son('10块钱','20块钱','儿子','18岁')//调用mycall方法
console.log(baba);
console.log(son);
son.add()
ES5实现构造函数实现继承
es5构造函数实现继承的几种方式:
- call,apply,bind 更改this指向实现继承,缺点不能继承原型链上的属性
- 儿子.prototype = new 父亲(),缺点无法传给父类参数
- for in 遍历赋值 (包含原型链)
if (子[key] == undefined) {
子[key] = 父[key];
}- 用一个函数xxx包裹父类,父类.prototype = xxx.prototype;
再对xxx赋值更改原型的内容,如xxx.prototype.study = “好好学习,天天向上。”,父类自然就有了 ,外面使用 window.x = xxx抛出,a.study自然就有了,new xxx()指向父类
修改构造函数子类的原型
第一种方法已经说过了就不再赘述了,说第二种方法,修改构造函数子类的原型,让其指向父类。
// 创建一个人的构造函数(原型对象)
function Person(name, age) {
this.name = name;
this.age = age;
this.eat = function () {
console.log("用手抓东西来吃;");
}
}
// 创建一个学生的构造 函数(原型对象)
function Student( cNumber, cName, sex) {
console.log(Student.prototype)//Person {name: "张三", age: 12, eat: ƒ}
this.cName = cName;
this.cNumber = cNumber;
this.sex = sex;
this.eat = function () {
console.log("用筷子夹东西来吃;");
}
}
// 在第一行,写代码
// prototype 只能指向一个实例化对象,修改了当前实例化对象的原型链;通过原型链来实现属性的继承!
Student.prototype = new Person("张三",12)
let s1 = new Student(1, "平果", '男');
console.log(s1);
console.log(s1.name)//"张三"
let s2 = new Student(1,"糖果",'女')
console.log(s2);
console.log(s2.name) //"张三"
- prototype 只能指向一个实例化对象,修改了当前实例化对象的原型链;通过原型链来实现属性的继承!
- 缺点:只是实现了属性的继承,但是不能正常的在实例化student(子类)时,对继承的属性进行赋值;
for in 实现继承
所有属性方法全部继承,包含原型链。
// 创建一个人的构造函数(原型对象)
function Person(name, age) {
this.name = name;
this.age = age;
this.eat = function () {
console.log("用手抓东西来吃;");
}
}
// 创建一个学生的构造 函数(原型对象)
function Student(cNumber, cName, sex) {
this.cName = cName;
this.cNumber = cNumber;
this.sex = sex;
this.eat = function () {
console.log("用筷子夹东西来吃;");
}
}
Person.prototype.xx = '原型属性'
let p1 = new Person("吴刚", 23);
let s1 = new Student(224, 'WEB前端', '男');
// 让s1从p1实现属性的继承:
// 从p1继承属性,那么就遍历 p1;
for (let key in p1) {
// console.log(s1[key]) //如果没些属性,则是undefined;
// 如果s1没有某个属性,那就添加这个属性:
// s1.key => s1[key] ==> 当变量做对象的动态属性名时,使用[]来获取获取属性的值;
if (s1[key] == undefined) {
s1[key] = p1[key];
// s1.name = p1.name;
// s1.age = p1.age
}
}
console.log(s1);
封装的函数的原型对象继承内部的原型对象
要求:
1. 对Person进行隐藏; 使用util的函数画封装它;
2. 当调用util()时,返回一个 new Person()的实例wx对象;
3. 在util.prototype上扩展的属性,去实现在Person的原型对象上去扩展;
4. 我要把util.prototype也隐藏了,对外提供一个叫fn的方法来修改util.prototype
通过修改prototype的指向 ,来实现继承
修改Person.prototype的指向,让它指向 uitl.prototype;因为,我们是在 util.prototype上进行属性扩展;
而new Person()实例的原型链上,没有study属性;因此,想要拿 study属性,只能修改原型链;
把new Person()的原型对象,指向 util.prototype;
通过修改prototype的指向 ,来实现继承;
普通函数方式
function util() {
function Person(name, age) {
this.name = name;
this.age = age;
this.eat = function () {
console.log("用手抓东西来吃;");
}
}
Person.prototype = util.prototype;
return new Person();
}
util.fn = function(){
util.prototype.study = "好好学习,天天向上
}
let p1 = util()
util.fn()
console.log(p1.study,p1)
自执行方式
(function(){
function _util() {
function Person() {
this.name = "张三";
this.age = 22;
this.eat = function () {
console.log("用手抓东西来吃;");
}
}
Person.prototype = util.prototype;
// Person.prototype ---> util.prototype;
// new Person().__proto__ ---> util.prototype;
return new Person();
}
_util.fn = function(){
util.prototype.study = "好好学习,天天向上
}
window.util = _util;
})()
let p1 = util()
util.fn()
console.log(p1.study,p1)