一、构造函数和原型
1.构造函数创建对象
js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>01三种创建对象的方式</title>
</head>
<body>
<script>
//1.利用new Object创建对象
var obj1 = new Object();
//2.利用字面量的方式
var obj2 = {};
//3.利用构造函数
function Star(name,age){
this.name = name;
this.age = age;
this.sing = function(){
console.log(this.name + "在唱歌");
}
}
var ldh = new Star('刘德华',58);
ldh.sing();
</script>
</body>
</html>
2.静态成员和实例成员
2.1实例成员
实例成员就是构造函数内部通过this添加的成员
实例成员是与对象的实例相关联的属性和方法,实例成员只能通过实例化的对象来访问
2.1.1实例属性
实例属性是对象实例特有的数据。例如,在创建一个
Person
类(在 ES6 类语法中)的实例时,每个实例都可以有自己的姓名和年龄属性。在这个例子中,
name
和age
就是Person
类实例的属性。this
关键字用于在构造函数中引用当前实例,通过this.name
和this.age
将传入的参数赋值给实例属性。
js
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
let person1 = new Person("Alice", 30);
let person2 = new Person("Bob", 25);
console.log(person1.name); // "Alice"
console.log(person2.name); // "Bob"
2.1.2实例方法
实例方法是在对象实例上调用的函数。它可以操作实例的属性或者执行与实例相关的任务。
在这个
Circle
类中,getArea
就是一个实例方法,它通过访问实例的radius
属性来计算圆的面积。在实例方法中,
this
指向调用该方法的对象实例。这使得方法能够访问和操作实例的属性。
js
class Circle {
constructor(radius) {
this.radius = radius;
}
getArea() {
return Math.PI * this.radius * this.radius;
}
}
let circle1 = new Circle(5);
console.log(circle1.getArea()); // 约78.54(PI * 5 * 5)
2.2静态成员
静态成员是属于类本身而不是类的实例的属性和方法。它们可以在不创建类的实例的情况下被访问和调用。
2.2.1静态属性
可以通过类名来访问静态属性,而不需要创建类的实例。
静态属性通常用于存储类级别的常量或共享的数据。例如,在一个
DatabaseConnection
类中,可以有一个静态属性来存储数据库的连接字符串,这个连接字符串对于该类的所有实例都是相同的。
js
class MathHelper {
static PI_VALUE = 3.14159;
}
console.log(MathHelper.PI_VALUE); // 3.14159
2.2.2静态方法
静态方法也是属于类本身的方法,通过
static
关键字定义。它不能直接访问实例属性,因为它没有与任何特定的实例相关联。静态方法常用于提供与类相关的工具函数,这些函数不需要访问实例的状态。例如,在一个
DateUtils
类中,可以有静态方法来进行日期格式的转换,而不需要依赖于DateUtils
类的某个具体日期实例。
js
class StringUtil {
static reverseString(str) {
return str.split("").reverse().join("");
}
}
console.log(StringUtil.reverseString("hello")); // "olleh"
3.构造函数存在的问题
在 JavaScript 中,如果使用构造函数创建多个相似的对象,可能会出现重复代码。例如假设有一个构造函数
Person
来创建人物对象当创建多个
Person
对象时,如let person1 = new Person("Alice", 30);
和let person2 = new Person("Bob", 25);
,每个对象的sayHello
方法都是一个独立的函数。这意味着在内存中会有多个相同功能的函数副本,占用了额外的内存空间,导致代码不够高效。
js
function Person(name, age) {
this.name = name;
this.age = age;
this.sayHello = function () {
console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
};
}
3.1构造函数原型prototype
在 JavaScript 中,每个函数都有一个
prototype
属性,它指向一个对象。当通过构造函数创建实例时,实例会通过原型链连接到构造函数的原型对象。在 JavaScript 中,
prototype
是函数对象的一个属性。它是一个对象,用于实现基于原型的继承。当通过构造函数创建对象实例时,实例会自动关联到构造函数的prototype
对象,从而可以访问prototype
对象上的属性和方法。
javascript
function Person(name) {
this.name = name;
}
- 这个
Person
函数有一个prototype
属性,它指向一个对象。可以在这个prototype
对象上添加方法,如:
javascript
Person.prototype.sayHello = function () {
console.log(`Hello, my name is ${this.name}`);
};
- 当创建
Person
的实例时,如let person1 = new Person("Alice");
,person1
可以访问sayHello
方法,就好像这个方法是在person1
对象本身定义的一样。这是因为 JavaScript 在查找对象的属性和方法时,会先在对象本身查找,如果找不到,就会沿着原型链到prototype
对象中查找。
注意:一般情况下,我们的公共属性定义在构造函数里,公共的方法我们就定义到原型对象身上。
js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>04prototype</title>
</head>
<body>
<script>
function Star(name,age){
this.name = name;
this.age = age;
}
//可以将那些不变的方法,直接定义在prototype对象上,
// 这样所有的对象的实例就可以共享这些方法
Star.prototype.sing = function(){
console.log('我会唱歌');
}
var ldh = new Star('刘德华',58);
var zxy = new Star('张学友',56);
console.log(ldh);
console.log(zxy);
ldh.sing(); //ldh实例调用构造函数原型的方法
zxy.sing(); //zxy实例调用构造函数原型的方法
</script>
</body>
</html>
3.2对象原型
在 JavaScript 中,对象原型(
prototype
)是一种机制,用于实现对象之间的继承和属性共享。每个 JavaScript 对象都有一个与之关联的原型对象,当访问一个对象的属性或方法时,如果在该对象本身不存在这个属性或方法,JavaScript 就会在其原型对象中查找。
例如,我们可以创建一个简单的对象,并查看它的原型:
javascript
let obj = {};
console.log(obj.__proto__);
// 输出Object.prototype,它是obj的原型
对象都会有一个属性
__proto__
指向构造函数prototype原型对象,之所以我们对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有__proto__
原型的存在。
__proto__
对象原型和prototype原型对象是等价的。
__proto__
对象原型的意义就在于为对象的查找机制提供了一个方向,或者时提供了一条路线。但是它时一个非标准属性,因此实际的开发中,不可以使用这个属性,它只是内部指向原型对象prototype
js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>05对象原型__proto__</title>
</head>
<body>
<script>
function Star(name,age){
this.name = name;
this.age = age;
}
//往原型对象中添加方法
Star.prototype.sing = function(){
console.log('我会唱歌');
}
var ldh = new Star('刘德华',58);
var zxy = new Star('张学友',56);
ldh.sing();
console.log(ldh); //对象身上系统会自动添加一个__proto__指向我们构造函数的原型对象prototype
console.log(ldh.__proto__ === Star.prototype); //true
//方法的查找规则:首先看ldh对象身上是否有sing方法,如果有就执行这个对象上的方法
//如果没有,因为__proto__的存在,就回去构造函数原型对象prototype身上去找这个方法
</script>
</body>
</html>
4.构造函数
对象原型:
__proto__
和构造函数中的原型对象(prototype
)里面都有一个属性constructor属性,constructor我们称为构造函数,因为它指回构造函数本身constructor主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
一般情况下,对象的方法都在构造函数的原型对象中设置,如果有多个对象的方法,我们可以给原型对象采取对象形式赋值,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的对象constructor就不再指向当前构造函数了。此时,我们可以在修改后的原型对象中添加一个constructor指向原来的构造函数
js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>06.constructor</title>
</head>
<body>
<script>
function Star(name,age){
this.name = name;
this.age = age;
}
//很多的情况下,我们需要手动的利用constructor这个属性指回原来的构造函数
// Star.prototype.sing = function(){
// console.log('我会唱歌');
// }
// Star.prototype.movie = function(){
// console.log('我会演电影');
// }
//对象形式赋值:这样就会覆盖构造函数原型对象原来的内容.这样修改后的对象constructor就不再指向当前构造函数了
Star.prototype = {
constructor:Star, //我们可以在修改后的原型对象中添加一个constructor指向原来的构造函数
sing:function(){
console.log('我会唱歌');
},
movie:function(){
console.log('我会演电影');
}
}
var ldh = new Star('刘德华',58);
var zxy = new Star('张学友',56);
console.log(Star.prototype);
console.log(ldh.__proto__);
console.log(ldh.__proto__ === Star.prototype);
console.log(Star.prototype.constructor);
console.log(ldh.__proto__.constructor);
</script>
</body>
</html>
5.原型链
- 原型链是 JavaScript 对象之间的一种关联方式。每个对象都有一个内部的
[[Prototype]]
属性(在大多数浏览器中可以通过__proto__
访问,但这不是标准属性),这个属性指向它的原型对象。 - 以
person1
为例,person1.__proto__
(非标准但方便理解)指向Person.prototype
。如果Person.prototype
本身也有一个__proto__
属性,它会指向Object.prototype
,因为Person.prototype
是一个对象。而Object.prototype.__proto__
为null
,这就形成了一个原型链。 - 当访问
person1.toString()
时,首先会在person1
对象本身查找toString
方法,如果没有找到,就会沿着原型链查找,最终会在Object.prototype
中找到通用的toString
方法。
js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>07原型链</title>
</head>
<body>
<script>
function Star(name,age){
this.name = name;
this.age = age;
}
Star.prototype.sing = function(){
console.log('我会唱歌');
}
var ldh = new Star('刘德华',58);
//1. 只要时对象,它有一个__proto__(对象原型),作用指向原型对象(prototype)
console.log(Star.prototype === ldh.__proto__);
console.log(Star.prototype.__proto__ === Object.prototype);
//2. 我们Star原型对象里面的__proto__原型主席昂的时Object.prototype
console.log(Object.prototype.__proto__);
//3. 我们的Object.prototype原型对象里面的__proto__原型指向的是null
</script>
</body>
</html>
6.原型链和成员的查找机制
任何对象都有原型对象,也就是
prototype
属性,任何原型对象也是一个对象。该对象就有__proto__
属性,这样一层一层往上找就形成一条链,我们就称之为原型链
- 当我们访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有这个属性
- 如果就直接调用。如果自身没有,就查找它的原型(也就是
__proto__
指向的prototype
原型对象) - 如果还没有查找到原型对象的原型(Object的原型对象)
- 依此类推一直找到Object为止(null)
__proto__
对象原型的意义:在于为对象成员查找机制提供一个方向,或者是一条线路
js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function Star(name,age){
this.name = name;
this.age = age;
}
Star.prototype.sex = '女'; //在原型对象中添加了一个sex属性,并赋值为女
var ldh = new Star('刘德华',58);
ldh.sex = '男';
console.log(ldh.sex);
console.log(Object.prototype);
console.log(ldh.toString()); //是Object.prototype中的
</script>
</body>
</html>
7.原型对象中this指向
构造函数中的this和原型对象的this都指向我们new出来的实例对象
js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>原型对象中this指向</title>
</head>
<body>
<script>
function Star(name,age){
this.name = name;
this.age = age;
}
//构造函数中的this和原型对象的this都指向我们new出来的实例对象
var that;
Star.prototype.sing = function(){
console.log('我会唱歌');
that = this;
}
var ldh = new Star('刘德华',58);
ldh.sing();
console.log(that === ldh); //原型对象函数里面的this,指向的是实例对象ldh
</script>
</body>
</html>
二、继承
1.call()
- call() 可以调用函数
- call() 可以修改this的指向,使用call() 的时候,参数一 是修改后的this指向,参数2,参数3...使用逗号隔开连接
javascript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>11.继承中的call()</title>
</head>
<body>
<script>
function fn(x,y){
console.log('我想喝可乐');
console.log(this);
console.log(x + y);
}
var obj = {
name : '坤坤'
}
//call() 可以调用函数,写法 函数名.call();
//fn.call();
//call() 可以改变这个函数的this指向,第一个参数就是修改后的this的指向的值
fn.call(obj,1,2);
</script>
</body>
</html>
2.子构造函数继承父构造函数中的属性
javascript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>12.借用原型对象继承方法</title>
</head>
<body>
<script>
function Father(name,age){
//this指向父构造函数的对象实例
this.name = name;
this.age = age;
console.log('Father构造函数被调用了');
}
Father.prototype.money = function(){
console.log(1000000);
}
function Son(name,age,score){
//改变this的指向
Father.call(this,name,age);
this.score = score;
}
//这样直接赋值有问题,如果修改了子原型对象,父原型对象也会一起变化
//Son.prototype = Father.prototype;
Son.prototype = new Father();
//如果利用对象的形式修改了原型对象,别忘了constructor指回原来的构造函数
//Son.prototype.constructor = Son;
Son.prototype.exam = function(){
console.log('孩子要考试');
}
var son = new Son('刘德华',56,100);
console.log(son);
console.log(Father.prototype);
console.log(Son.prototype);
console.log(Son.prototype.constructor);
</script>
</body>
</html>
三、ES5新增方法
1.数组方法forEach遍历数组
语法:
数组名.forEach(function(value,index,array){
});
- value: 遍历出来的数组中的每一个元素
- index:数组中每个元素的索引
- array:数组本身
javascript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>13.forEach</title>
</head>
<body>
<script>
var arr = [1,2,3,4,5,6];
var sum = 0;
// for(var i = 0; i < arr.length; i++){
// sum += arr[i];
// }
arr.forEach(function(value,index,array){
sum += value;
console.log(index); //数组中每个元素的索引
//console.log('数组本身-->' + array[index]);
});
console.log(sum);
</script>
</body>
</html>
需求:数组 var arr = [1,2,3,4,5,6,7,9,10] 数组中所有的元素的和、偶数和、奇数和、和平均值
javascript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>14.forEach案例</title>
</head>
<body>
<script>
var arr = [1,2,3,4,5,6,7,8,9,10];
//元素的和、偶数和、奇数和、和平均值
var sum = 0;even = 0,odd = 0,avg = 0;
arr.forEach(function(val){
sum += val; //总和
//偶数
if(val % 2 == 0){
even += val;
}
//奇数
if(val % 2 != 0){
odd += val;
}
});
avg = sum / arr.length ;
console.log('总和为--->' + sum);
console.log('偶数和为--->' + even);
console.log('奇数和为--->' + odd);
console.log('平均值为--->' + avg);
</script>
</body>
</html>
2.filter
语法:
javascript
var 新数组 = 数组名.filter(function(value,index){
return xxx;
})
- value:表示遍历数组中的每一个元素
- index:表示数组中元素的索引
- return:表示返回结果到新的数组
javascript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>15filter</title>
</head>
<body>
<script>
//需求:找出给定数组中所有的偶数(将一个数组中的偶数全部取出称为一个新数组)
var arr = [12,66,4,48,88,3,7]
var newArr = arr.filter(function(value,index){
//将能被2整除的数放入到新的数组中
return value % 2 === 0;
});
console.log(newArr);
</script>
</body>
</html>
3.some
javascript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>16some</title>
</head>
<body>
<script>
//需求:查找数组中小于3的元素
var arr = [10,30,2,4];
var flag = arr.some(function(value){
return value < 3; //如果有小于3的就返回true,否则返回false
});
console.log(flag);
var arr1 = ['red','pink','blue'];
var flag2 = arr1.some(function(value){
return value === 'pink';
});
console.log(flag2);
</script>
</body>
</html>
3.som和filter的区别
- filter 是查找满足条件的元素,返回的是一个数组,而且把所有满足条件的元素都返回回来
- some也是查找0满足条件的元素是否存在,返回的是一个布尔值,如果查找到了一个满足条件的元素就会终止循环
4.案例
js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
table{
width: 400px;
border: 1px solid #000;
border-collapse: collapse;
margin: 0 auto;
}
td,th{
border: 1px solid #000;
text-align: center;
}
input{
width: 50px;
}
.search{
width: 600px;
margin: 20px auto;
}
</style>
</head>
<body>
<div class="search">
按照价格搜索: <input type="text" class="start"> - <input type="text" class="end">
<button class="search-price">搜索</button>
<br/>
按照商品名称搜索: <input type="text" class="product">
<button class="search-pro">查询</button>
</div>
<table>
<thead>
<th>id</th>
<th>产品名称</th>
<th>价格</th>
</thead>
<tbody>
</tbody>
</table>
<script>
var data = [
{
id:1,
name:'小米',
price:3999
},
{
id:2,
name:'华为',
price:6999
},
{
id:3,
name:'苹果',
price:5999
},
{
id:4,
name:'荣耀',
price:7999
},
{
id:5,
name:'大米',
price:2999
}
];
//1.获取到相应的元素
var tbody = document.querySelector('tbody');
var search_price = document.querySelector('.search-price');
var search_pro = document.querySelector('.search-pro');
var start = document.querySelector('.start');
var end = document.querySelector('.end');
var product = document.querySelector('.product');
//2.将数据渲染到table中
setData(data);
//将数组渲染到table中
function setData(mydata){
//每次渲染之前就清空table
tbody.innerHTML = '';
//遍历数组
mydata.forEach(function(value){ //value是一个一个的对象
//console.log(value);
//创建tr
var tr = document.createElement('tr');
tr.innerHTML = '<td>'+value.id+'</td><td>'+value.name+'</td><td>'+value.price+'</td>';
tbody.appendChild(tr);
});
}
//3.根据商品名称查询商品
search_pro.addEventListener('click',function(){
var newArr =[]; //查询出来的结果
if(product.value == ''){ //如果用户不输入搜索条件,直接查询全部后返回
setData(data);
return;
}
newArr = data.filter(function(value){
//精确查询
if(value.name === product.value){
return value;
}
});
setData(newArr); //把查询的结果渲染到table中
});
//4.按照价格搜索商品
search_price.addEventListener('click',function(){
//将符合条件的商品返回
var newArr = data.filter(function(value){
return value.price >= start.value && value.price <= end.value;
})
setData(newArr); //把查询的结果渲染到table中
});
</script>
</body>
</html>
5.trim
该方法可以去除字符串两端的空格
javascript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>18trim</title>
</head>
<body>
<script>
//1.该方法可以去除字符串两端的空格
var str = ' hel lo '; //字符串中间的空格不能去除
console.log(str.trim().length);
</script>
</body>
</html>
6.获取对象的属性名
语法:Object.keys(对象); 获取到当前对象中的属性名,返回的是一个数组
javascript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var obj = {
id:1,
name:'小米',
price:3999
}
var result = Object.keys(obj);
console.log(result); //[id,name,price]
//console.log(result[0]);
</script>
</body>
</html>
7.Object.defineProperty
Object.defineProperty 设置或修改对象中的属性
javascript
//语法:
Object.defineProperty(对象,修改或新增的属性名,{
value:修改或新增的属性的值,
writable: true /false, //如果为false不允许修改这个属性的值
enumerable: false, //如果值为false,则不允许遍历
configurable:false //configurable如果它的值为false,则不允许删除这个属性,属性是否可以被删除或再次修改
})
javascript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Object.defineProperty</title>
</head>
<body>
<script>
var obj = {
id:1,
name:'小米',
price:3999
}
//1. 以前的对象添加或修改属性的方式
obj.name = '大麦';
obj.price = 10;
console.log(obj);
//2.Object.defineProperty:设置或修改对象中的属性
Object.defineProperty(obj,'name',{
value:'荞麦',
enumerable: true
});
console.log(obj);
Object.defineProperty(obj,'id',{
writable: false //如果为false表示不允许修改这个数值的值
});
obj.id = 2; //因为不允许修改,所以修改失败
console.log(obj);
Object.defineProperty(obj,'name',{
value:'大麦',
writable:false, //表示不允许被修改
enumerable: false, // 为false表示不允许遍历
configurable:false //如果为false则不允许删除这个属性,不允许在修改第三个参数里面的特性
});
obj.name = 'aaaa'
console.log(obj.name);
console.log(Object.keys(obj)); // ['id', 'price']
</script>
</body>
</html>