构造函数和原型
构造函数函数名一般要大写;构造函数时一种特殊的函数,即为对象成员变量赋初值,它总与new一起使用。我们把对象中一些属性和方法抽取出来,然后封装到这个函数里面
new在执行的时候会做四件事情
- 在内存中创建一个新的空对象
- 让this指向这个新的对象
- 执行构造函数里面的代码,给这个新对象添加属性和方法
- 返回这个新对象(所以构造函数里面不需要return)
实例成员就是构造函数内部通过this添加的成员,只能通过示例对象来访问
在构造函数外面添加Star.sex=’’是静态属性只能通过Star.sex访问
构造函数的问题:浪费内存资源(有同一个函数时,开辟多个空间存在同一个函数)
原型对象Star.prototype.sing=function(){}每个构造函数都存在一个原型对象,存放一些通用不变的方法(共享方法)
对象原型_proto_指向的就是原型对象所以实例对象可以直接访问原型对象的方法
对象原型和构造函数的原型对象里面都有一个属性constructor属性,主要用于记录该对象引用于哪个构造函数。如下我们每声明一个函数就Star.prototype一次比较麻烦,可以声明一个对象,但是必须把这个对象重新指向一下!!不然默认指向Object的构造函数。
Star.prototype = {
// 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数,否则原型对象指向的不是原来的构造函数,
constructor: Star,//关键语句
sing: function() {
console.log('我会唱歌');
},
movie: function() {
console.log('我会演电影');
}
}
构造函数、实例对象、原型对象的关系:
优先执行实例对象自身的属性
this指向问题:原型对象里面的this指向的还是实例对象本身
扩展内置对象方法
数组和字符串内置对象不能给原型对象覆盖操作Array.prototype={}只能是Array.prototype.xxx=function(){}的方式。
call()可以调用函数,还可以修改this指向
fn.call();fn.call(object)此时这个函数的this就指向object对象还可以传递参数:fn.call(object, 1, 2);
通过构造函数+原型对像模拟实现继承,被称为组合继承;使用call()
Father.call(this, uname, age);利用父构造函数继承属性
function Father(uname, age) {
// this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
// 2 .子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
Father.call(this, uname, age);
this.score = score;
}
子构造函数继承父构造函数的方法
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('孩子要考试');
}
类的本质还是一个函数,可以认为类就是构造函数的另外一种写法!!!!
// (1) 构造函数有原型对象prototype
// (2) 构造函数原型对象prototype 里面有constructor 指向构造函数本身
// (3) 构造函数可以通过原型对象添加方法
// (4) 构造函数创建的实例对象有__proto__ 原型指向 构造函数的原型对象
数组方法:
eg:
arr.forEach(function(value, index, array) {
console.log(value)
console.log(index)
console.log(array);
sum += value
})
filter()方法创建一个新的数组,主要用于筛选;返回的是一个新数组
eg:
arr.filter(function(value, index,array) {
// return value >= 20;
return value % 2 === 0
});
some()用于检测数组中的元素是否满足指定条件,返回true或false
eg:
arr1.some(function(currentValue,index,array){
})
找到第一个就直接返回了,不会继续执行
// 1. filter 也是查找满足条件的元素 返回的是一个数组 而且是把所有满足条件的元素返回回来
// 2. some 也是查找满足条件的元素是否存在 返回的是一个布尔值 如果查找到第一个满足条件的元素就终止循环
如果查询数组中唯一的元素, 用some方法更合适,因为它找到这个元素,就不在进行循环,效率更高!!!!
使用some的时候,如果想单独返回唯一的一个值,必须return true,如果是false的话遍历就不会停止一直找到最后一个满足条件的value,这样也就失去了some的价值!!!!
eg:
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
}
});
forEach()(可以遍历数组)和filter()里面加return不会终止迭代,所以使用some()效率更高!!!找到了就直接退出了。
修改属性值方法
// 2. Object.defineProperty() 定义新属性或修改原有的属性
Object.defineProperty(obj, 'num', {
value: 1000,
enumerable: true
});
Object.defineProperty(obj, 'id', {
// 如果值为false 不允许修改这个属性值 默认值也是false
writable: false,
});
value设置值;writable是否允许修改
var arr = Object.keys(obj);获取对象的所有属性名效果类似for…in;返回的是一个由属性名组成的数组。enumerable可以设置对象的某个属性值是否可以被遍历。
eg: Object.defineProperty(obj, 'address', {
value: '中国山东蓝翔技校xx单元',
// 如果只为false 不允许修改这个属性值 默认值也是false
writable: false,
// enumerable 如果值为false 则不允许遍历, 默认的值是 false
enumerable: false,
// configurable 如果为false 则不允许删除这个属性 不允许在修改第三个参数里面的特性 默认为false,修改一次之后就不能再修改了
configurable: false
});
delete obj.uname可以直接删除对象的某个属性!!
函数的定义和调用:
function(){}
let fn=function(){}
let fn=new Function(‘参数一’,’参数二’,’函数体’)
了解:
var f = new Function('a', 'b', 'console.log(a + b)');
f(1, 2);
// 4. 所有函数都是 Function 的实例(对象)
console.dir(f);
// 5. 函数也属于对象
console.log(f instanceof Object);
Function()也可以用原型链来分析
函数的调用:
function fn() {
console.log('人生的巅峰');
}
// fn(); fn.call()
// 2. 对象的方法
var o = {
sayHi: function() {
console.log('人生的巅峰');
}
}
o.sayHi();
// 3. 构造函数
function Star() {};
new Star();
// 4. 绑定事件函数
// btn.onclick = function() {}; // 点击了按钮就可以调用这个函数
// 5. 定时器函数
// setInterval(function() {}, 1000); 这个函数是定时器自动1秒钟调用一次
// 6. 立即执行函数
(function() {
console.log('人生的巅峰');
})();
// 立即执行函数是自动调用
this指向:
// 1. 普通函数 this 指向window
function fn() {
console.log('普通函数的this' + this);
}
window.fn();
// 2. 对象的方法 this指向的是对象 o
var o = {
sayHi: function() {
console.log('对象方法的this:' + this);
}
}
o.sayHi();
// 3. 构造函数 this 指向 ldh 这个实例对象 原型对象里面的this 指向的也是 ldh这个实例对象
function Star() {};
Star.prototype.sing = function() {
}
var ldh = new Star();
// 4. 绑定事件函数 this 指向的是函数的调用者 btn这个按钮对象
var btn = document.querySelector('button');
btn.onclick = function() {
console.log('绑定时间函数的this:' + this);
};
// 5. 定时器函数 this 指向的也是window
window.setTimeout(function() {
console.log('定时器的this:' + this);
}, 1000);
// 6. 立即执行函数 this还是指向window
(function() {
console.log('立即执行函数的this' + this);
})();
call 第一个可以调用函数 第二个可以改变函数内的this指向主要作用是可以实现继承
apply 第一个可以调用函数 第二个可以改变函数内的this指向,但是参数必须是数组的方式传递;apply 的主要应用 比如说我们可以利用 apply 借助于数学内置对象求数组最大值 :apply可以将一个字符串数组转换成一个字符串,可以将一个数字数组转换成多个单个数字;这样我们就可以直接使用Math.max方法求出最大值或者是最小值,很巧妙也很方便!!
// Math.max();
var arr = [1, 66, 3, 99, 4];
var arr1 = ['red', 'pink'];
// var max = Math.max.apply(null, arr);
var max = Math.max.apply(Math, arr);
var min = Math.min.apply(Math, arr);
console.log(max, min);
bind()方法和call方法使用基本一致但是bind方法使用不会调用该方法!!!!返回的是对原函数的一个拷贝的新函数!!如果有的函数我们不需要立即调用,但是又想改变这个函数内部的this指向此时我们就用bind()实现
eg:
btns[i].onclick = function() {
this.disabled = true;
setTimeout(function() {
this.disabled = false;
}.bind(this), 2000);
}
严格模式:可以应用到整个脚本或者个别函数中,我们将严格模式分为为脚本开启严格模式和为函数开启严格模式
function fn() {
'use strict';
// 下面的代码按照严格模式执行
}
(function() {
'use strict';
})();
严格模式的变化:不声明直接调用变量会报错;不能随意删除已经声明好的变量;严格模式下全局作用域中函数中的 this 是 undefined;严格模式下,如果 构造函数不加new调用, this 指向的是undefined 如果给他赋值则 会报错;定时器 this 还是指向 window;函数里面的参数不能有重名。。不允许在非函数的代码块中声明函数。
高阶函数:是对其他函数进行操作的函数,它接受作为参数或将函数作为返回值输出。
闭包
闭包:有权访问另一个函数作用域中变量的函数。
闭包的主要作用: 延伸了变量的作用范围
function fn() {
var num = 10;
return function() {
console.log(num);
}
}
var f = fn();
f();
在之前,我们时没办法在一个作用域外面去访问作用域里面的变量,我们可以通过return将一个函数返回给一个变量,之后再调用就能形成闭包,可以直接访问另一个作用域里面的变量。(按理来说一个函数里面的变量在被调用执行之后,就会被销毁,闭包可以延长变量的使用周期,只有该变量再被调用输出的时候才被销毁,也扩大了变量的使用范围)
利用给对象添加属性的方式输出点击li的索引号
for (var i = 0; i < lis.length; i++) {
lis[i].index = i;
lis[i].onclick = function() {
// console.log(i);
console.log(this.index);
}
}
利用闭包的形式对多个li进行点击输出索引号的操作
for (var i = 0; i < lis.length; i++) {
// 利用for循环创建了4个立即执行函数
// 立即执行函数也成为小闭包因为立即执行函数里面的任何一个函数都可以使用它的i这变量
(function(i) {
// console.log(i);
lis[i].onclick = function() {
console.log(i);
}
})(i);
}
闭包的方式不一定是最好的,它创建了多个立即执行函数,i在不点击的情况下会一直存在比较占用内存空间。
递归函数:函数内部自己调用自己(很容易出现栈溢出问题,所以必须加一个return退出函数)
// 利用递归函数求斐波那契数列(兔子序列) 1、1、2、3、5、8、13、21...
// 用户输入一个数字 n 就可以求出 这个数字对应的兔子序列值
// 我们只需要知道用户输入的n 的前面两项(n-1 n-2)就可以计算出n 对应的序列值,实际上就是不停的1+1+1…的过程
function fb(n) {
if (n === 1 || n === 2) {
return 1;
}
return fb(n - 1) + fb(n - 2);
}
console.log(fb(3));
console.log(fb(6));
深拷贝和浅拷贝
浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用(也就是只拷贝了对象里面对象元素的地址,一次修改会原来的对象相关属性也会被修改)
深拷贝可以拷贝多层,每一级的数据都会拷贝
assign(新对象,旧对象)方法可以实现浅拷贝
深拷贝(利用递归实现)
var o = {};
// 封装函数
function deepCopy(newobj, oldobj) {
for (var k in oldobj) {
// 判断我们的属性值属于那种数据类型
// 1. 获取属性值 oldobj[k]
var item = oldobj[k];
// 2. 判断这个值是否是数组
if (item instanceof Array) {
newobj[k] = [];
deepCopy(newobj[k], item)
} else if (item instanceof Object) {
// 3. 判断这个值是否是对象
newobj[k] = {};
deepCopy(newobj[k], item)
} else {
// 4. 属于简单数据类型
newobj[k] = item;
}
}
}