JavaScript进阶-2.构造函数和原型

一、构造函数和原型

1. 构造函数

创建对象可以通过以下三种方式:

1.对象字面量
2.new Object()
3.自定义构造函数
	// 1. 利用 new Object() 创建对象
	var obj1 = new Object();
	// 2. 利用对象字面量创建对象
	var obj2 = {};
	// 3. 利用构造函数创建对象
	function Obj(name,age) {
		this.name = name;
		this.age = age;
		this.method = function() {
			console.log('创建的方法');
		}
	}
	var a = new Obj('Jack',19);
	var b = new Obj('andy',18);
	cosnole.log(a);	// Obj3 {name: "jack", age: 19, method: ƒ}
	a.method();	// 创建的方法
	b.method(); // 创建的方法
1.1 构造函数

构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与 new 一起使用。我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面。

在 JS 中,使用构造函数时要注意以下两点:

  1. 构造函数用于创建某一类对象,其首字母要大写
  2. 构造函数要和 new 一起使用才有意义

new 在执行时会做四件事情:

1. 在内存中创建一个新的空对象。
2. 让 this 指向这个新的对象。
3. 执行构造函数里面的代码,给这个新对象添加属性和方法。
4. 返回这个新对象(所以构造函数里面不需要 return )。

JavaScript 的构造函数中可以添加一些成员,可以在构造函数本身上添加,也可以在构造函数内部的 this 上添加。通过这两种方式添加的成员,就分别称为静态成员实例成员

  • 静态成员:在构造函数本上添加的成员称为静态成员,只能由构造函数本身来访问
  • 实例成员:在构造函数内部创建的对象成员称为实例成员,只能由实例化的对象来访问
// 构造函数中的属性和方法我们称为成员,成员可以添加
	function Obj(name,age) {
		// 1. 实例成员是构造函数内部通过this添加的成员(name age method)就是实例成员
		this.name = name;
		this.age = age;
		this.method = function() {
			console.log('创建的方法');
		}
	}
	// a是实例化的对象
	var a = new Obj('Joker',18);
	// 实例成员只能通过实例化的对象来访问
	console.log(a.name);	// Joker
	a.method();
	// 2. 静态成员 在构造函数本身上添加的成员 sex就是静态成员
	Obj.sex = '男';
	// 静态成员只能通过构造函数来访问
	console.log(Obj.sex);
	console.log(a.sex); // undefined 不能通过对象来访问
1.2 构造函数的问题

构造函数方法很好用,但是存在浪费内存的问题

// 构造函数的问题
	function Obj(name,age) {
		this.name = name;
		this.age = age;
		this.method = function() {
			console.log('创建的方法');
		}
	}
	var a = new Obj('Jack',19);
	var b = new Obj('andy',18);
	console.log(a.method === b.method); // false
2. 原型
2.1 构造函数原型 prototype

构造函数通过原型分配的函数是所有对象所共享的
JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象。注意这个 prototype 就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。
我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
在这里插入图片描述

// 2. 一般情况下,我们的公共属性定义到构造函数里面,而公共的方法放到原型对象身上
	function Obj(name,age) {
		this.name = name;
		this.age = age;
		// this.method = function() {
		//	console.log('创建的方法');
		// }
	}
	Obj.prototype.method = function() {
		console.log('原型对象创建的方法');
	}
	var a = new Obj('Jack',19);
	var b = new Obj('andy',18);
	console.log(a.method === b.method); // true
2.2 对象原型 __ proto __

对象都会有一个属性 proto指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 proto 原型的存在。

  • __proto__对象原型和原型对象 prototype 是等价的
  • __proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype

在这里插入图片描述

	function Obj(name,age) {
		this.name = name;
		this.age = age;
	}
	Obj.prototype.method = function() {
		console.log('创建的方法');
	}
	var a = new Obj('Jack',19);
	var b = new Obj('andy',18);
	console.log(a);	// 对象身上系统自己添加一个 __proto__ 指向构造函数的原型对象 prototype
	console.log(a.__proto__ === Obj.prototype);		// true
	// 方法查找规则:
	// 首先看a对象身上是否有method方法,有就执行对象身上的method
	// 没有method,因为有 __proto__ 的存在,就去构造函数原型对象prototype里查找method方法
3. constructor 构造函数
3.1 constructor

对象原型( proto构造函数(prototype)原型对象里面都有一个属性 constructor 属性 ,constructor 我们称为构造函数,因为它指回构造函数本身。

constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。

一般情况下,对象的方法都在构造函数的原型对象中设置。如果有多个对象的方法,我们可以给原型对象采取对象形式赋值,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了。此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。

	function Obj(name,age) {
		this.name = name;
		this.age = age;
	}
	/*
	Obj.prototype.method1 = function() {
		console.log('创建的方法1');
	}
	Obj.prototype.method2 = function() {
		console.log('创建的方法2');
	}
	*/
	Obj.prototype = {
		// 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用 constructor指向原来的构造函数
		constructor: Obj,
		method1: function() {
			console.log('创建的方法1');
		},
		method2: function() {
			console.log('创建的方法2');
		}
	}
	var a = new Obj('Jack',19);
	var b = new Obj('andy',18);
	console.log(Obj.prototype);
	console.log(a.__proto__);
	console.log(Obj.prototype.constructor);
	console.log(a.__proto__.constructor);

构造函数、实例、原型对象三者关系

3.2 原型链

在这里插入图片描述

function Obj(name,age) {
		this.name = name;
		this.age = age;
}
Obj.prototype.method = function() {
	console.log('创建的方法');
}
var a = new Obj('Jack',19);
// 1. 只要是对象就有 __proto__ 原型,指向原型对象
console.log(Obj.prototype);
console.log(Obj.prototype.__proto__ === Object.prototype);
// 2. Obj原型对象里面的 __proto__ 原型指向的是Object.prototype
console.log(Object.prototype.__proto__);
// 3. Object.prototype原型对象里的 __proto__ 原型指向null

在这里插入图片描述

4. 原型对象this指向

构造函数中的this 指向我们实例对象.
原型对象里面放的是方法, 这个方法里面的this 指向的是这个方法的调用者, 也就是这个实例对象

function Obj(name,age) {
		this.name = name;
		this.age = age;
}
var that;
Obj.prototype.method = function() {
	console.log('创建的方法');
	that = this;
}
var a = new Obj('Jack',19);
// 1. 在构造函数中,里面this指向的是对象实例a
a.method();
console.log(that == this); 	// true
// 2. 原型对象函数里面的this指向的是实例对象a
5. 扩展内置对象

可以通过原型对象,对原来的内置对象进行扩展自定义的方法。比如给数组增加自定义求偶数和的功能。

注意:数组和字符串内置对象不能给原型对象覆盖操作 Array.prototype = {} ,只能是 Array.prototype.xxx = function(){} 的方式。

Array.prototype.sum = function() {
    var sum = 0;
    for (var i = 0; i < this.length; i++) {
        sum += this[i];
    }
    return sum;
};
// Array.prototype = {
//     sum: function() {
//         var sum = 0;
//         for (var i = 0; i < this.length; i++) {
//             sum += this[i];
//         }
//         return sum;
//     }
// }
var arr = [1, 2, 3];
console.log(arr.sum());
console.log(Array.prototype);
var arr1 = new Array(11, 22, 33);
console.log(arr1.sum());

二、继承

ES6之前并没有给我们提供 extends 继承。我们可以通过构造函数+原型对象模拟实现继承,被称为组合继承

1. call()

调用这个函数, 并且修改函数运行时的 this 指向

   fun.call(thisArg, arg1, arg2, ...) 
  • thisArg :当前调用函数 this 的指向对象
  • arg1,arg2:传递的其他参数
 		// call 方法
        function fn(x, y) {
            console.log('我想喝咖啡');
            console.log(this);
            console.log(x + y);
        }
        var o = {
            name: 'andy'
        };
        // fn();
        // 1. call() 可以调用函数
        // fn.call();
        // 2. call() 可以改变这个函数的this指向 此时这个函数的this 就指向了o这个对象
        fn.call(o, 1, 2);
2. 借用构造函数继承父类型属性

核心原理: 通过 call() 把父类型的 this 指向子类型的 this ,这样就可以实现子类型继承父类型的属性。

    // 父类
    function Person(name, age, sex) {
      this.name = name;
      this.age = age;
      this.sex = sex;
    }
    // 子类
    function Student(name, age, sex, score) {
      Person.call(this, name, age, sex);  // 此时父类的 this 指向子类的 this,同时调用这个函数
      this.score = score;
    }
    var s1 = new Student('zs', 18, '男', 100);
    console.dir(s1); 

3. 借用原型对象继承父类型属性

一般情况下,对象的方法都在构造函数的原型对象中设置,通过构造函数无法继承父类方法。
核心原理:

  1. 将子类所共享的方法提取出来,让子类的 prototype 原型对象 = new 父类()
  2. 本质:子类原型对象等于是实例化父类,因为父类实例化之后另外开辟空间,就不会影响原来父类原型对象
  3. 将子类的 constructor 从新指向子类的构造函数
		// 借用父构造函数继承属性
        // 1. 父构造函数
        function Father(uname, age) {
            // this 指向父构造函数的对象实例
            this.uname = uname;
            this.age = age;
        }
        Father.prototype.money = function() {
            console.log(100000);
        };
        // 2 .子构造函数 
        function Son(uname, age, score) {
            // this 指向子构造函数的对象实例
            Father.call(this, uname, 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('王多多', 18, 100);
        console.log(son);
        console.log(Father.prototype);
        console.log(Son.prototype.constructor);
类的本质

在这里插入图片描述

		// ES6 之前通过 构造函数+ 原型实现面向对象 编程
        // (1) 构造函数有原型对象prototype 
        // (2) 构造函数原型对象prototype 里面有constructor 指向构造函数本身
        // (3) 构造函数可以通过原型对象添加方法
        // (4) 构造函数创建的实例对象有__proto__ 原型指向 构造函数的原型对象
        // ES6 通过 类 实现面向对象编程 
        class Obj {
        }
        console.log(typeof Obj);
        // 1. 类的本质其实还是一个函数 我们也可以简单的认为 类就是 构造函数的另外一种写法
        // (1) 类有原型对象prototype 
        console.log(Obj.prototype);
        // (2) 类原型对象prototype 里面有constructor 指向类本身
        console.log(Obj.prototype.constructor);
        // (3)类可以通过原型对象添加方法
        Obj.prototype.method = function() {
            console.log('bengbeng');
        }
        var a = new Obj();
        console.dir(a);
        // (4) 类创建的实例对象有__proto__ 原型指向 类的原型对象
        console.log(a.__proto__ === Obj.prototype);

三、ES5新增的方法

1. 数组方法

在这里插入图片描述

		// forEach 迭代(遍历) 数组
        var arr = [1, 2, 3];
        var sum = 0;
        arr.forEach(function(value, index, array) {
            console.log('每个数组元素' + value);
            console.log('每个数组元素的索引号' + index);
            console.log('数组本身' + array);
            sum += value;
        })
        console.log(sum);

在这里插入图片描述

// filter 筛选数组
var arr = [12, 66, 4, 88, 3, 7];
var newArr = arr.filter(function(value, index) {
    // return value >= 20;
    return value % 2 === 0;
});
console.log(newArr);

在这里插入图片描述

// some 查找数组中是否有满足条件的元素 
// var arr = [10, 30, 4];
// var flag = arr.some(function(value) {
//     // return value >= 20;
//     return value < 3;
// });
// console.log(flag);
var arr1 = ['red', 'pink', 'blue'];
var flag1 = arr1.some(function(value) {
    return value == 'pink';
});
console.log(flag1);
// 1. filter 也是查找满足条件的元素 返回的是一个数组 而且是把所有满足条件的元素返回回来
// 2. some 也是查找满足条件的元素是否存在  返回的是一个布尔值 如果查找到第一个满足条件的元素就终止循环

forEach和some的区别

var arr = ['red', 'green', 'blue', 'pink'];
// 1. forEach迭代 遍历
// arr.forEach(function(value) {
//     if (value == 'green') {
//         console.log('找到了该元素');
//         return true; // 在forEach 里面 return 不会终止迭代
//     }
//     console.log(11);

// })
// 如果查询数组中唯一的元素, 用some方法更合适,
arr.some(function(value) {
    if (value == 'green') {
        console.log('找到了该元素');
        return true; //  在some 里面 遇到 return true 就是终止遍历 迭代效率更高
    }
    console.log(11);

});
// arr.filter(function(value) {
//     if (value == 'green') {
//         console.log('找到了该元素');
//         return true; //  // filter 里面 return 不会终止迭代
//     }
//     console.log(11);

// });
1.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>
			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> 按照商品名称查询: <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>
		<script>
			// 利用新增数组方法操作数据
			var data = [{
				id: 1,
				pname: '小米',
				price: 3999
			}, {
				id: 2,
				pname: 'oppo',
				price: 999
			}, {
				id: 3,
				pname: '荣耀',
				price: 1299
			}, {
				id: 4,
				pname: '华为',
				price: 1999
			}, ];
			// 1. 获取相应的元素
			var tbody = document.querySelector('tbody');
			var search_price = document.querySelector('.search-price');
			var start = document.querySelector('.start');
			var end = document.querySelector('.end');
			var product = document.querySelector('.product');
			var search_pro = document.querySelector('.search-pro');
			setDate(data);
			// 2. 把数据渲染到页面中
			function setDate(mydata) {
				// 先清空原来tbody 里面的数据
				tbody.innerHTML = '';
				mydata.forEach(function(value) {
					// console.log(value);
					var tr = document.createElement('tr');
					tr.innerHTML = '<td>' + value.id + '</td><td>' + value.pname + '</td><td>' + value.price + '</td>';
					tbody.appendChild(tr);
				});
			}
			// 3. 根据价格查询商品
			// 当我们点击了按钮,就可以根据我们的商品价格去筛选数组里面的对象
			search_price.addEventListener('click', function() {
				// alert(11);
				var newDate = data.filter(function(value) {
					return value.price >= start.value && value.price <= end.value;
				});
				console.log(newDate);
				// 把筛选完之后的对象渲染到页面中
				setDate(newDate);
			});
			// 4. 根据商品名称查找商品
			// 如果查询数组中唯一的元素, 用some方法更合适,因为它找到这个元素,就不在进行循环,效率更高]
			search_pro.addEventListener('click', function() {
				var arr = [];
				data.some(function(value) {
					if (value.pname === product.value) {
						// console.log(value);
						arr.push(value);
						return true; // return 后面必须写true  
					}
				});
				// 把拿到的数据渲染到页面中
				setDate(arr);
			})
		</script>
	</body>

</html>

2. 字符串方法

在这里插入图片描述
用于input验证空白字符

<!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>
</head>

<body>
    <input type="text"> <button>点击</button>
    <div></div>
    <script>
        // trim 方法去除字符串两侧空格
        var str = '   an  dy   ';
        console.log(str);
        var str1 = str.trim();
        console.log(str1);
        var input = document.querySelector('input');
        var btn = document.querySelector('button');
        var div = document.querySelector('div');
        btn.onclick = function() {
            var str = input.value.trim();
            if (str === '') {
                alert('请输入内容');
            } else {
                console.log(str);
                console.log(str.length);
                div.innerHTML = str;
            }
        }
    </script>
</body>

</html>
3. 对象方法
3.1 Object.keys()

在这里插入图片描述

// 用于获取对象自身所有的属性
var obj = {
    id: 1,
    pname: '小米',
    price: 1999,
    num: 2000
};
var arr = Object.keys(obj);
console.log(arr);
arr.forEach(function(value) {
    console.log(value);

})
3.2 Object.defineProperty()

在这里插入图片描述
在这里插入图片描述

		// Object.defineProperty() 定义新属性或修改原有的属性
        var obj = {
            id: 1,
            pname: '小米',
            price: 1999
        };
        // 1. 以前的对象添加和修改属性的方式
        // obj.num = 1000;
        // obj.price = 99;
        // console.log(obj);
        // 2. Object.defineProperty() 定义新属性或修改原有的属性
        Object.defineProperty(obj, 'num', {
            value: 1000,
            enumerable: true
        });
        console.log(obj);
        Object.defineProperty(obj, 'price', {
            value: 9.9
        });
        console.log(obj);
        Object.defineProperty(obj, 'id', {
            // 如果值为false 不允许修改这个属性值 默认值也是false
            writable: false,
        });
        obj.id = 2;
        console.log(obj);
        Object.defineProperty(obj, 'address', {
            value: '中国山东蓝翔技校xx单元',
            // 如果只为false 不允许修改这个属性值 默认值也是false
            writable: false,
            // enumerable 如果值为false 则不允许遍历, 默认的值是 false
            enumerable: false,
            // configurable 如果为false 则不允许删除这个属性 不允许在修改第三个参数里面的特性 默认为false
            configurable: false
        });
        console.log(obj);
        console.log(Object.keys(obj));
        delete obj.address;
        console.log(obj);
        delete obj.pname;
        console.log(obj);
        Object.defineProperty(obj, 'address', {
            value: '中国山东蓝翔技校xx单元',
            // 如果值为false 不允许修改这个属性值 默认值也是false
            writable: true,
            // enumerable 如果值为false 则不允许遍历, 默认的值是 false
            enumerable: true,
            // configurable 如果为false 则不允许删除这个属性 默认为false
            configurable: true
        });
        console.log(obj.address);
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凡小多

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值