Day8
Day8
1-什么是构造函数?
在 JavaScript 中,用 new 关键字来调用的函数,称为构造函数。构造函数首字母一般大写
构造函数主要解决创建对象时出现重复的问题
var p1 = { name: 'zs', age: 6, gender: '男', hobby: 'basketball' };
var p2 = { name: 'ls', age: 6, gender: '女', hobby: 'dancing' };
var p3 = { name: 'ww', age: 6, gender: '女', hobby: 'singing' };
var p4 = { name: 'zl', age: 6, gender: '男', hobby: 'football' };
构造函数
function Person(name, gender, hobby) {
this.name = name;
this.gender = gender;
this.hobby = hobby;
this.age = 6;
}
通过 new 关键字调用
var p1 = new Person('zs', '男', 'basketball');
var p2 = new Person('ls', '女', 'dancing');
var p3 = new Person('ww', '女', 'singing');
var p4 = new Person('zl', '男', 'football');
构造函数的返回值
构造函数执行过程的最后一步是默认返回 this 。言外之意,构造函数的返回值还有其它情况。下面我们就来聊聊关于构造函数返回值的问题。
(1) 没有手动添加返回值,默认返回 this
function Person1() {
this.name = 'zhangsan';
}
var p1 = new Person1();
按照上面讲的,我们复习一遍。首先,当用 new 关键字调用时,产生一个新的内存空间 #f11,并标记为 Person1 的实例;接着,函数体内部的 this 指向该内存空间 #f11;执行函数体内部的代码;由于函数体内部的this 指向该内存空间,而该内存空间又被变量 p1 所接收,所以 p1 中就会有一个 name 属性,属性值为 ‘zhangsan’。
p1: {
name: 'zhangsan'
}
(2) 手动添加一个基本数据类型的返回值,最终还是返回 this
function Person2() {
this.age = 28;
return 50;
}
var p2 = new Person2();
console.log(p2.age); // 28
p2: {
age: 28
}
如果上面是一个普通函数的调用,那么返回值就是 50。
(3) 手动添加一个复杂数据类型(对象)的返回值,最终返回该对象
直接上例子
function Person3() {
this.height = '180';
return ['a', 'b', 'c'];
}
var p3 = new Person3();
console.log(p3.height); // undefined
console.log(p3.length); // 3
console.log(p3[0]); // 'a'
再来一个例子
function Person4() {
this.gender = '男';
return { gender: '中性' };
}
var p4 = new Person4();
console.log(p4.gender); // '中性'
5构造函数首字母必须大写吗?
大小写都可以
6不用new关键字,直接运行构造函数,是否会出错?如果不会出错,那么,用new和不用new调用构造函数,有什么区别?
使用new操作符调用函数
例子:
function Person(name){
this.name = name;
this.say = function(){
return "I am " + this.name;
}
}
var person1 = new Person('nicole');
person1.say(); // "I am nicole"
用new调用构造函数,函数内部会发生如下变化:
创建一个this变量,该变量指向一个空对象。并且该对象继承函数的原型;
属性和方法被加入到this引用的对象中;
隐式返回this对象(如果没有显性返回其他对象)
用伪程序来展示上述变化:
function Person(name){
// 创建this变量,指向空对象
var this = {};
// 属性和方法被加入到this引用的对象中
this.name = name;
this.say = function(){
return "I am " + this.name;
}
// 返回this对象
return this;
}
可以看出,用new调用构造函数,最大特点为,this对象指向构造函数生成的对象,所以,person1.say()会返回字符串: “I am nicole”。
小贴士
如果指定了返回对象,那么,this对象可能被丢失。
function Person(name){
this.name = name;
this.say = function(){
return "I am " + this.name;
}
var that = {};
that.name = "It is that!";
return that;
}
var person1 = new Person('nicole');
person1.name; // "It is that!"
直接调用函数
如果直接调用函数,那么,this对象指向window,并且,不会默认返回任何对象(除非显性声明返回值)。
还是拿Person函数为例,直接调用Person函数:
var person1 = Person('nicole');
person1; // undefined
window.name; // nicole
可见,直接调用构造函数的结果,并不是我们想要的。
1. 对象字面量方式
var
per = {
name:
'zhangsan'
,
age:25,
job:
'html'
,
sayName:
function
(){
alert(
this
.name);
}
}
2、工厂模式
由于在ECMAScript中无法创建类,开发人员就发明了一种函数,用函数来封装以特定接口创建对象的细节,如下面的例子:
function
createPerson(name,age,job){
var
o =
new
Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName =
function
(){
alert(
this
.name);
}
return
o;
}
var
person1 = createPerson(
'zhang'
,30,
'java'
);
var
person2 = createPerson(
'zhao'
,25,
'php'
);
3、构造函数模式
可以使用构造函数模式将前面的例子重写如下:
function
Person(name,age,job){
this
.name= name;
this
.age = age;
this
.job = job;
this
.sayName = sayName();
}
function
sayName(){
alert(
this
.name);
}
var
person1 = createPerson(
'zhang'
,30,
'java'
);
var
person2 = createPerson(
'zhao'
,25,
'php'
);
4、原型模式
我们创建的每个函数都有一个prototype(原型)
属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。使用原型对象的好处是可以让所有的对象实例共享他所包含的属性和方法。
function
Person(){}
Person.prototype.name =
'zhang'
;
Person.prototype.age =
'22'
;
Person.prototype.job =
'html5'
;
Person.prototype.sayName =
function
(){
alert(
this
.name);
};
var
person1 =
new
Person();
var
person2 =
new
Person();
原型模式也不是没有缺点。首先,它省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。虽然这会在某种程度上带来一些不方便,但还不是原型的最大问题。原型模式的最大问题是由共享的本性所导致的。
5、组合使用构造函数模式和原型模式
组合使用构造函数模式和原型模式,是创建自定义类型的最常见方式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度的节省了内存。
function
Person(){}
Person.prototype = {
constructor:Person,
name:
'zhang'
,
age :
'22'
,
job :
'html5'
,
friends:[
'wang'
,
'li'
],
sayName :
function
(){
alert(
this
.name);
}
};
var
person1 =
new
Person();
var
person2 =
new
Person();
person1.friends.push(
'zhao'
);
alert(person1.friends);
//'wang,li,zhao'
alert(person2.friends);
//'wang,li,zhao'
alert(person1.friends === person2.friends);
//true
在上面的例子中,实例属性都是在构造函数中定义的,而由所有实例共享的属性constructor
和方法satName()
则是在原型中定义的。而修改了person1.friends
(向其中添加一个新字符串),并不会影响到person2.friends
,因为他们分别引用了不同的数组。
3-js 种实现继承的方式(重点?)
1、原型链继承
核心: 将父类的实例作为子类的原型
2、构造继承
核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
3、实例继承
核心:为父类实例添加新特性,作为子类实例返回
4、拷贝继承
5、组合继承
核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
6、寄生组合继承
核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点
4-什么是闭包?有什么作用?
什么是闭包
如果一个函数用到了它作用域外面的变量,那么这个变量和这个函数之间的环境就叫闭包
例子
var a = 5
function xx(){
console.log(a)
}
//5
作用
1.模仿块级作用域
所谓块级作用域就是指在循环中定义的变量,一旦循环结束,变量也随之销毁,它的作用范围只在这一小块
而在JavaScript中没有这样的块级作用域,由于JavaScript不会告诉你变量是否已经被声明,所以容易造成命名冲突,如果在全局环境定义的变量,就会污染全局环境,因此可以利用闭包的特性来模仿块级作用域。
function X(num) {
(function(){
for(var i = 0; i < num.length; i++){
num++
}
}).call() //声明一个函数立即调用以后,浏览器刷新页面会报错,可以用一个小括号把整段函数包起来。
console.log(i)//undefined
}
2.储存变量
闭包的另一个特点是可以保存外部函数的变量,内部函数保留了对外部函数的活动变量的引用,所以变量不会被释放。
function S(){
var a = 1
return {
function(){
renturn a
}
}
}
var d = S() // 100
3.封装私有变量
我们可以把函数当作一个范围,函数内部的变量就是私有变量,在外部无法引用,但是我们可以通过闭包的特点来访问私有变量。
var person = function(){
//变量作用域为函数内部,外部无法访问
var name = "default";
return {
getName : function(){
return name;
},
setName : function(newName){
name = newName;
}
}
}();
print(person.name);//直接访问,结果为undefined
print(person.getName()); // default
person.setName("abruzzi");
print(person.getName()); // abruzzi
5-什么是预解析?
在当前作用域中,JavaScript代码执行之前,浏览器首先会默认的把所有带var和function声明的变量进行提前的声明或者定义。
声明和定义
var
num = 24;
- 声明:
var num; 告诉浏览器在全局作用域中有一个num变量了,如果一个变量只是声明了,但是没有赋值,默认值是undefined。
- 定义:
num = 12;
定义就是给变量进行赋值。
var声明的变量和function声明的函数在预解析的区别
var声明的变量在预解析的时候只是提前的声明,
function声明的函数在预解析的时候会提前声明并且会同时定义。也就是说var声明的变量和function声明的函数的区别是在声明的同时有没同时进行定义。
预解析只发生在当前的作用域下
程序最开始的时候,只对window下的变量和函数进行预解析,只有函数执行的时候才会对函数中的变量很函数进行预解析。
深入理解JavaScript中的预解析