创建构造函数
和普通函数的书写方式一样(命名以大驼峰方式)
let num=123;
console.log(num,num.length,typeof num);//123,undefined,"number
let f70=num.toString();
console.log(f70,typeof f70);
字符串可以直接使用String的方法和属性
console.log("123".length);
基本数据类型:数值不可以直接使用toString方法
原因:数组分整型和浮点型(小数)
123.toString() 123后边的点,会被视为小数点,不会装箱为对象
解决方法如下
console.log((123).toString());
自动拆箱:自动装箱成对象,然后自动变成基本数值类型。
let num=new String(456);
num.abc=123;
console.log(num.abc);//123
console.log(456+1);//4561
console.log(456-1);//455
构造函数
工作原理
1、创建空对象,执行代码
2、执行代码,返回值
3、在外部调用
function Students(name,age,gender,weight){
当创建构造函数时,会自动添加this对象
let this={};然后向里添加
this.name=name;
this.age=age;
this.gender=gender;
this.weight=weight;
之后返回this对象
return this 默认值
如果返回值是手动写的基本数据类型
如 return 123; 权重没有自动生成的返回值高,所有没有用的。
如果返回的是对象,
如 return [123]; 返回值是undefined;
}
当用调用时,在返回值this中取值
用new的构造函数,返回值一定是个对象
let stu1=new Students("xiaoguo",18,"male","100kg");
let stu2=new Students("xiaomi",15,"female","10kg");
console.log(stu1.name);
console.log(stu2.gender);
面向过程:把解决问题的方法和步骤,一步一步写出来(JS代码)
Oriented Object Analysis Design 面向对象分析设计
面向对象:把解决问题的方法和步骤中
涉及到的属性和方法,封装成一个函数(对象)
即对象的抽象;
类 Class
类是对象的的抽象,对象是类的实例
ES6 类 语法糖
class 首字母大写的名字 {}
class Students{
name="123";
sayhello=function(){
console.log(`hello,${this.name}`);
}
haha(){
console.log("haha");
}
}
let stu=new Students();
console.log(stu.name);
stu.sayhello();
stu.haha();
ES6 类 语法糖
注:类似封装一个构造函数
typeof 类实际情况就是function
普通函数和构造函数本质区别:调用方式
1、普通函数:函数名()
2、构造函数:new 函数名(){}
3、类: new 类名(){}
class Students{
// constructor构造器
constructor(name,age,gender){
this.name=name;
this.age=age;
this.gender=gender;
}
sayhello(){
console.log("hello", this.name);
}
}
ES5写法
function Teachers(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
this.sayhello=function(){
console.log("hello", this.name);
}
}
let tex=new Teachers("l4",28,"female");
Students.prototype.banjiName="f70";
类添加原型是 类名.prototype.对象名
其他实例都可以用
let stu=new Students("z3",18,"male");
let stu1=new Students("l3",18,"male");
实例添加原型是 类名.proto.对象名;
其他实例都可以用
stu.__proto__.isRich=true;
console.log(stu.isRich);
console.log(stu1.isRich);
let stu2=new Students();
ES5可以使使用普通函数调用方法,ES6会报错,提示调用前加new
let stu2=Students();
私有:
this.XXX=XXX;
this.XXX=function(){}
公有:
类.prototype.XXX=XXX;
类.prototype.XXX=function(){};
实例.__proto__.XXX=XXX;
实例.__proto__.XXX=function(){};
注:同类别的实例对象可以使用公共的属性和方法
new 一个实例的工作流程
1、this的空对象
2、this的"proto"指向"类.prototype"
3、执行代码(给this添加属性和方法)
4、return this(即返回对象给实例)
注:手动更改返回值,更改的基本数据类型无效
更改引用数据类型有效
【this指向】
1、普通对象,谁调用指向谁
2、构造函数中的this,指向的是实例化的对象
3、监听器中的this,指向的是事件源:绑定事件的节点
4、普通函数中的this,指向的是window
注:严格模式下( "use strict"),指向的是undefined;
【1】
function f70(){
this.name=10
console.log(this.name);
}
let stu=f70();
【2】
function f70(){
this.name=10;
}
let stu=new f70();
stu.name=20;
let stu1=new f70();
console.log(stu.name);
console.log(stu1.name);
【3】
document.addEventListener("mouseover",function(){
console.log(this);
})
【4】
严格模式
function f70(){
"use strict";
console.log(this);
}
f70();
5、箭头函数中this的指向,取决于所处环境
箭头函数没有自己的arguments和this
class Students{
constructor(name){
• this.name=name;
• this.shuchu();
}
shuchu(){
• console.log(this.name);
}
}
let stu1=new Students("xiaoguo");
console.log(stu1);
Object.assign(stu1,{gender:true},{name:"gao"});
console.log(stu1);
改变this的指向
function f70(){
this.name="a";
this.say=function(weight,height){
• console.log(this.name,weight,height);
}
}
let stu1=new f70();
let stu2={
// name:"xiaoguo"
}
谁调用.方法名.call(指向谁的原型,函数上传参数,);
立即执行
stu1.say.call(stu2,"70kg","188");
谁调用.方法名.apply(指向谁的原型,函数上传参数用数组方式);
立即执行
stu1.say.apply(stu2,["65kg","175"]);
谁调用.方法名.bind(指向谁的原型,函数上传参数)有返回值,返回的是一个函数;
用变量接受后,取决于我什么时候用它;
写法1
let xiaoguo=stu1.say.bind(stu2,"80kg","190");
xiaoguo();
写法2 可以先接受函数时传参,也可以调用时传参
let xiaoguo=stu1.say.bind(stu2,"80kg");
xiaoguo("190");
作用域链是找变量的
原型链
1、在我自身找属性,没有做第2步
2、在原型找属性,没有有做第3步
3、在原型.proto里找
每一个函数都有一个prototype属性叫原型
let fn=new Function();
console.log(Function.prototype);//原型
console.log(fn.__proto__);//原型
console.log(fn.__proto__.__proto__);//{} Object
console.log(fn.__proto__.__proto__.__proto__);//null
面向对象的3大特征
1、封装
2、继承
3、多态
【封装】
ES6 把属性和方法封装成一个对象
【多态】
同一个磨具,生产出不同的产品
同一个构造函数或类、实例化不同的对象(根据参数的不同);
不同的实例对象,调用同一个方法,得到不同的结果
【继承】
function Grandpa(faqi){
this.faqi=faqi;
}
let g1=new Grandpa("大保健");
function Father(money){
this.money=money;
}
let f1=new Father(10000);
function Son(age){
this.age=age;
}
let s1=new Son(80);
Object.assign(s1.__proto__.__proto__,g1);
// Object.assign(Son.prototype,f1);
// ES5写法
// s1.__proto__=new Father(10000);
console.log(s1.money);
console.log(s1.age);
console.log(s1.faqi);
ES6继承
class Father{
constructor(money){
• // this.name=name;
• this.money=money;
}
say(){
• console.log(this.name,this.age);
}
hello(){
• console.log(1);
}
}
class Son extends Father{
constructor(name,age,money){
• super必须放在this之前
• super(money);
• this.age=age;
• this.name=name;
}
say(){
• console.log(this.name,this.age);
}
}
let s1=new Son("z3",12,10000);
console.log(s1.money);
s1.hello();
function Grand(money){
this.money=100;
}
Grand.prototype.say=function(){
console.log(22);
}
function Father(age){
// this={}
// this.__proto__= Father.prototype
this.xiaoguo=Grand
this.xiaoguo();
delete this.xiaoguo;
this.age=18;
}
Father.prototype.hello=function(){
console.log(11);
}
function Son(name){
// 如何拿到名字
// this={}
// this.__proto__= Son.prototype
this.xiaoguo=Father
this.xiaoguo();
delete this.xiaoguo;
this.name=name;
}
Son.prototype=new Father(18);
Father.prototype.__proto__=Grand.prototype
Son.prototype.__proto__=Father.prototype;
let s1=new Son("Z3");
console.log(s1.name);//z3
如何输出Z3
function Son(name){
如何拿到名字z3,当new一个函数后,函数为构造函数
然后会创建一个this为空对象
this={}
然后this的"__proto__"指向"名字是Son类.prototype";
this.name=name;
向里添加name:"z3";
最后返回this这个对象;
}
console.log(s1.age);//18
如何输出18
function Son(name){
如何拿到名字z3,当new一个函数后,函数为构造函数
然后会创建一个this为空对象
this={}
然后this的"__proto__"指向"名字是Son类.prototype";
this.name=name;
调用这个普通函数;普通函数指向是谁调用我,this就指向谁
this.xiaoguo=Father
this.xiaoguo();
function Father(age){
this.age=18;
}
向里添加name:"z3";
跟age:"18";
性能优化删除这个函数
delete this.xiaoguo;
最后返回this这个对象;
}
console.log(s1.money);//100
同样的道理在Father函数里书写
s1.hello();//11
s1.say();//22
Father.prototype.__proto__=Grand.prototype
Son.prototype.__proto__=Father.prototype;
s1没有hello()这个方法,就向原型找,原型没有继续向上找,都没有;
所有要修改指向:Son.prototype.__proto__修改指向到Father.prototype;
S1就得到了hello()的方法;
s1没有say()这个方法,就向原型找,原型没有继续向上找,都没有;
所有要修改指向:Father.prototype.__proto__修改指向到Grand.prototype
S1就得到了hello()的方法;
【补充内容:拓展】
let obj={
name:"z3",
}
obj.age=123;
Object.defineProperty(obj,"gender",{
value:"male",
writable:true,
enumerable:true
});
console.log("a",obj);
obj.gender="female";
console.log("b",obj);
for(let item of Object.keys(obj)){
console.log(item);
}
属性被赋值或更改时,触发set的方法
属性被使用时,触发get方法
let age=0;
let obj={
name:"z3",
}
Object.defineProperty(obj,"cc",{
set(value){
• if(value<40){
• age=18;
• }else{
• age=19;
• }
• ;
},
get(){
• return age;
}
});
console.log(obj.cc);
obj.cc=30;
console.log(obj.cc);
obj.cc=50;
console.log(obj.cc);
【深拷贝,浅拷贝】
浅拷贝:共用一个地址,因此其中一个更改,另一个也会发生变化;
let arr=[1,2,3];
let arr2=arr;
arr[2]="a";
console.log(arr2);//[1,2,"a"]
console.log(arr);//[1,2,"a"]
深拷:贝共用一个地址,因此其中一个更改,另一个不会发生变化;
let arr=[1,2,3];
let arr2=arr.slice(0);
arr[2]="a";
console.log(arr2);//[1,2,3]
console.log(arr);//[1,2,"a"]
例2:
深拷贝:只有不共用一个地址就是深拷贝
let obj1={
name:1,
age:2
}
{...obj1}
... 的功能
功能1:批量修改跟赋值();
功能2:不定参数
let obj2={...obj1};
obj1.name="A";
console.log(obj2.name);