JavaScript设计模式之面向对象编程

news2024/11/17 17:38:54

为了深入地学习 javascript ,奔着一名标准 Web 开发人员的标准,想要深入了解一下面向对象的编程思想,提高自己模块化开发的能力,编写可维护、高效率、可拓展的代码,最近一直拜读 《JavaScript设计模式》 ,对其重点内容做了归纳与总结,如有总结的不详细或者理解不透彻的,还望批评斧正~

什么是面向对象编程(OOP)?

简单来说,面向对象编程就是将你的需求抽象成一个对象,然后对这个对象进行分析,为其添加对应的特征(属性)与行为(方法),我们将这个对象称之为 。 面向对象一个很重要的特点就是封装,虽然 javascript 这种解释性的弱类型语言没有像一些经典的强类型语言(例如C++,JAVA等)有专门的方式用来实现类的封装,但我们可以利用 javascript 语言灵活的特点,去模拟实现这些功能,接下里我们就一起来看看~

封装

  • 创建一个类

javascript 中要创建一个类是很容易的,比较常见的方式就是首先声明一个函数保存在一个变量中(一般类名首字母大写),然后将这个函数(类)的内部通过对 this 对象添加属性或者方法来实现对类进行属性或方法的添加,例如:

//创建一个类
var Person = function (name, age ) {
	this.name = name;
	this.age = age;
} 

我们也可以在类的原型对象(prototype)上添加属性和方法,有两种方式,一种是一一为原型对象的属性赋值,以一种是将一个对象赋值给类的原型对象:

//为类的原型对象属性赋值
Person.prototype.showInfo = function () {//展示信息console.log('My name is ' + this.name , ', I\'m ' + this.age + ' years old!');
}

//将对象赋值给类的原型对象
Person.prototype = {showInfo : function () {
	//展示信息
	console.log('My name is ' + this.name , ', I\'m ' + this.age + ' years old!');
	}
} 

这样我们就将所需要属性和方法都封装在 Person 类里面了,当我们要用的时候,首先得需要使用 new 关键字来实例化(创建)新的对象,通过 . 操作符就可以使用实例化对象的属性或者方法了~

var person = new Person('Tom',24);
console.log(person.name)// Tom
console.log(person.showInfo())// My name is Tom , I'm 24 years old! 

我们刚说到有两种方式来添加属性和方法,那么这两种方式有啥不同呢?

通过 this 添加的属性和方法是在当前对象添加的,而 javascript 语言的特点是基于原型 prototype 的,是通过 原型prototype 指向其继承的属性和方法的;通过 prototype 继承的方法并不是对象自身的,使用的时候是通过 prototype 一级一级查找的,这样我们通过 this 定义的属性或者方法都是该对象自身拥有的,我们每次通过 new 运算符创建一个新对象时, this 指向的属性和方法也会得到相应的创建,但是通过 prototype 继承的属性和方法是每个对象通过 prototype 访问得到,每次创建新对象时这些属性和方法是不会被再次创建的,如下图所示:

其中 constructor 是一个属性,当创建一个函数或者对象的时候都会给原型对象创建一个 constructor 属性,指向拥有整个原型对象的函数或者对象。

如果我们采用第一种方式给原型对象(prototype)上添加属性和方法,执行下面的语句会得到 true

 console.log(Person.prototype.constructor === Person ) // true 

那么好奇的小伙伴会问,那我采用第二种方式给原型对象(prototype)上添加属性和方法会是什么结果呢?

 console.log(Person.prototype.constructor === Person ) // false 

卧槽,什么鬼,为什么会产生这种结果?

原因在于第二种方式是将一整个对象赋值给了原型对象(prototype),这样会导致原来的原型对象(prototype)上的属性和方法会被全部覆盖掉(pass: 实际开发中两种方式不要混用),那么 constructor 的指向当然也发生了变化,这就导致了原型链的错乱,因此,我们需要手动修正这个问题,在原型对象(prototype)上手动添加上 constructor 属性,重新指向 Person ,保证原型链的正确,即:

 Person.prototype = {
		constructor : Person ,
		showInfo : function () {
			//展示信息
			console.log('My name is ' + this.name , ', I\'m ' + this.age + ' years old!');
		}}console.log(Person.prototype.constructor === Person ) // true 
  • 属性与方法的封装

在大部分面向对象的语言中,经常会对一些类的属性和方法进行隐藏和暴露,所以就会有 私有属性、私有方法、公有属性、公有方法等这些概念~

ES6 之前, javascript 是没有块级作用域的,有函数级作用域,即声明在函数内部的变量和方法在外部是无法访问的,可以通过这个特性模拟创建类的 私有变量私有方法 ,而函数内部通过 this 创建的属性和方法,在类创建对象的时候,每个对象都会创建一份并可以让外界访问,因此我们可以将通过 this 创建的属性和方法看作是 实例属性实例方法,然而通过 this 创建的一些方法们不但可以访问对象公有属性和方法,还能访问到类(创建时)或对象自身的私有属性和私有方法,由于权力这些方法的权力比较大,因此成为 特权方法 ,通过 new 创建的对象无法通过 . 运算符访问类外面添加的属性和和方法,只能通过类本身来访问,因此,类外部定义的属性和方法被称为类的 静态公有属性静态公有方法 , 通过类的原型 prototype 对象添加的属性和方法,其实例对象都是通过 this 访问到的,所以我们将这些属性和方法称为 公有属性公有方法,也叫 原型属性原型方法

 //创建一个类
	var Person = function (name, age ) {	//私有属性	var IDNumber = '01010101010101010101' ;	//私有方法function checkIDNumber () {}//特权方法this.getIDNumber = function () {}//实例属性this.name = name;this.age = age;//实例方法this.getName = function () {}
	}

	//类静态属性Person.isChinese = true;
	//类静态方法Person.staticMethod = function () {console.log('this is a staticMethod')}//公有属性
	Person.prototype.isRich = false;
	//公有方法Person.prototype.showInfo = function () {} 

通过 new 创建的对象只能访问到对应的 实例属性 、实例方法 、原型属性 和 原型方法 ,而无法访问到类的静态属性和私有属性,类的私有属性和私有方法只能通过类自身方法,即:

 var person = new Person('Tom',24);console.log(person.IDNumber) // undefinedconsole.log(person.isRich)// falseconsole.log(person.name) // Tomconsole.log(person.isChinese) // undefinedconsole.log(Person.isChinese) // trueconsole.log(Person.staticMethod()) // this is a staticMethod 
  • 创建对象的安全模式

我们在创建对象的时候,如果我们习惯了 jQuery 的方式,那么我们很可能会在实例化对象的时候忘记用 new 运算符来构造,而写出来下面的代码:

 //创建一个类
	var Person = function (name, age ) {
		this.name = name;
		this.age = age;
	}
	
	var person = Person('Tom',24) 

这时候 person 已经不是我们期望的那样,是 Person 的一个实例了~

 console.log(person)// undifined 

那么我们创建的 nameage 都不翼而飞了,当然不是,他们被挂到了 window 对象上了,

 console.log(window.name)// Tomconsole.log(window.age) // 24 

我们在没有使用 new 操作符来创建对象,当执行 Person 方法的时候,这个函数就在全局作用域中执行了,此时 this 指向的也就是全局变量,也就是 window 对象,所以添加的属性都会被添加到 window 上,而我们的 person 变量在得到 Person 的执行结果时,由于函数中没有 return 语句, 默认返回了 undifined

为了避免这种问题的存在,我们可以采用安全模式解决,稍微修个一下我们的类即可,

 //创建一个类
	var Person = function (name, age) {
		// 判断执行过程中的 this 是否是当前这个对象 (如果为真,则表示是通过 new 创建的)
		if ( this instanceof Person ) {
			this.name = name;
			this.age = age;
		} else {
			// 否则重新创建对象
			return new Person(name, age)
		}
	} 

ok,我们现在测试一下~

 var person = Person('Tom', 24)
	console.log(person) // Person
	console.log(person.name)// Tom
	console.log(person.age) // 24
	console.log(window.name)// undefined
	console.log(window.age) // undefined 

这样就可以避免我们忘记使用 new 构建实例的问题了~

pass:这里我用的 window.name ,这个属性比较特殊,它是 window 自带的,用于设置或返回存放窗口的名称的一个字符串,注意更换~

继承

继承也是面型对象的一大特征,但是 javascript 中没有传统意义上的继承,但是我们依旧可以借助 javascript 的语言特色,模拟实现继承

类式继承

比较常见的一种继承方式,原理就是我们是实例化一个父类,新创建的对象会复制父类构造函数内的属性和方法,并将圆形 __proto__ 指向父类的原型对象,这样就拥有了父类原型对象上的方法和属性,我们在将这个对象赋值给子类的原型,那么子类的原型就可以访问到父类的原型属性和方法,进而实现了继承,其代码如下:

 //声明父类function Super () {this.superValue = 'super';}//为父类添加原型方法Super.prototype.getSuperValue = function () {return this.superValue; }
	//声明子类function Child () {this.childValue = 'child';}
	//继承父类Child.prototype = new Super();//为子类添加原型方法Child.prototype.getChildValue = function () {return this.childValue;} 

我们测试一下~

 var child = new Child();console.log(child.getSuperValue());// superconsole.log(child.getChildValue());// child 

但是这种继承方式会有两个问题,第一由于子类通过其原型 prototype 对其父类实例化,继承父类,只要父类的公有属性中有引用类型,就会在子类中被所有实例共用,如果其中一个子类更改了父类构造函数中的引用类型的属性值,会直接影响到其他子类,例如:

 //声明父类function Super () {	this.superObject = {		a: 1,		b: 2	}}//声明子类function Child () {}//继承父类Child.prototype = new Super();}var child1 = new Child();var child2 = new Child();console.log(child1.superObject);// { a : 1 , b : 2 }child2.superObject.a = 3 ;console.log(child1.superObject);// { a : 3,b : 2 } 

这会对后面的操作造成很大困扰!

第二,由于子类是通过原型 prototype 对父类的实例化实现的,所以在创建父类的时间,无法给父类传递参数,也就无法在实例化父类的时候对父类构造函数内部的属性进行初始化操作。

为了解决这些问题,那么就衍生出其他的继承方式。

构造函数继承 利用 call 这个方法可以改变函数的作用环境,在子类中调用这个方法,将子类中的变量在父类中执行一遍,由于父类中是给 this 绑定的, 因此子类也就继承了父类的实例属性,即:

 //声明父类function Super (value) {	this.value = value;	this.superObject = {		a: 1,		b: 2	}}//为父类添加原型方法Super.prototype.showSuperObject = function () {	console.log(this.superValue);}//声明子类function Child (value) {	// 继承父类Super.call(this,value)}var child1 = new Child('Tom');var child2 = new Child('Jack');child1.superObject.a = 3 ;console.log(child1.superObject);// { a : 3 , b : 2 }console.log(child1.value) // Tomconsole.log(child2.superObject);// { a : 1,b : 2 }console.log(child2.value);// Jack 

Super.call(this,value) 这段代码是构造函数继承的精华,这样就可以避免类式继承的问题了~

但这种继承方式没有涉及到原型 prototype , 所以父类的原型方法不会得到继承,而如果要想被子类继承,就必须要放到构造函数中,这样创建出来的每个实例都会单独拥有一份,不能共用,为了解决这个问题,有了 组合式继承。

组合式继承

我们只要在子类的构造函数作用环境中执行一次父类的构造函数,在将子类的原型 prorotype 对父类进行实例化一次,就可以实现 组合式继承 , 即:

 //声明父类function Super (value) {	this.value = value;	this.superObject = {		a: 1,		b: 2	}}//为父类添加原型方法Super.prototype.showSuperObject = function () {	console.log(this.superObject);}//声明子类function Child (value) {	// 构造函数式继承父类 value 属性Super.call(this,value)}//类式继承Child.prototype = new Super();var child1 = new Child('Tom');var child2 = new Child('Jack');child1.superObject.a = 3 ;console.log(child1.showSuperObject());// { a : 3 , b : 2 }console.log(child1.value) // Tomchild1.superObject.b = 3 ;console.log(child2.showSuperObject());// { a : 1,b : 2 }console.log(child2.value);// Jack 

这样就能融合类式继承和构造函数继承的有点,并且过滤掉其缺点。 看起来是不是已经很完美了,NO , 细心的同学可以发现,我们在使用构造函数继承时执行了一遍父类的构造函数,而在实现子类原型的类式继承时又调用了一父类的构造函数,那么父类的构造函数执行了两遍,这一点是可以继续优化的。

寄生组合式继承

我们上面学习了 组合式继承 ,也看出了这种方式的缺点,所以衍生出了 寄生组合式继承 ,其中 寄生 是寄生式继承 ,而寄生式继承依托于原型式继承,因此学习之前,我们得了解一下 原型式继承寄生式继承

原型式继承跟类式继承类似,当然也存在同样的问题,代码如下:

 //原型式继承function inheritObject (o) {// 声明一个过渡函数对象function F () {}	// 过渡对象的原型继承父对象	F.prototype = o;// 返回过渡对象的一个实例,该实例的原型继承了父对象	return new F();}var Super = {	name : 'Super' ,object : {		a : 1 ,b : 2}}var child1 = inheritObject(Super);var child2 = inheritObject(Super);console.log(child1.object) // { a : 1 , b : 2 }child1.object.a = 3 ;console.log(child2.object) // { a : 3 , b : 2 } 

寄生式继承是对原型继承的第二次封装,并在封装过程中对对象进行了拓展,新对象就有了新增的属性和方法,实现方式如下:

 //原型式继承function inheritObject (o) {// 声明一个过渡函数对象function F () {}	// 过渡对象的原型继承父对象	F.prototype = o;// 返回过渡对象的一个实例,该实例的原型继承了父对象	return new F();}// 寄生式继承// 声明基对象var Super = {	name : 'Super' ,object : {		a : 1 ,b : 2}}function createChild (obj) {	// 通过原型继承创建新对象var o = new inheritObject(obj);// 拓展新对象o.getObject = function () {console.log(this.object)	}	return o;} 

我们将两者的特点结合起来就出现了寄生组合式继承,通过借用构造函数来继承属性,通过原型链的混成形式来继承方法,

 /*** 寄生组合式继承* 传递参数* childClass 子类* superClass 父类* *///原型式继承function inheritObject (o) { // 声明一个过渡函数对象 function F () {} // 过渡对象的原型继承父对象 F.prototype = o; // 返回过渡对象的一个实例,该实例的原型继承了父对象 return new F();}function inheritPrototype (childClass , superClass) {// 复制一份父类的原型保存在变量中var p = inheritObject(superClass.prototype);// 修复子类的 constructorp.constructor = childClass;// 设置子类的原型childClass.prototype = p;} 

我们需要继承父类的原型,不需要在调用父类的构造函数,我们只需要父类原型的一个副本,而这个副本我们是可以通过原型继承拿到,如果直接赋值给子类对象,会导致子类的原型错乱,因为父类的原型对象复制到 P 中的 constructor 指向的不是子类的对象,所以经行了修正,并赋值给子类的原型,这样子类也就继承了父类的原型,但是没有执行父类的构造方法。

ok,测试一下:

 // 定义父类function SuperClass (name) {	this.name = name;	this.object = {		a: 1,		b: 2	}}// 定义父类的原型SuperClass.prototype.showName = function () {console.log(this.name)}// 定义子类function ChildClass (name,age) {// 构造函数式继承SuperClass.call(this,name);// 子类新增属性this.age = age;}// 寄生式继承父类原型inheritPrototype(ChildClass,SuperClass);// 子类新增原型方法ChildClass.prototype.showAge = function () {console.log(this.age)}//var child1 = new ChildClass('Tom',24);var child2 = new ChildClass('Jack',25);console.log(child1.object)// { a : 1 , b : 2 }child1.object.a = 3 ;console.log(child1.object)// { a : 3 , b : 2 }console.log(child2.object)// { a : 1 , b : 2 }console.log(child1.showName())// Tomconsole.log(child2.showAge()) // 25 

现在没问题了哈,之前的问题也都解决了,大功告成~

多继承

JavaC++ 面向对象中会有多继承你的概念,但是 javascript 的继承是依赖原型链实现的,但是原型链只有一条,理论上是不能实现多继承的。但是我们可以利用 javascript 的灵活性,可以通过继承多个对象的属性来实现类似的多继承。

首先,我们来看一个比较经典的继承单对象属性的方法 —— extend

function extend (target,source) {//遍历源对象中的属性for( var property in source ){	//将源对象中的属性复制到目标对象target[property] = source[property]} // 返回目标对象return target;
} 

但是这个方法是一个浅复制过程,也就是说只能复制基本数据类型,对于引用类型的数据达不到预期效果,也会出现数据篡改的情况:

var parent = {
	name: 'super',
	object: {
		a: 1,
		b: 2
	}
}

var child = {
	age: 24
}

extend(child, parent);

console.log(child); //{ age: 24, name: "super", object: { a : 1 , b : 2 } }
child.object.a = 3;
console.log(parent);//{ name: "super", object: { a : 3 , b : 2 } } 

顺着这个思路,要实现多继承,就要将传入的多个对象的属性复制到源对象中,进而实现对多个对象的属性继承,我们可以参考 jQuery 框架中的 extend 方法,对我们上面的函数进行改造~

//判断一个对象是否是纯对象
function isPlainObject(obj) {var proto, Ctor;// (1) null 肯定不是 Plain Object// (2) 使用 Object.property.toString 排除部分宿主对象,比如 window、navigator、globalif (!obj || ({}).toString.call(obj) !== "[object Object]") {return false;}proto = Object.getPrototypeOf(obj);// 只有从用 {} 字面量和 new Object 构造的对象,它的原型链才是 nullif (!proto) {return true;}// (1) 如果 constructor 是对象的一个自有属性,则 Ctor 为 true,函数最后返回 false// (2) Function.prototype.toString 无法自定义,以此来判断是同一个内置函数Ctor = ({}).hasOwnProperty.call(proto, "constructor") && proto.constructor;return typeof Ctor === "function" && Function.prototype.toString.call(Ctor) === Function.prototype.toString.call(Object);
}

function extend() {var name, options, src, copy, clone, copyIsArray;var length = arguments.length;// 默认不进行深拷贝var deep = false;// 从第二个参数起为被继承的对象var i = 1;// 第一个参数不传布尔值的情况下,target 默认是第一个参数var target = arguments[0] || {};// 如果第一个参数是布尔值,第二个参数是 targetif (typeof target == 'boolean') {deep = target;target = arguments[i] || {};i++;}// 如果target不是对象,我们是无法进行复制的,所以设为 {}if (typeof target !== "object" && !( typeof target === 'function')) {target = {};}// 循环遍历要复制的对象们for (; i < length; i++) {// 获取当前对象options = arguments[i];// 要求不能为空 避免 extend(a,,b) 这种情况if (options != null) {for (name in options) {// 目标属性值src = target[name];// 要复制的对象的属性值copy = options[name];// 解决循环引用if (target === copy) {continue;}// 要递归的对象必须是 plainObject 或者数组if (deep && copy && (isPlainObject(copy) ||(copyIsArray = Array.isArray(copy)))) {// 要复制的对象属性值类型需要与目标属性值相同if (copyIsArray) {copyIsArray = false;clone = src && Array.isArray(src) ? src : [];} else {clone = src && isPlainObject(src) ? src : {};}target[name] = extend(deep, clone, copy);} else if (copy !== undefined) {target[name] = copy;}}}}return target;
}; 

该方法默认是浅拷贝,即:

var parent = {name: 'super',object: {a: 1,b: 2}
}
var child = {age: 24
}

extend(child,parent)
console.log(child); // { age: 24, name: "super", object: { a : 1 , b : 2 } }
child.object.a = 3;
console.log(parent) // { name: "super", object: { a : 3 , b : 2 } } 

我们只需要将第一个参数传为 true , 就可以深复制了,即:

 extend(true,child,parent)console.log(child); // { age: 24, name: "super", object: { a : 1 , b : 2 } }child.object.a = 3;console.log(parent) // { name: "super", object: { a : 1 , b : 2 } } 

ok~这些就是 javascript 中面向对象的一些知识,能仔细看到这里的小伙伴,相信你们对 javascript 中面向对象编程有了进一步的认识和了解,也为后面的设计模式的学习奠定了基础,接下来也会继续分享 javascript 中不同的设计模式,欢迎喜欢的小伙伴持续关注~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/149690.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【LeetBook】二叉树

参考资料&#xff1a;LeetBook / 二叉树 前言 差不多的解题思路就是dfs能够解决&#xff0c;其次就是bfs。 主要是递归的解法。 一刷就是了解了 解题的 思路 后序再补一些二叉树的题再刷一刷 目录树的介绍树的遍历前序遍历中序遍历后序遍历层序遍历(广度优先搜索)递归解决问题“…

【FPGA】中值滤波处理BMP图片

文章目录一、中值滤波二、BMP图片格式三、功能实现1.代码设计思路2.shift IP核3.代码实现四、结果测试参考博客一、中值滤波 中值滤波法是一种非线性平滑技术&#xff0c;它将每一像素点的灰度值设置为该点某邻域窗口内的所有像素点灰度值的中值。 中值滤波是基于排序统计理论…

【GO】K8s 管理系统项目[API部分--Ingress]

K8s 管理系统项目[API部分–Ingress] 1. 接口实现 service/dataselector.go import ("sort""strings""time"appsv1 "k8s.io/api/apps/v1"corev1 "k8s.io/api/core/v1"nwv1 "k8s.io/api/networking/v1" )// Ing…

JavaScript client screen offset scroll

文章目录JavaScript client screen offset scrollclientX和clientY、offsetX和offsetY、screenX和screenY、pageX和pageYclientWidth、offsetWidth、scrollWidthwindow.outerWidth、window.innerWidth、document.documentElement.clientWidthJavaScript client screen offset s…

【开发工具】Gradle的安装 与 配置环境变量

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ Gradle安装配置教程一、安装Gradle二、配置环境…

C++: Essential C++ 读书笔记:面向过程编程:调用函数。

1&#xff1a;传值和传址区别 按值传递&#xff1a; 在调用函数中将原函数的值拷贝一份过去给被调用的函数&#xff0c;在被调用函数中对该值的修改&#xff0c;不会影响原函数的值。值传递&#xff0c;变量赋值&#xff0c;修改变量的值------修改的是新地址&#xff08;复制地…

智能家居加速落地,景联文科技提供数据采集标注服务

“以AI驱动智能家居&#xff0c;智能家庭助手和智能家居安防同向发展的智能物联网是目前主流趋势。高质量的标注数据能够高效训练算法&#xff0c;加速应用落地。景联文科技为相关企业提供、智能语音助手、人脸识别、指纹识别门禁系统、非法闯入检测、扫地机器人智能终端控制等…

临床资料研究中的风险因素评估相关指标

前言 写这篇文章是因为最涉及的医学相关的项目比较多&#xff0c;有些常常遇到的概念容易混淆&#xff0c;在这里着重区分一下。&#xff08;感谢广大学霸的分享&#xff09; 1. Ratio 与Rate 的区别 Ratio&#xff1a;表示相对比&#xff0c;简单理解为一个数值相对于另一个…

【1】高危漏洞利用工具 (2023.1.6更新)-- Apt_t00ls

0x01 工具介绍增加CNVD-2023-00895包括&#xff1a;泛微、蓝凌、用友、万户、致远、通达、中间件、安全设备等多个高位漏洞。泛微: e-cology workrelate_uploadOperation.jsp-RCE (默认写入冰蝎4.0.3aes) e-cology page_uploadOperation.jsp-RCE (暂未找到案例 仅供检测poc) e-…

Vivado综合属性之ASYNC_REG

本文验证了综合属性ASYNC_REG对寄存器位置的影响。 ASYNC_REG用于单bit信号采用双&#xff08;或多&#xff09;触发器实现异步跨时钟域的场合&#xff0c;此时所有用于同步的触发器都要标记ASYNC_REG。标记方式为&#xff1a; (* ASYNC_REG "TRUE" *) reg sync_0…

想在2023 年成为前端 Web 开发人员的分步指南

当我开始成为一名前端开发人员时&#xff0c;这是我希望拥有的路线图我想出了这个路线图&#xff0c;它有助于实现成为全能开发人员的目标。让我们开始吧。谁是前端开发人员&#xff1f;好的&#xff0c;现在谁是后端开发人员&#xff1f;那么如何成为一名前端开发人员呢&#…

Java 集合练习题

SourceURL:file:///home/windstorm/Documents/JAVA/JavaCoursePractise/Java 集合练习题.docx 答案&#xff1a; import java.lang.reflect.Array; import java.security.cert.CollectionCertStoreParameters; import java.util.*; public class Main { public static voi…

promise和async用法及区别(详解)

一、promisepromise的概念Promise 是异步编程的一种解决方案&#xff0c;是一个构造函数&#xff0c;自身有all、reject、resolve方法&#xff0c;原型上有then、catch等方法。特点&#xff1a;对象的状态不受外界影响。Promise对象代表一个异步操作&#xff0c;有三种状态&…

【django】模型类中数据的增删改查操作总结

文章目录一、数据库数据操作二、创建对象三、批量创建对象方法一&#xff1a;for循环迭代方法二&#xff1a;bulk_create()四、更新对象save()默认更新所有的字段指定要更新的字段一次性更新多个对象五、查询对象1、管理器2、QuerySet3、检索全部对象a、要注意&#xff1a;4、过…

01 踏上python之旅

Python是一种跨平台的、开源的、免费的、解释型的高级编程语言。它具有丰富和强大的库&#xff0c;能够把用其他语言制作的各种模块很轻松地连接在一起。所以被称为胶水语言。 python的应用领域&#xff1a; Web开发大数据处理人工智能自动化运维开发云计算爬虫游戏开发 解释…

【C语言】详解#define,#ifdef,#ifndef,#elif,#undef,以及相关运算符

1.明示常量 #define 预处理指令结尾不带&#xff1b;&#xff08;分号&#xff09;&#xff0c;在预编译的过程中使用宏的地方会进行展开&#xff0c;是用多少次就展开多少次&#xff0c;但是只替换 不计算&#xff0c;预处理器在发现程序中的宏后&#xff0c;会用宏等价的替换…

生物化学 SY002实验 最常用的酸spiric acid+阿司匹林Aspirin+从柳树皮得到水杨苷

阿司匹林已应用百年&#xff0c;成为医药史上三大经典药物之一。 小知识点&#xff1a;水杨酸的作用和阿司匹林的作用不同之处(抗凝)&#xff0c;使用时的胃损伤等 使用历史 埃及埃伯斯纸莎草纸中关于使用白柳树叶制成的混合物治疗发炎伤口的建议。&#xff08;因为柳叶和树…

软著申请你还不会?我是这样申请的

大家好&#xff0c;我是小悟 兄弟姐妹们&#xff0c;关于软著申请的话题&#xff0c;早前已经写过几篇文章的介绍了&#xff0c;包括软著的申请流程攻略和踩到被打回补正的坑&#xff0c;有兴趣的可以翻翻之前的文章&#xff0c;搜关键字【软著】就有。 私信的小伙伴来自各行…

9.Isaac教程-- Laikago 四足机器人的自主导航

Laikago 四足机器人的自主导航 开发智能机器人系统是一项多学科的工作&#xff0c;集成了动力学、控制、计算机视觉、人工智能等。 很难掌握所有这些领域。 即使你掌握了所有这些&#xff0c;也需要花费大量时间才能正确和稳健。 为了帮助机器人专家加速智能机器人的开发&…

笔试强训(12)

算法题1:计算日期到天数转换: 我们假设用例输入的是2022 1 1&#xff0c;那么我们对应的天数就是1天&#xff0c;我们就不应该在输出1月份的所有月数 public class Main {public static void main(String[] args) {Scanner scannernew Scanner(System.in);int yearscanner.next…