构造函数和原型导读
三种创建对象方式
- 利用new Object()创建
var obj = new Object();
- 利用对象字面量创建
var obj = {}
- 利用构造函数创建对象
function Star(name,age){
this.name = name;
this.age = age;
this.sing= funcrion(){
console.log("我会唱歌")
}
}
var wanwan = new Star('wanwan',18)
实例成员和静态成员
- 实例成员就是构造函数内部通过shis添加的成员,实例成员只能通过实例化对象来访问
function Star(name.age){
this.name = name;
this.age = age;
this.sing = function(){
console.log("ok")
}
}
var obj = new Star("wanwan",18);
构造函数原型对象
- prototype原型对象和__proto__对象原型
function Star(name,age){
this.name = name;
this.age = age;
this.sing = function(){
console.log('ok')
}
}
var a = new Star('aoao',18);
var w = new Star('wanwan',18);
console.log(a === w)//结果为false,两者不存在同一空间
Star.portotype.sing = function(){
console.log('ok')
}
console.log(a === w)//结果为true
//每个对象都会有一个__proto__指向prototype
//__proto__对象原型和prototype是等价的
//__proto__对象原型的意义是为对象的查找机制提供一个方向,它是一个非标准属性,不可以赋值,只是指向原型对象prototype
console.log(a.__proto__ === prototype)//true
原型constructor
function Star(name,age){
this.name = name;
this.age = age;
this.sing = function(){
console.log('ok')
}
}
var a = new Star('aoao',18);
var w = new Star('wanwan',18);
User.prototype = {
//此时的原型对象被修改赋值的是一个对象,则必须手动利用constructor指回原来的构造函数
constructor: User,
lovely:function(){
console.log('lovely')
},
sing:function(){
console.log('sing')
}
}
console.log(User.prototype.constructor)//Object
console.log(w.__proto__.constructor)//Object
构造函数,实例,原型对象三者之间的关系
原型链
如果都有相同属性,执行就近原则,如果都没有返回undefined
<script>
function User(name,age){
this.neme = name;
this.age = age;
}
// User.prototype.sex = '女2'
Object.prototype.sex = '女3'
var ww = new User("wanwan",18);
// ww.sex = '女1'
console.log(ww.sex);
</script>
//如果都有sex属性,那么执行就近原则。如果都没有则返回undefined
原型对象this指向
不管是构造函数还是原型对象里的this都是指向实例对象
<script>
function User(name,age){
this.name = name;
this.age = age;
}
var that;
User.prototype.sing = function(){
console.log('会唱歌');
that = this;
}
var ww = new User('wanwan',19);
ww.sing();
console.log(that === ww); //true
</script>
利用原型对象扩展内置对象方法
Array.prototype.sum = function(){
var sum = 0;
for(var i=0;i<this.length;i++){
sum += this[i];
}
return sum;
}
//此方法不可用会覆盖原有的方法,会报错!
//Array.prototype.sum = {
// sum:function(){
// var sum = 0;
// for(var i=0;i<this.length;i++){
// sum += this[i];
// }
// return sum;
// }
// }
var arr = [1,2,3];
soleon.og(arr.sum())//6
//打印内置对象方法,此时内部新添加了自定义sum()方法
console.log(Array.prototype)
call()方法的作用
function demo(x,y){
console.log('奥里给');
console.log(this);
console.log(x + y);
}
var a = {
name: 'xixi'
}
//1.call()可以调用函数
// deme.call()
//2.call()可以改变这个函数的this指向
//此时打印this的指向是a这个对象
demo.call(a,6,6)
//后面可以象demo传参
利用父类构造方法继承属性
function Father(name.age){
this.name = name;
this.age = age;
}
function Son(name,age){
//改变this指向,这里的this指向了子类,继承了父类构造函数的属性
Father.call(this,name,age);
this.sex = '男';
}
var son = new Son("xixi",18);
sonsole.log(son)
利用原型对象继承方法
function Father(name,age){
this.name = name;
this.age = age;
}
//定义父类原型对象
Father.prototype.money = function(){
console.log('工作');
}
function Son(name,age){
Father.call(this,name,age);
}
//利用原型对象继承父类方法
// 1.不可选,这样是同一对象,子类会修改公共的原型对象
// Son.prototype = Father.prototype;
//2. Son.prototype = new Father();
//如果利用了对象的形式修改原型对象,一定要用cunstructor指回原来的原型对象
Son.prototype.cunstructor = Son;
//定义子类原型对象
Son.prototype.exem = function(){
console.log('上学');
}
var son = new Son('yaya',18);
console.log(son);
console.log(Father.prototype);
console.log(Son.prototype.cunstructor);
</script>
函数进阶导读
函数的定义和调用
- ES5之前通过构造函数 + 原型实现面向对象编程
(1)构造函数有原型对象prototype
(2)构造函数有原型对象prototype里面有constructor指向构造函数本身
(3)构造函数可以通过原型对象添加方法
(4)构造函数创建的实例对象有__proto__原型指向构造函数的原型对象
ES5通过类实现面向对象编程
class Star{}
console.log(typeof Star);//function
//1.类的本质其实还是一个函数,也可以理解为函数的另一种写法
//(1)类有原型对象prototype
console.log(Star.prototype);
//(2)类的原型对象prototype里有cunstructor指向本身
console.log(Star.prototype.constructor);
//(3)类可以通过原型对象添加方法
Star.prototype.test = function(){
console.log('ok');
}
var demo = new Star();
console.log(demo);
//(4)类创建的实例对象有__proto__原型指向类的原型对象
console.log(demo.__proto__ === Star.prototype);//true
ES5数组方法
遍历方法: forEach(),mao(),filter(),some(),every()
- forEach(),用法数组.forEach(数组当前项的值,数组当前值的索引,数组对象)
var arr = [1,2,3];
var sum = 0;
arr.forEach(function(value,indx,array){
console.log('元素是'+value);
console.log('索引是'+indx);
console.log('数组本身'+array);
sum += value;
})
console.log(sum);
- filter()方法创建一个新的数组,主要用于筛选数组,
注意它直接返回一个新数组
var arr = [10,20,30,40];
//返回的是一个新数组需要变量接收数组
var newArr = arr.forEach(function(value,indx,array){
return value >= 20;
})
console.log(newArr);
- some()方法用于检测数组中是否有满足条件的元素
注意它返回的是布尔类型,有就返回true无就返回false,如果第一个条件满足就终止循环不在继续查找
var arr = [1,'a',2,'b',3,'c'];
var flag = arr.some(function(value){
return value == 1;
})
console.log(flag)//ruue
字符串方法
- trim():去除字符串两侧空格
var str = ' aa bb ';
console.log(str.trim());
//打印aa bb
对象方法
- Object.defineProperty(obj,prop,descriptor),定义对象中的新属性或修改原有的属性.
- obj:必须,目标对象
- prop:必须,需定义或修改的属性名
- descriptor:必须,目标属性所有的特性
var str = {id:1,name:‘mini’,price:1998};
//1原来修改和添加方法
str.num = 1000;
str.price = 998;
//2.Object.defineProperty()方法
Object.defineProperty(str,'num',{
//value:设置属性的值,默认为undefined
value:998,
});
Object.defineProperty(str,'id',{
//writable:值是否可以重写,true | false,默认为false无法修改
weitable:false,
});
Object.defineProperty(str,'color',{
value:'skyblue',
//enumerable:如果为false则不允许遍历,默认false
enumerable:false,
//cnofigurable:如果为false则不允许删除这个属性,不允许在线修改第三个参数里的特性,默认false
cnofigurable:false,
//如果修改会报错
})
函数的定义与调用
- 自定义函数(命名函数)
function fn1(){}
fn1();
window.fn1();//指向window
- 函数表达式(匿名函数)
var f = function(){};//this指向f
- 利用new Function(‘参数1’,‘参数2’,‘函数体’)
var f = new Function('a','b','a + b ')
f(6,6);//this指向f
- 绑定事件函数
btn.onclick = function(){
console.log(this);//this指向绑定元素
}
- 定时器函数
window.setTimeout
setTimeout(function(){
console.log(this)//指向window
},2000)
- 立即执行函数
(function(){
console.log(this)//指向window
})()
改变this指向方法
- call()方法,可以改变this指向,一般用于继承
function Father(name,age){
this.name = name;
this.age = age;
}
function Son(name,age){
//这个this是用call方法把Son的this指向Father里的this,从而实现继承
Father.call(this,name,age)
}
var son = new Son('wanwan',18);
console.log(son);
- apply()也是调用函数,但是它的参数必须是数组(伪数组),一般常用借助数学内置对象求最大最小值
var ww = {name: 'wanwan'}
function fn(arr){
console.log(this);
console.log(arr);
};
fn.apply(ww,['skyblue']);
//应用例如
var arr = [1,3,5,7,9];
//var max = Math.max.apply(null,arr);
//这里null要写为Math指向Math;
var max = Math.max.apply(null,arr);
console.log(max)//9
- bind()方法改变this指向但是不立即调用,返回新的函数
<button>按钮</button>
<button>按钮</button>
<button>按钮</button>
<script>
var ww = {name: "wanwan"}
function fn(x,y){
console.log(this);
console.log(x + y);
};
var f = fn.bind(ww,6,9);
f();
//常用例如:点击按钮三秒后才能再次点击
var btn = document.querySelector('button');
//单个按钮
btn.onclick = function(){
this.disabled = true;
setTimeout(function(){
this.disabled = false;
}.bind(this),3000)
}
//多个按钮
var btns = document.querySelectorAll('button');
for(var i=0;i<btn.length;i++){
btns[i].onclick = function(){
this.disabled = true;
setTimeout(function(){
this.disabled = false;}.bind(this),2000)
}
}
call apply bind
相同点:
- 都可以改变this指向
区别:
- call和apply会调用函数并改变函数内部this指向
- call和apply传递的参数不一样,call传递aru1,aru2…形式,app必须以数组形式[‘arg’]
- bind不会调用函数,可以改变this指向
主要应用场景
- call经常做继承
- apply经常跟数组有关,比如借助数学对象实现数组最大最小值
- bind 比如改变定时器内部的this指向
严格模式
全局严格模式
<script>
//开启全局严格模式
'use strict';
</script
开启函数内部严格模式
function fn(){
'use strict';
};
我们的变量名必须先声明再使用
num = 10;
console.log(num);//报错:num is not defined
var num = 10;
console.log(num);
我们不可以删除已经定义好的变量
var num = 10;
delete num;
//报错:Uncaught SyntaxError: Delete of an unqualified identifier in strict mode.
严格模式下的this指向问题
- 以前在全局作用域函数中的this指向window对象
- 严格模式下全局作用域中函数的this是undefined
- 以前构造函数时不加new也可以调用当普通函数,this指向全局
- 严格模式下如果构造函数不加new调用this会报错
- new 实例化的构造函数指向创建的对象实例
- 定时器this还是指向window
- 事件,对象还是指向调用者
严格模式下函数变化
1.函数不能有重名的参数
2.函数必须声明在顶层,不允许在非函数的代码块内声明函数,例如if,for内部不允许,函数内可以声明函数
高阶函数
==高阶函数是对其他函数进行操作的函数,它接收函数最为参数或将函数作为返回值输出
闭包
闭包值指有权访问另一个函数作用域中变量的函数,就是哟个作用域可以访问另一个函数内部的局部变量。
闭包的作用
- 主要延伸了变量的作用范围
第一种:
function fn1() {
//1.声明变量
var sum = 10;
function fn2() {
//3.访问了另外一个函数的变量产生了闭包
console.log(num)
};
//2.调用fn2函数
fn2();
};
fn1()//打印10
第二种:
function fn3() {
//1.声明变量
var num = 10;
function fn4() {
//3.调用了局部变量的num
console.log(num);
}
//2.返回一个fn4函数
return fn4;
};
var f = fn3();
//因为内部return了函数fn4,类似于一下操作
var f = function fn4() { console.log(num)}
简化版:
function fn3() {
var num = 10;
return function(){
//就是高阶函数,闭包也是高阶函数的一种用法
console.log(sum)
}
}
闭包的应用
1.利用闭包方式得到所有li的索引
<ul>
<li>第一个li</li>
<li>第二个li</li>
<li>第三个li</li>
</ul>
<script>
var lis = document.querySelector('ul').querySelector('li');
//第一种:利用动态添加属性的方式
for(var i=0;i<lis.length;i++) {
//需要为每个li动态的添加一个属性
lis[i].index = i;
//为每个li添加点击事件,并打印出当前li的索引号
lis[i].onclick = function(){
//此时的打印的索引都是3,因为是异步
console.log(this.index)
}
}
第二种:利用闭包
for(var i=0;i<lis.length;i++) {
//立即执行函数也称为小闭包,使用了外部的i
//2.再把接收的i传给下面的i
(function(i){
lis[i].onclick = function() {
//3.打印当前的i,就是当前的索引号了
console.log(i)
}
})(i)//1.这里的i接收for里的i
}
</script>
- 定时器中的闭包
<ul>
<li>第一个li</li>
<li>第二个li</li>
<li>第三个li</li>
</ul>
<script>
var lis = document.querySelector('ul').querySelector('li');
//利用闭包3秒后打印三个li
for(var i=0;i<lis.length;i++) {
(function(i){
setTimeout(function(){
console.log(lis[i].innerHTML)
},3000)
})(i);
}
</script>
闭包练习
- 闭包应用计算车费,起步价10(3公里内),没3公里加5,如果堵车要加5快.
var car = (function(){
var start = 10;//起步价
var total = 0;//总共车费
return {
//正常车费
price: function(p){
//如果在起步价以内
if( p <= 3){
total = start;
}else{
//如果大于3公里
total = statr + (p - 3)*5
}
return total;
},
//堵车费用
jam: function(flag){
//如果堵车就加5,否则就直接返回
return flag ? total + 5 :total
}
}
})();
//打印5公里和堵车费用
console.log(car.price(5),car.jam(true))
递归
递归函数:函数内部自己调用自己,这个函数就是递归函数
var num = 1;
function fn() {
if(num == 3){
return;//递归必须添加退出条件
}
fu();
};
fn();
- 利用递归计算阶乘
function fn(n){
if(n == 1){
return 1;
}
return n + (n-1);
fn();
};
console.log(fn(3))//返回6
//解析:
return 3 * (fn(3-1))
return 3 * (fn(2))
return 3 * (2 * fn(2-1))
return 3 * (2 * fn(1))
return 3 * (2 * 1)
- 利用递归遍历数组
var data = [{
id: 1,
name: '零食',
{
id: 2,
name: '可乐',
waters:[{
id: 22,
name: '可口可乐'
},{
id: 23,
name: '百事可乐'
}]
}];
//利用递归获取用户输入数子的返回相应结果
function getId(data,id){
//用forEach遍历里面的每一个对象
data.forEach(function(item){
//可以声明一个变量保存数据
var o = {};
//如果用户输入的是外层就输出外层
if(item.id == id){
//console.log(item);
o = item;
}else if(item.foods && item.foods.length > 0){
//如果外层找不到再把item内部foods传进去继续找
o = getIid(item.foods,id);
}else if(item.waters && item.waters.length > 0){
o = getId(item.waters,id);
}
});
//遍历完了之后打印o
return o;
};
console.log(getId(data,22))
浅拷贝和深拷贝
浅拷贝
浅拷贝只是拷贝第一层,更深层次对象级别只能拷贝引用
var obj = {
id: 1,
name: 'wanwan',
msg: {
age: 18
}
};
var w = {};
//把obj拷贝给w
for(var k in obj) {
//k 是属性名 obj[k] 是属性值
w[k] = obj[k];
}
w.msg.age = 19;
//浅拷贝内容指向的是同一地址,数据改变都会改变
//浅拷贝提供的方法,把obj拷贝给w
Object.assign(w,obj)
深拷贝
深拷贝拷贝多层,每一级的数据都会拷贝
var obj = {
id: 1,
name: 'wanwan',
msg: {
age: 18
},
color:['skyblue','pink']
};
var w = {};
function deppCopy(newobj,oldobj) {
for(var k in oldobj) {
//先获取属性值才可以判断
var item = oldobj[k];
//判断属于什么数据类型再拷贝
if(item instanceof Array) {
newobj[k] = [];
deppCopy(newobj[k],item)
}else if(item instanceof Object) {
newobj[k] = {};
deppCopy(newobj[k],item)
}else{
newobj[k] = w;
}
}
}
deppCopy(o,obj)
console.log(o)