目录
8.2静态方法和属性:实例不会继承的属性和方法,但是子类还是会继承父类的静态方法和属性
10.1hasOwnProperty():看是不是对象自身底下的属性
10.2contructor()查看对象的构造函数 可以用来做判断是属于哪个构造函数:
10.3instanceof():对象与构造函数是否在原型链上有关系。检测的是原型:
1.主题:
- 拖拽案例
- 构造函数继承
- 原型的继承
- 拖拽案例的继承改造
- es6中的类的用法
2.拖拽的构造函数实现
2.1- 拖拽的面相过程写法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
#div1 {
width: 100px;
height: 100px;
background: red;
position: absolute;
top: 20px;
left: 50px;
}
#div2 {
width: 100px;
height: 100px;
background: blue;
position: absolute;
top: 200px;
left: 400px;
}
</style>
</head>
<body>
<div id="div1"></div>
<div id="div2"></div>
<script>
{
//需求:面向过程方法实现拖拽
let div1 = document.querySelector("#div1");
div1.addEventListener("mousedown",function(ev){
//浏览器兼容
let e = ev || window.event;
//获取初始化鼠标坐标
let startPos = {};
startPos.x = e.clientX;
startPos.y = e.clientY;
//获取初始left和top
let left = div1.offsetLeft;
let top = div1.offsetTop;
// console.log(startPos);
function move(ev){
let e = ev || window.event;
let curPos = {};
curPos.x = e.clientX;
curPos.y = e.clientY;
// console.log(curPos);
console.log(curPos.x - startPos.x + div1.offsetLeft);
console.log(curPos.y - startPos.y + div1.offsetTop);
div1.style.left = curPos.x - startPos.x + left + 'px';
div1.style.top = curPos.y - startPos.y + top + 'px';
}
document.addEventListener("mousemove",move);
document.addEventListener("mouseup",function(){
document.removeEventListener("mousemove",move);
},{
once:true
});
});
}
</script>
</body>
</html>
2.2- 拖拽的面相对象写法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
#div1 {
width: 100px;
height: 100px;
background: red;
position: absolute;
top: 20px;
left: 50px;
}
#div2 {
width: 100px;
height: 100px;
background: blue;
position: absolute;
top: 200px;
left: 400px;
}
</style>
</head>
<body>
<div id="div1"></div>
<div id="div2"></div>
<script>
{
//需求:面向对象方法实现拖拽
//构造函数一般只写属性
function Drag(ele){
this.ele = ele;
this.mouseDown();
}
Drag.prototype.mouseDown = function(){
//需要将this传递到事件里:赋值或者箭头函数
this.ele.addEventListener("mousedown",ev=>{
//浏览器兼容
let e = ev || window.event;
//获取初始化鼠标坐标,直接求出初始化时鼠标坐标和left/top的值,以免要传递多个参数
let x = e.clientX - this.ele.offsetLeft;
let y = e.clientY - this.ele.offsetTop;
this.mouseMove(x,y);
this.mouseUp(x,y);
});
}
//由于move方法也需要共用,所以也写在原型上
Drag.prototype.setStyle = function(l,t){
this.ele.style.left = l + 'px';
this.ele.style.top = t + 'px';
}
Drag.prototype.mouseMove = function(x,y){
//如果将onmousemove加在document上并使用监听和匿名函数,则在move方法中的this会指向document,所以需要加在this.ele
//使用监听时不好传递参数
document.onmousemove = ev=>{
let e = ev || window.event;
let left = e.clientX - x;
let top = e.clientY - y;
this.setStyle(left,top);
};
}
Drag.prototype.mouseUp = function(x,y){
document.onmouseup = ev=>{
//清除鼠标移动事件可以直接使this.ele.onmousemove = "";
document.onmousemove = "";
};
}
let div1 = document.querySelector("#div1");
let div2 = document.querySelector("#div2");
let drag1 = new Drag(div1);
let drag2 = new Drag(div2);
}
</script>
</body>
</html>
3.构造函数继承
- 继承:子类继承父类所有属性和行为,父类不受影响。
- 目的:找到类之间的共性精简代码
- ES5中,使用call()/apply()/bind()修改this指向来实现继承
- 只会继承父类的构造函数,而不会继承父类的原型
- 解决原型不会继承继承问题:直接将父类原型赋值给子类,JOSN序列化拷贝对象,自定义深拷贝方法,组合继承
- 简单原型继承(直接将父类原型赋值给子类),当子类修改原型时会影响父类的情况(传址给子类);
- JOSN序列化原型继承:当对象中存在undefined和函数时,拷贝的对象会忽略
3.1构造函数继承:
通过call()/apply()/bind()修改this指向来实现,子类继承父类所有属性和行为
//继承:子类继承父类的所有属性和行为,父类不受影响
function Parent(name){
this.eyes = 2;
this.name = name;
this.legs = 2;
}
//写子类,通过call()/apply()/bind()方法修改子类的this指向,并传入参数即可
function Son(name){
Parent.call(this,name);
//并由自己的独特属性
this.hobby = "打篮球";
}
//发现子类继承父类后,拥有所有父类的属性和行为,父类不受影响
let parent = new Parent("老张");
let son = new Son("小明");
console.log(parent);//Parent {eyes: 2, name: "老张", legs: 2}
console.log(son);//Son {eyes: 2, name: "小明", legs: 2, hobby: "打篮球"}
- 继承问题:继承只会继承父类的构造函数,而不会继承父类的原型
//继承:只会继承父类的构造函数,而不会继承父类的原型
Parent.prototype.job = function(){
console.log("程序员");
}
let parent = new Parent("老张");
let son = new Son("小明");
parent.job();//程序员
son.job();//报错:Uncaught TypeError: son.job is not a function
4.原型的继承
解决构造函数继承不能继承原型问题:直接将父类原型赋值给子类;JSON序列化;自定义深拷贝继承;组合继承
- - 简单原型继承(直接将父类原型赋值给子类),出现影响父类的情况;
如下:直接将父类原型赋值给子类后,子类拥有了父类的原型,但是当子类修改了原型时,父类原型也收到了影响。所以这种方式不可用。解决:进行深拷贝,见下一节。
//简单继承:直接将父类原型赋值给子类。问题:修改子类原型时会影响父类
Son.prototype = Parent.prototype;
let parent = new Parent("老张");
let son = new Son("小明");
parent.job();//程序员
son.job();//程序员
//当通过子类Son修改赋值得到的原型时,发现父类的原型也发生了改变
Son.prototype.job = function(){
console.log("销售");
}
parent.job();//销售
son.job();//销售
5.原型的深拷贝继承
深拷贝继承(JSON序列化/自定义深拷贝函数);组合继承
Object.assign()方法只会拷贝一层;arr.flat()方法扁平化多维数组,都不适合多层的深拷贝。
- 传值和传址问题:
- - 基本数据类型传值:Number、String、Boolean、Null、Undefined、BigInt、Symbol
- - 复杂数据类型/引用数据类型传址:Array、Date、Math、RegExp、Object、Function等
5.1JSON序列化继承原型
- JSON序列化的不足:
- 如果拷贝对象包含函数,或者undefined等值,此方法就会出现问题,不会进行拷贝函数和undefined。
所以如果原型上有为undefined或者函数时,尽量不要使用JSON进行原型拷贝。
//JSON序列化解决不能继承原型的问题。问题:当对象中有undefined或函数时,不会进行拷贝
let parentProto = {
name:"张三",
age:20,
test:undefined,
fn:function(){
console.log("fn...");
}
};
let sonProto = JSON.parse(JSON.stringify(parentProto));
console.log(parentProto);//{name: "张三", age: 20, test: undefined, fn: ƒ}
console.log(sonProto);//{name: "张三", age: 20}
5.2自定义深拷贝函数:
如果对象上有多层对象嵌套,可以进行深拷贝。
问题1:for...in循环会循环原型上的属性或方法,所以需要使用hasOwnProperty()方法判断,传入的对象自身的属性而不是原型上的属性,再进行深拷贝
问题2:但是如果有属性值为null,通过自定义深拷贝函数进行拷贝后,得到的对象中,null会转为一个空对象。
//自定义深拷贝函数
let parentProto = {
name:"张三",
age:20,
gender:undefined,
addr:null,
fn:function(){
console.log("fn...");
}
};
function deepCopy(obj){
//判断参数是对象还是数组,如果是数组就创建新数组进行拷贝,如果是对象就创建新对象进行拷贝
let newObj = Array.isArray(obj)?[]:{};
//通过for...in循环进行拷贝
for(let key in obj){
//hasOwnProperty():看是不是对象自身底下的属性
//for...in循环会循环原型上的属性或方法,所以需要使用hasOwnProperty()方法判断,传入的对象自身的属性而不是原型上的属性,再进行深拷贝
if(obj.hasOwnProperty(key)){
//如果得到的属性仍然是一个对象,就继续进行循环
if(typeof obj[key] == "object"){
newObj[key] = deepCopy(obj[key]);
}else{
newObj[key] = obj[key];
}
}
}
return newObj;
}
let sonProto = deepCopy(parentProto);
console.log(parentProto);//{name: "张三", age: 20, gender: undefined, addr: null, fn: ƒ}
console.log(sonProto);//{name: "张三", age: 20, gender: undefined, addr: {…}, fn: ƒ}
5.3使用自定义深拷贝函数实现原型继承:
注意:必须在进行深拷贝时是直接将父类进行拷贝后的对象赋值给子类,会覆盖掉原有的原型对象上的constructor。这样当需要通过原型上的contructor 判断属于哪个构造函数时,会失效。所以,需要将原型中的构造函数进行追加Son.prototype.constructor = Son;
//继承:子类继承父类的所有属性和行为,父类不受影响
function Parent(name){
this.eyes = 2;
this.name = name;
this.legs = 2;
}
function Son(name){
Parent.call(this,name);
this.hobby = "打篮球";
}
//继承:只会继承父类的构造函数,而不会继承父类的原型
Parent.prototype.job = function(){
console.log("程序员");
}
//自定义深拷贝实现原型继承
function deepCopy(obj){
let newObj = Array.isArray(obj)?[]:{};
for(let key in obj){
if(obj.hasOwnProperty(key)){
if(typeof obj[key] == "object"){
newObj[key] = deepCopy(obj[key]);
}else{
newObj[key] = obj[key];
}
}
}
return newObj;
}
Son.prototype = deepCopy(Parent.prototype);
//实例化后的对象有构造函数和原型构成,如果只拷贝原型会丢失构造函数,所以还需要将构造函数返回
//有时候会通过constructor来判断实例化后的对象是属于哪个构造函数,如果Son.prototype.constructor = Son;不写就判断不了
Son.prototype.constructor = Son;
let parent = new Parent("老张");
let son = new Son("小明");
Son.prototype.job = function(){
console.log("销售");
}
parent.job();//程序员
son.job();//销售
重新追加constructor :Son.prototype.constructor = Son;
//如果不写Son.prototype.constructor = Son;时
console.log(son.constructor);// Object() { [native code] }
Son.prototype.constructor = Son;
console.log(son.constructor);//Son(name){Parent.call(this,name);this.hobby = "打篮球";}
需要追加constructor 的作用:可以通过constructor来判断,其属于哪个构造器
//需要追加constructor的作用:可以通过constructor来判断,其属于哪个构造器
console.log(son.constructor === Son);//true
let str = 'abc';
console.log(str.constructor === String);//true
5.3组合继承
- 写一个空的构造函数;
- 把父级原型赋值给这个空的构造函数;
- 再把实例化这个空构造函数并赋值给自己的原型
function Parent(name){
this.eyes = 2;
this.name = name;
this.legs = 2;
}
function Son(name){
Parent.call(this,name);
}
Parent.prototype.job = function(){
console.log("程序员");
}
//写一个空的构造函数;
// 把父级原型赋值给这个空的构造函数;
// 再把实例化这个空构造函数并赋值给自己的原型
let Link = function(){};
Link.prototype = Parent.prototype;
Son.prototype = new Link();
//无论哪种方式实现继承都需要设置会子类的构造函数
Son.prototype.constructor = Son;
let parent = new Parent("张三");
let son = new Son("李四");
Son.prototype.job = function(){
console.log("销售");
}
parent.job();//程序员
son.job();//销售
6.原型链
构造函数有构造函数自身和原型两部分组成。而原型本身也是一对对象,所以构造函数的原型又由其自身和原型组成。一直链条式进行。
查找顺序:先找自身构造函数->再找自身的原型->Object底层的原型->最终找不到返回undefined
原型链是指对象在访问属性或方法时的查找方式。
1.当访问一个对象的属性或方法时,会先在对象自身上查找属性或方法是否存在,如果存在就使用对象自身的属性或方法。如果不存在就去创建对象的构造函数的原型对象中查找 ,依此类推,直到找到为止。如果到顶层对象中还找不到,则返回 undefined。
2.原型链最顶层为 Object 构造函数的 prototype 原型对象(Object的原型prototype不会再有原型,即Object.prototype再也没有原型了),给 Object.prototype 添加属性或方法可以被除 null 和 undefined 之外的所有数据类型对象使用。
如下图:此处:首先在实例化对象parent本身的构造函数Parent上查找name,如果没有找到,会到实例化对象parent的原型上即Parent.prototype上查找,还是没有找到就到底层Object.prototype上查找,最终都没有找到就返回undefined
//原型链
function Parent(){
this.name = "张三";
}
Parent.prototype.name = "李四";
Object.prototype.name = "王五";
let parent = new Parent();
//此处:首先在实例化对象parent本身的构造函数Parent上查找name,如果没有找到,会到实例化对象parent的原型上即Parent.prototype上查找,还是没有找到就到底层Object.prototype上查找,最终都没有找到就返回undefined
console.log(parent.name);
7.拖拽的边界需求 :通过继承来解决
需求:2个div实现拖拽 其中第二个需要设置边界不能拖出可视区域
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
#div1 {
width: 100px;
height: 100px;
background: red;
position: absolute;
top: 20px;
left: 50px;
}
#div2 {
width: 100px;
height: 100px;
background: blue;
position: absolute;
top: 200px;
left: 400px;
}
</style>
</head>
<body>
<div id="div1"></div>
<div id="div2"></div>
<script>
{
//需求:使用继承实现第二个div不超出边界
//构造函数一般只写属性
function Drag(ele){
this.ele = ele;
this.mouseDown();
}
Drag.prototype.mouseDown = function(){
//需要将this传递到事件里:赋值或者箭头函数
this.ele.addEventListener("mousedown",ev=>{
//浏览器兼容
let e = ev || window.event;
//获取初始化鼠标坐标,直接求出初始化时鼠标坐标和left/top的值,以免要传递多个参数
let x = e.clientX - this.ele.offsetLeft;
let y = e.clientY - this.ele.offsetTop;
this.mouseMove(x,y);
this.mouseUp(x,y);
});
}
//由于move方法也需要共用,所以也写在原型上
Drag.prototype.setStyle = function(l,t){
this.ele.style.left = l + 'px';
this.ele.style.top = t + 'px';
}
Drag.prototype.mouseMove = function(x,y){
//如果将onmousemove加在document上并使用监听和匿名函数,则在move方法中的this会指向document,所以需要加在this.ele
//使用监听时不好传递参数
document.onmousemove = ev=>{
let e = ev || window.event;
let left = e.clientX - x;
let top = e.clientY - y;
this.setStyle(left,top);
};
}
Drag.prototype.mouseUp = function(x,y){
document.onmouseup = ev=>{
//清除鼠标移动事件可以直接使this.ele.onmousemove = "";
document.onmousemove = "";
};
}
let div1 = document.querySelector("#div1");
let drag1 = new Drag(div1);
//给第二个div创建子类继承父类
function Div2Drag(ele){
//继承的同时要把所有参数传递过去
Drag.call(this,ele);
}
//声明新的Temp空构造,将父级原型赋值给这个空构造的原型
let Temp = function(){};
Temp.prototype = Drag.prototype;
//将示例化后的Temp赋值给子级的原型
Div2Drag.prototype = new Temp();
//还原子级原有的构造方法
Div2Drag.prototype.constructor = Div2Drag;
//重写Div2Drag的鼠标移动方法
Div2Drag.prototype.mouseMove = function(x,y){
document.onmousemove = ev=>{
let e = ev || window.event;
let left = e.clientX - x;
let top = e.clientY - y;
//限定边界
left = Math.max(0,left);
top = Math.max(0,top);
left = Math.min(document.documentElement.clientWidth - this.ele.offsetWidth,left);
top = Math.min(document.documentElement.clientHeight - this.ele.offsetHeight,top);
this.setStyle(left,top);
};
}
let div2Drag = new Div2Drag(div2);
}
</script>
</body>
</html>
8.ES6中的类
ES6中的方法会自动加到原型中;
父类的静态属性和方法,不能通过实例化后的对象获取;
子类可以继承父类的静态属性和方法,但是子类实例化后的对象不能获取到父类的静态属性和方法;
8.1类的写法
class Person{
height="178cm";
constructor(name,age){
//属性
this.name = name;
this.age = age;
}
//方法
getName(){
console.log("姓名是:"+this.name);
}
}
let student = new Person("张三",20);
student.getName();
8.2静态方法和属性:实例不会继承的属性和方法,但是子类还是会继承父类的静态方法和属性
class Person{
//静态方法
static hobby(){
console.log("喜欢篮球");
}
}
//静态属性
Person.height = "178cm";
//通过类来调用
Person.hobby();
console.log(Person.height);
8.3类的继承:extends
class Dad{
name = "张三";
age = 40;
constructor(height){
this.height = height;
}
hobby(){
console.log("喜欢篮球");
}
}
class Son extends Dad{
constructor(height){
//表示父类的构造函数
super(height);
}
}
let son1 = new Son("178cm");
son1.hobby();
console.log(son1.height);
8.4子类重写父类方法
class Dad{
name = "张三";
age = 40;
constructor(height){
this.height = height;
}
hobby(){
console.log("喜欢篮球");
}
}
class Son extends Dad{
constructor(height){
//表示父类的构造函数
super(height);
}
hobby(){
super.hobby();
console.log("也喜欢足球");
}
}
let son1 = new Son("178cm");
son1.hobby();//喜欢篮球 也喜欢足球
console.log(son1.height);//178cm
8.5使用class继承实现拖拽
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
#div1 {
width: 100px;
height: 100px;
background: red;
position: absolute;
top: 20px;
left: 50px;
}
#div2 {
width: 100px;
height: 100px;
background: blue;
position: absolute;
top: 200px;
left: 400px;
}
</style>
</head>
<body>
<div id="div1"></div>
<div id="div2"></div>
<script>
{
//需求:ES6的继承,class实现继承没有原型的浅拷贝问题
class Drag {
constructor(ele) {
this.ele = ele;
this.mouseDown();
}
mouseDown() {
this.ele.addEventListener("mousedown", ev => {
//浏览器兼容
let e = ev || window.event;
//获取初始化鼠标坐标,直接求出初始化时鼠标坐标和left/top的值,以免要传递多个参数
let x = e.clientX - this.ele.offsetLeft;
let y = e.clientY - this.ele.offsetTop;
this.mouseMove(x, y);
this.mouseUp();
});
}
setStyle(l, t) {
this.ele.style.left = l + 'px';
this.ele.style.top = t + 'px';
}
mouseMove(x, y) {
document.onmousemove = ev => {
let e = ev || window.event;
let left = e.clientX - x;
let top = e.clientY - y;
this.setStyle(left, top);
};
}
mouseUp() {
document.onmouseup = function () {
document.onmousemove = "";
}
}
}
let div1 = document.querySelector("#div1");
let drag1 = new Drag(div1);
//给第二个div创建子类继承父类
class Div2Drag extends Drag {
constructor(ele) {
super(ele);
}
mouseMove = function (x, y) {
document.onmousemove = ev => {
let e = ev || window.event;
let left = e.clientX - x;
let top = e.clientY - y;
//限定边界
left = Math.max(0, left);
top = Math.max(0, top);
left = Math.min(document.documentElement.clientWidth - this.ele.offsetWidth, left);
top = Math.min(document.documentElement.clientHeight - this.ele.offsetHeight, top);
this.setStyle(left, top);
};
}
}
let div2Drag = new Div2Drag(div2);
}
</script>
</body>
</html>
9.包装对象
- 除过null,undefined,基本类型都有自己对应的包装对象:String Number Boolean
- 包装对象把所有的属性和方法给了基本类型,然后包装对象消失
当基本数据类型需要用到包装类型上的方法时,对自动生成包装对象。
原理分析:
//包装对象:String Number Boolean
let str = "a b c";
//基本数据类型调用包装对象的方法时,原理分析
function mySplit(str,method,arg){
let temp = new String(str);
//最终包装完后系统会销毁包装对象
return temp[method](arg);
}
let res = mySplit(str,"split"," ");
console.log(res);//(3) ["a", "b", "c"]
10.常用方法
javascript中判断数据类型的四种方法及typeof、instanceof、constructor、toString的详细讲解参考链接:https://blog.csdn.net/liwenfei123/article/details/77978027
- - hasOwnProperty():看是不是对象自身底下的属性(而不是属于原型的)。如自定义深拷贝
- - contructor查()看对象的构造函数 可以用来做判断是属于哪个构造函数
- - instanceof():对象与构造函数是否在原型链上有关系
- typeof():typeof 是一个操作符,其右侧跟一个一元表达式,并返回这个表达式的数据类型。而对于Date,Array,RegExp等都是返回原型链最顶端的Object。
- - toString()判断类型; 转换字符串 进制转换
10.1hasOwnProperty():看是不是对象自身底下的属性
function deepCopy(obj){
//判断参数是对象还是数组,如果是数组就创建新数组进行拷贝,如果是对象就创建新对象进行拷贝
let newObj = Array.isArray(obj)?[]:{};
//通过for...in循环进行拷贝
for(let key in obj){
//hasOwnProperty():看是不是对象自身底下的属性,而不是原型链
//for...in循环会循环原型上的属性或方法,所以需要使用hasOwnProperty()方法判断,传入的对象自身的属性而不是原型上的属性,再进行深拷贝
if(obj.hasOwnProperty(key)){
//如果得到的属性仍然是一个对象,就继续进行循环
if(typeof obj[key] == "object"){
newObj[key] = deepCopy(obj[key]);
}else{
newObj[key] = obj[key];
}
}
}
return newObj;
}
10.2contructor()查看对象的构造函数 可以用来做判断是属于哪个构造函数:
let str = 'abc';
console.log(str.constructor === String);//true
10.3instanceof():对象与构造函数是否在原型链上有关系。检测的是原型:
//instanceof函数:instanceof 检测的是原型
let arr = [];
let obj = {};
console.log(arr instanceof Array);//true
console.log(obj instanceof Object);//true
//因为instanceof判断的是原型链上的关系,而数组本身也是复杂数据类型-对象。所以不能使用instanceof来判断一个数组是否是Array
console.log(arr instanceof Object);//true
//因为字符串str是基本数据类型,没有原型,而instanceof判断的是原型,所以会返回false
let str = "abc";
let str1 = new String("sdf");
console.log(str instanceof String);//false
console.log(str1 instanceof String);//true
console.log(str);
console.log(str1);//String {"sdf"}
10.4typeof() 返回的是表达式的数据类型:
console.log(typeof arr);//object
console.log(typeof obj);//object
10.5toString()判断类型;
转换字符串 进制转换:用Object.prototype.toString.call(arr)方法判断的类型最为准确。但是必须通过 call 或 apply 来调用,而不能直接调用 toString()
console.log(Object.prototype.toString.call(arr));//[object Array]
console.log(Object.prototype.toString.call(obj));//[object Object]
console.log(Object.prototype.toString.call(str));//[object String]