拷贝继承
var obj1={
age:20
cat:["大黄","加菲猫"]
};
var obj2 = obj1; // 拷贝继承
obj2.age = 100;
console.log(obj1.age); // 100
console.log(obj2.age); // 100
// 另一种写法
var obj1={
a:10,
b:20,
c:30
};
var obj2={
a:obj1.a,
b:obj1.b,
c:obj1.c
}
obj2.a = 100;
console.log(obj1.a); // 10
console.log(obj2.a); // 100
两种方式继承都是拷贝继承,第二种可以理解成深拷贝,上面一种方式就是浅拷贝
深拷贝
深拷贝:实际上还是复制,把一个对象中的所有的属性或方法,一个一个的找到,并且在另一个对象中开辟相应的空间,一个一个的存储到这个新的对象中。
// 递归
var obj1={
age:10,
sex:"男",
car:["奔驰","宝马","特斯拉","奥拓"],
dog:{
name:"大黄",
age:5,
color:"黄色"
}
};
// 通过深拷贝把数据拷贝到另一个对象中
// 空对象
var obj2={};
// a---当前的对象,b是一个目标对象,把a对象中所有的内容复制到b对象中
function extend(a,b){
// 遍历
for(var key in a){
// 先获取a对象中每个属性值
var item = a[key];
// 判断这个属性是不是数组
if(item instanceof Array){
// 如果数据是数组,就在b对象中添加一个新的属性,并且这个属性的值也是数组
b[key]=[];
// 把对象a中的数据一个一个的复制到b对象这个数组中
extend(item,b[key]);
}else if(item instanceof Object){
// 如果这个数组是对象,那么b对象中添加一个属性,也是对象类型的
b[key]={};
extend(item,b[key]);
}else{
b[key]=item; // 直接赋值
}
}
}
// 调用方法
extend(obj1,obj2);
obj2.age=100;
console.log(obj1.age); // 10
console.log(obj2.age); // 100
浅拷贝:第一个对象和第二个对象的地址是相同的(类似于指针),其中一个对象的属性值改变,另一个对象的属性值也会改变
深拷贝:两个对象之间发生拷贝后,属性的值是不会相互影响的
对象冒充
对象冒充:看起来是继承,实际上是假的,继承一个类的时候需要用到“对象冒充”
// 构造函数
function Animal(name,color){
this.name=name;
this.color=color;
}
// 通过原型添加方法
Animal.prototype.eat=function(){
console.log("今晚吃的是鱼");
};
// 构造函数
function Dog(name,color,age){
// this.name=name;
// this.color=color;
this.age=age;
// 对象冒充 --- 仅仅是盗用了别人的构造函数实现属性的继承
this.Animal=Animal; // 借用
this.Animal(name,color);
delete this.Animal; // 删掉了(可以省略)
}
// 实例化
var dog = new Dog("大黄","黄色",20);
console.log(dog.name,dog.age,dog.color);
// dog.eat(); 没有继承
原型的不同写法
// 构造函数
function Person(){
}
// 通过原型添加属性和方法
Person.prototype.name="乔峰";
Person.prototype.age="20";
Person.prototype.sex="男";
Person.prototype.eat=function(){
console.log("吃的饭");
}
Person.prototype.play=function(){
console.log("玩的好累");
}
Person.prototype.sleep=function(){
console.log("一个人睡觉");
}
// 用对象的方法,改变原型的执行,在这个对象中直接添加属性和方法
Person.prototype={
constructor:Person, // 一定要加,不加有问题
name:"卡卡西",
age:20,
sex:'男',
eat:function(){
console.log("吃");
},
play:function(){
console.log("玩");
}
}
动态原型
动态原型:会让这种写法看起来更像面向对象语言中类的写法
function Person(name,age){
this.name=name;
this.age=age;
// 每次把原型的方法写在构造函数外部很麻烦,所以可以这样写
// 在构造函数初始化的时候直接添加原型方法
if(typeof Person.init=="undefined"){
// 原型方法
Person.prototype.sayHi=function(){
console.log("hi");
}
Person.init=true; // 标识
}
}
var p = new Person("小白",20);
p.sayHi();
通过原型为系统对象添加方法
// 沙箱写法 不会污染全局
(function(){
// 通过原型的方式为系统的对象添加原型原型方法
// 字符串有没有可以干掉字符串中所有的空格的方法呢?
String.prototype.myTrim=function(){
return this.replace(/\s*/ig,"");
}
})();
Object中的三个常见方法
function Person(name,sex){
this.name=name;
this.sex=sex;
}
// 原型添加方法
Person,prototype.height="170";
Person.prototype.eat=function(){
console.log("吃");
};
// 构造函数
function Student(age){
this.age=age;
}
Student.prototype=new Person("小明")
// 对象
var stu = new Student(10);
console.log(stu.hasOwnProperty("age")); // 返回布尔值,判断该属性是不是当前对象
Student.prototype=new Person("小明")
这个代码出来就是代表有继承了,原型指向改变
因为js中并没用对hasOwnProperty
方法做保护,所以,我们可以私自更改这个方法
var myObj={
age:20,
hasOwnProperty:function(str){
return false;
}
}
console.log(myObj.hasOwnProperty("age")); //false
此时这个方法不是很容易辨别属性是不是属于某个对象的了,如果担心上面问题,这样解决
console.log(Object.prototype.hasOwnProperty.call(myObj,"age"));
判断对象p是不是实例对象的原型
function Teacher(){
}
var p = new Person("小明",20); // 实例对象
Teacher.prototype=p;// 原型指向改变了
var t= new Teacher();// 实例对象
// 用来判断当前对象是不是另一个对象的原型
console.log(p.isPrototypeOf(t));
判断这个属性是不是当前对象可以枚举的属性
// 能够被for-in遍历的属性是可枚举的
var p1= new Person("小明",10);
// 遍历
for(var key in p1){
console.log(key);
}
// 判断这个属性是不是当前对象可以枚举的属性
console.log(p1.propertyIsEnumerable("weight"));
// 这个属性必须是属于实例对象的,并且不属于原型中
// 这个属性必须是可枚举的,也就是自定义的属性
// 如果对象没有指定的属性,返回false
作用域和预解析
- 作用域:变量的使用范围
- 全局作用域和局部作用域
- 全局作用域:整个页面或者都可以理解为
script
标签内部 - 局部作用域:函数内部
- 作用域链:变量在使用时候会层层搜索
var num = 10;// 全局变量,在全局作用域中
function f1() {
var number = 20 ; // 局部变量
}
f1();
console.log(number); //报错
作用域链
var age = 10;
// 0 级
function f1(){
// 1 级
var age = 20;
function f2(){
// 2 级
var age = 20;
function f3(){
// 3 级
var age = 40;
console.age(age); // 40
}
f3();
}
f2();
}
f1();
会从当前一直找到全局,全局没有就报错
预解析
预解析:在解析代码之前,变量和函数的声明会提前
console.log(num); // undefined
var num = 100 ;
// 上面等价于
var num;
console.log(num);
num = 100; // 赋值不会提前
console.log(a);
console.log(b);
var a = b = 10;
// 等价于
var a ;
console.log(a);
console.log(b);
a = b = 10;
闭包
- 闭包:一个函数A中包含了另一个函数B,函数B中可以访问函数A中的变量,一般是嵌套关系
- 闭包模式:函数式闭包,对象式闭包
- 闭包的优点和缺点:延长作用域链,缓存数据(这也是缺点)
闭包演示
// 函数式闭包:函数中有函数
function a(age){
return function(){
return age+10;
}
} // 这就是一个闭包
// 对象式闭包:函数中有对象
function f1(age){
return{
age:age+20
}
}
通过闭包缓存数据
function f1(){
var num = parseInt(Math.random()*10+1);
return num;
}
console.log(f1());
console.log(f1());
console.log(f1());
// 通过闭包实现,把每次产生的随机数都是一样的来进行缓存
function f2(){
var num = parseInt(Math.random()*10+1);
return function(){
return num;
}
}
var ff = f2();
console.log(ff());
console.log(ff());
console.log(ff());
console.log(ff());
通过闭包实现点赞
html和css就算了,上js
// 点击按钮实现点赞
function my$(tagName){
return document.getElementsByTagName(tagName);
}
// 闭包缓存数据
function getValue(){
var value = 2; // 用来记录赞的次数
return function(){
this.value="赞("+(value++)+")";
}
}
// 遍历所有的按钮
for(var i = 0; i<my$("input").length;i++){
my$('input')[i].onclick=getValue();
}
Memorization 记忆化
用处是优化递归
示例:
var fib=(function(){
var arr=[]; // 定义一个数组来缓存数据
return function(n){
if(n==0||n==1){
return n;
}else{
// 从数组中获取查询结果,如果没有重新计算
arr[n-1]=arr[n-1]||fib(n-1);
arr[n-2]=arr[n-2]||fib(n-2);
return arr[n-1]+arr[n-2];
}
}
})();
沙箱
沙箱:在虚拟的世界里模拟外面真实的世界,结果会和真实的世界的效果是一样的,但是不会对外面的世界造成影响,为了避免变量污染的一个问题,就是一个函数的自调用----jQuery中都是这样的
// 函数的自调用
(function(){
var num = 20 ; // 本身是局部变量
console.log(num);
win.num=num;// 此时这个就是一个全局变量
})(window)