1. 构造函数和原型
1.1 对象的三种创建方式--复习
- 对象字面量的方式 :
let obj = {
name: '张三',
age: 23,
sayHi: function () {
console.log('hi!');
}
}
console.log(obj.name);
obj.sayHi();
- 使用 new 关键字的方式创建对象 :
let object = new Object();
object.name = '李四';
object.age = 24;
object.sayHello = function () {
console.log('say hello');
}
object.sayHello();
- 使用构造函数创建对象:
function Star(name, age) {
this.name = name;
this.age = age;
this.print = function () {
console.log(name + age);
}
}
let star = new Star('王五', 35);
star.print();
1.2 静态成员和实例成员
实例成员
- 实例成员就是构造函数内部通过
this
添加的成员 如下列代码中uname age sing
就是实例成员,实例成员只能通过实例化的对象来访问。
// 实例成员就是构造函数内部通过 this 添加的成员
function Star(name, age) {
this.name = name;
this.age = age;
this.sayHello = function () {
console.log('say hello');
};
}
// 实例成员只能通过实例化的对象来访问
let star = new Star('张三', 23);
console.log(star.name);
console.log(star.age);
// 不可以通过构造函数来访问实例成员
console.log(Star.age); // undefined
// 静态成员是在构造函数本身上添加的成员,静态成员只能通过构造函数访问不能通过实例对象访问
Star.sex = '男';
console.log(star.sex); // undefined 实例对象调用静态成员
console.log(Star.sex); // 男 构造函数调用静态成员
静态成员
- 静态成员 在构造函数本身上添加的成员 如下列代码中 sex 就是静态成员,静态成员只能通过构造函数来访问。
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function() {
console.log('我会唱歌');
}
}
Star.sex = '男';
var ldh = new Star('刘德华', 18);
console.log(Star.sex);//静态成员只能通过构造函数来访问
1.3 构造函数的问题
- 构造函数方法很好用,但是存在浪费内存的问题。
1.4 构造函数原型 prototype
构造函数通过原型分配的函数是所有对象所共享的。
JavaScript
规定,每一个构造函数都有一个prototype
属性,指向另一个对象。注意这个prototype
就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。我们可以把那些不变的方法,直接定义在
prototype
对象上,这样所有对象的实例就可以共享这些方法。
/**
* 构造函数
* */
function Star(name, age) {
this.name = name;
this.age = age;
/**
* 还是会优先调用它
*
*/
/*this.sing = function () {
console.log('我会唱歌吗?');
};*/
}
console.dir(Star);
// 在构造函数的原型对象里面 添加共享的方法
// 原型是一个对象是每个构造函数中都会存在的一个对象
// 原型的作用是: 共享方法
// 一般情况下,我们的公共属性定义在构造函数中,但是公共的方法我们需要放到原型对象身上
Star.prototype.sing = function () {
console.log('我会唱歌');
};
let star = new Star('黎明', 23);
let s = new Star('张华自', 23);
console.log(star.sing() === s.sing()); // true
1.5 对象原型
对象实例都会有一个属性
__proto__
(对象原型)指向构造函数的prototype
原型对象,之所以我们对象可以使用构造函数prototype
原型对象的属性和方法,就是因为对象有__proto__
原型的存在。
__proto__
对象原型和原型对象prototype
是等价的__proto__
对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象prototype
。
function Star(name, age) {
this.name = name;
this.age = age;
}
/**
* 在原型对象上添加一个共享方法
*/
Star.prototype.sing = function () {
console.log('我会唱歌');
}
let ldh = new Star('刘德华', 66);
// 对比原型对象和对象原型
console.log(ldh.__proto__ === Star.prototype); // true
1.6 constructor构造函数
对象原型(
__proto__
)和构造函数(prototype
)原型对象里面都有一个属性constructor
属性 ,constructor
我们称为构造函数,因为它指回构造函数本身。
constructor
主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
一般情况下,对象的方法都在构造函数的原型对象中设置。如果有多个对象的方法,我们可以给原型对象采取对象形式赋值,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了。此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。
如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数如:
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
// 很多情况下,我们需要手动的利用constructor 这个属性指回 原来的构造函数
Star.prototype = {
// 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
constructor: Star, // 手动设置指回原来的构造函数
sing: function() {
console.log('我会唱歌');
},
movie: function() {
console.log('我会演电影');
}
}
var zxy = new Star('张学友', 19);
console.log(zxy)
以上代码运行结果,设置constructor属性如图:
如果未设置constructor属性,如图:
1.7 原型链
- 每一个实例对象又有一个
proto
属性,指向的构造函数的原型对象,构造函数的原型对象也是一个对象,也有proto
属性,这样一层一层往上找就形成了原型链。
function Star(name, age) {
this.name = name;
this.age = age;
}
console.log(Star.prototype); // 构造函数中的原型对象 原型对象中有对象原型 __proto__ 指向 Object
/**
* Star.prototype.__proto__ 构造函数中原型对象 的对象原型 指向 Object 的原型对象
* 我们Star原型对象里面
*/
console.log(Star.prototype.__proto__ === Object.prototype);
console.log(Object.prototype.__proto__); // null Object的 原型对象的对象原型指向的是 null
console.dir(Object);
1.8 构造函数实例和原型对象三角关系
1.构造函数的prototype
属性指向了构造函数原型对象;
2.实例对象是由构造函数创建的,实例对象的__proto__
属性指向了构造函数的原型对象;
3.构造函数的原型对象的constructor
属性指向了构造函数,实例对象的原型的constructor
属性也指向了构造函数。
function Star(name, age) {
this.name = name;
this.age = age;
}
let star = new Star('张三', 23);
// 1. 构造函数中的 prototype 指向了原型对象
console.log(Star.prototype);
// 2. 原型对象中constructor指向了构造函数
console.log(Star.prototype.constructor);
// 3. 构造函数指向对象实例 ,对象实例中的 __proto__(对象原型) 指向了原型对象
console.log(star.__proto__);
console.log(star.__proto__.constructor);
1.9 原型链和成员的查找机制
- 任何对象都有原型对象,也就是
prototype
属性,任何原型对象也是一个对象,该对象就有proto
属性,这样一层一层往上找,就形成了一条链,我们称此为原型链;
- 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
- 如果没有就查找它的原型(也就是
__proto__
指向的prototype
原型对象)。- 如果还没有就查找原型对象的原型(Object的原型对象)。
- 依此类推一直找到
Object
为止(null
)。__proto__
对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
/**
* @param name
* @param age
* @constructor
*/
function Star(name, age) {
this.name = name;
this.age = age;
}
Star.prototype.sing = function () {
console.log('我会唱噶');
}
Star.height = 178;
console.log(Star.height);
let star = new Star('张三', 23);
// star.sex = '男'; // 1.先在实例对象上查找
// Star.prototype.sex = '男'; // 2. 再在原型对象上找
Object.prototype.sex = '女'; // 3. 最后在Object的原型对象上找
console.log(star.sex);
star.sing();
star.sex = '男';
console.log(star.sex); // 男
// 对象成员查找是根据原型链的机制进行查找
console.log(Object.prototype); // 这里面有个toString方法
console.log(Star.prototype);
console.log(star.toString());
1.10 原型对象中this指向
- 构造函数中的
this
和原型对象的this
,都指向我们new
出来的实例对象:
let that;
/**
* 原型对象的 this 指向问题
* @param name
* @param age
* @constructor
*/
function Star(name, age) {
this.name = name;
this.age = age;
console.log("创建实例对象" + that === this);
}
Star.prototype.sing = function () {
// 这里的this指向的是谁? 指向的是函数的调用者 当前的实例对象
console.log('我会唱歌');
console.log(this);
console.log("创建实例对象" + that === this);
that = this;
}
let star = new Star('张三', 23);
console.log(star);
star.sing();
// 1. 在构造函数中,里面this指向的是对象实例
// 2. 原型对象函数里面的this指向的是函数的调用者,但
// 是不管是构造函数中的this还是原型对象中的this都是指向的实例对象
1.11 通过原型为数组扩展内置方法
console.log(Array.prototype); // 打印Array构造函数中的原型对象
/**
* 扩展求和函数
*/
Array.prototype.sum = function () {
let sum = 0;
for (let i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
};
let arr = [100, 200, 300, 400, 500];
console.log(arr.sum());
/**
* 错误的追加方式
*/
Array.prototype = function () {
let sum = 0;
for (let i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
};
// 注意:在数组和字符串内置对象中不能给原型对象覆盖操作 Array.prototype = {} 只能是采取 Array.prototype.函数或者属性名 = function(){} 的方式
2. 继承
2.1 call()
call()
可以调用函数;call()
可以修改this
的指向,使用call()
的时候 参数一是修改后的this
指向,参数2,参数3..使用逗号隔开连接。
// 1.call() 可以调用函数
function fn(x, y) {
console.log('call()可以调用函数和改变函数的this指向');
console.log(this);
console.log(x + y);
}
// fn.call();
let o = {
name: 'andy'
}
// 2.call() 可以改变函数的this的指向
fn.call(o, 1, 2);
2.2 子构造函数继承父构造函数中的属性
先定义一个父构造函数;
再定义一个子构造函数;
子构造函数继承父构造函数的属性(使用call方法)。
/**
* 1. 父构造函数
* @param name
* @param age
* @constructor
*/
function Father(name, age) {
this.name = name;
this.age = age;
}
/**
* 2. 子构造函数
* @param name
* @param age
* @constructor
*/
function Son(name, age) {
/**
* 这里的意思是将子构造函数的this、指向了父构造函数的this
*/
Father.call(this, name, age);
}
let son = new Son('李四', 24);
console.log(son);
2.3 借用原型对象继承方法
先定义一个父构造函数;
再定义一个子构造函数;
子构造函数继承父构造函数的方法(使用
call
方法)。
/**
* 1. 父构造函数
* @param name
* @param age
* @constructor
*/
function Father(name, age) {
this.name = name;
this.age = age;
}
/**
*
* 在父类的原型对象中添加一个共享方法
* */
Father.prototype.money = function () {
console.log('100000');
}
/**
* 2. 子构造函数
* @param name
* @param age
* @constructor
*/
function Son(name, age) {
/**
* 这里的意思是将子构造函数的this、指向了父构造函数的this
*/
Father.call(this, name, age);
}
// 如果使用Son的原型对象 直接指向 Father的原型对象将会出现问题
// Son.prototype = Father.prototype;
// 让Son的原型对象指向 Father 的实例对象
Son.prototype = new Father();
/**
* 此时再Son构造函数中添加一个方法
* @type {Son}
*/
Son.prototype.exam = function () {
console.log('我要考试');
}
let son = new Son('李四', 24);
console.log(son);
let father = new Father();
console.log(father);
如上代码结果如图:
3. ES5新增方法
3.1 数组方法forEach遍历数组
arr.forEach(function(value, index, array) {
//参数一是:数组元素
//参数二是:数组元素的索引
//参数三是:当前的数组
})
//相当于数组遍历的 for循环 没有返回值
演示案例 :
/**
* 数组方法 foreach,遍历数组的值,并求所有值的和
*/
let arr = [1, 2, 3, 4, 5, 6, 6, 7, 8, 90];
let sum = 0;
arr.forEach((value, index, array) => {
console.log('每一個数组元素' + value);
console.log('每一个数组元素的索引' + index);
console.log('数组本身' + array);
sum += value;
});
console.log(sum);
3.2 数组方法filter过滤数组
var arr = [12, 66, 4, 88, 3, 7];
var newArr = arr.filter(function(value, index,array) {
//参数一是:数组元素
//参数二是:数组元素的索引
//参数三是:当前的数组
return value >= 20;
});
console.log(newArr);//[66,88] //返回值是一个新数组
3.3 数组方法some
some 查找数组中是否有满足条件的元素
var arr = [10, 30, 4];
var flag = arr.some(function(value,index,array) {
//参数一是:数组元素
//参数二是:数组元素的索引
//参数三是:当前的数组
return value < 3;
});
console.log(flag);//false返回值是布尔值,只要查找到满足条件的一个元素就立马终止循环
3.4 案例 : 筛选商品案例
页面结构
<div class="search">
按照价格查询: <input type="text" class="start"> - <input type="text" class="end">
<button class="search-price">搜索</button>
按照商品名称查询: <input type="text" class="product">
<button class="search-pro">查询</button>
</div>
<table>
<thead>
<tr>
<th>id</th>
<th>产品名称</th>
<th>价格</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
页面样式
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;
}
自定义数据
// 利用新增数组方法操作数据
let data = [{
id: 1,
pname: '小米',
price: 3999
}, {
id: 2,
pname: 'oppo',
price: 999
}, {
id: 3,
pname: '荣耀',
price: 1299
}, {
id: 4,
pname: '华为',
price: 1999
}, {
id: 5,
pname: '华为p40',
price: 6999
}, {
id: 6,
pname: '华为',
price: 1999
}];
操作逻辑
// 1. 获取相应的元素
let tbody = document.querySelector('tbody');
setData(data);
function setData(myData) {
// 先清空原来的数据
tbody.innerHTML = '';
// 2. 将数据渲染到页面中
myData.forEach((value) => {
// 创建行元素
let tr = document.createElement('tr');
tr.innerHTML = '<td>' + value.id + '</td><td>' + value.pname + '</td><td>' + value.price + '</td>';
tbody.appendChild(tr);
});
}
// 3. 根据价格查询商品
// 搜索按钮
let search_price = document.querySelector('.search-price');
// 开始输入框
let start = document.querySelector('.start');
// 结束输入框
let end = document.querySelector('.end');
search_price.addEventListener('click', () => {
let newData = data.filter((value) => {
return value.price >= start.value && value.price <= end.value;
});
setData(newData);
});
// 4. 根据商品名称查找商品
// 如果查询的是数组中唯一的元素,用some方法更加合适,因为它找到这个元素就不再进行循环效率更高
let product = document.querySelector('.product');
let search_pro = document.querySelector('.search-pro');
search_pro.addEventListener('click', () => {
let arr = [];
data.some((value) => {
/**
* 找到之后就会终止循环
*/
if (value.pname === product.value) {
console.log(value);// 可以实现打印
arr.push(value);
return true; //
}
})
// 将拿到的数据渲染到页面中
setData(arr);
});
3.5 some和forEach区别
如果查询数组中唯一的元素, 用
some
方法更合适,在some
里面 遇到return true
就是终止遍历 迭代效率更高。在
forEach
里面return
不会终止迭代。
let arr = ['red', 'green', 'blue', 'pink'];
// 在forEach中return不会终止循环
/**
* value 满足条件的值
*
* index 满足条件的索引
*
* array 满足条件的新数组
*/
arr.forEach((value, index, array) => {
if (value === 'green') {
console.log(value);
console.log('元素已经找到');
return true;
}
console.log(666);
});
// some的效率会更高因为在第一次找到元素之后就会终止循环
arr.some((value, index, array) => {
if (value === 'green') {
console.log(value);
console.log('元素已经找到');
return true;
}
console.log(666);
});
3.6 trim方法去除字符串两端的空格
-
trim()
方法清除的是字符串两端的空格,不清楚中间的空格。
<input type="text">
<button>提交</button>
<div></div>
/**
* trim 去除字符串两端的空白字符
*/
let input = document.querySelector('input');
let btn = document.querySelector('button');
let div = document.querySelector('div');
btn.addEventListener('click', () => {
let strInput = input.value.trim();
// 首先判断一下文本框的内容是否为空
if (strInput === '') {
alert('输入框不能为空!');
} else {
// 将文本框的内容设置给 div
div.innerHTML = strInput;
console.log(strInput);
}
});
3.7 Object.keys() 获取对象的属性名
-
Object.keys(对象)
获取到当前对象中的属性名 ,返回值是一个数组:
var obj = {
id: 1,
pname: '小米',
price: 1999,
num: 2000
};
var result = Object.keys(obj)
console.log(result)//[id,pname,price,num]
3.8 Object.defineProperty
/**
*
* @type {{id: number, pname: string, price: number}}
*
* Object.defineProperty(obj , prop,descriptor)
*
* 1. obj 对象
*
* 2. prop 需要设置的属性
*
* 3. 一个属性配置对象
*
* 3.1 value: 设置属性的值,默认值是undefined
*
* 3.2 writable: 是否可以被重写
*
* 3.3 enumerable:目标属性是否可以被枚举。 true | false 默认false
*
* 3.4 configurable:目标属性是否可以被删除或是否可以在此修改 true | false 默认是false
*/
let obj = {
id: 1,
pname: '小米',
price: 36669
};
Object.defineProperty(obj, 'num', {
value: 30000,
writable: false, // 是否可以被重写
enumerable: false // 默认值是false
});
Object.defineProperty(obj, 'pname', {
writable: false,// 是否可以被重写
configurable: false // 是否可以被删除 设置为false时则不再允许删除这个属性,不允许修改第三个参数中的特性
});
obj.pname = '华为';
console.log(obj);
console.log(Object.keys(obj));
delete obj.pname // 删除 pname 属性
console.log(obj);