Appearance
前言
浅入JS基础。本篇从工作、学习总结归纳JavaScript,列举以下技巧。
- 文章涉及:
- 变量声明
- typeof
- 数据类型
- 理解函数
- 全局对象(GO)
- 闭包
- 作用域
- 作用域链
- 立即执行函数与函数表达式
- 运算符
- 对象
- new关键字
- 包装类
- 原型
- 原型链
- Object.create()
- call
- apply
- 继承
- 命名空间
- 链式调用
- 对象枚举
- this
- 数组
- 类数组
- try catch
var
- 即任何变量,如果未经声明就使用,此变量就为全局变量所拥有。
// 没有使用var,在全局声明变量num,变量num属于window
num = 10;
console.log(window.num) // 10
// 没有使用var,在全局声明变量num,变量num属于window
num = 10;
console.log(window.num) // 10
- 一切声明的全局变量,全是window的属性。
// 使用var在全局声明变量num,变量num就属于window。
var num = 10;
console.log(window.num) // 10
// 使用var在全局声明变量num,变量num就属于window。
var num = 10;
console.log(window.num) // 10
注意
一旦经历var操作,所得出的属性属于window,这种属性叫做不可配置属性,不可配置属性不能delete。
var num = 1;
delete window.num // false
var num = 1;
delete window.num // false
- 全局定义相同变量,后面变量能把前面变量覆盖。
var num = 1; // 首次定义
...
var num = 10; // 后面再次定义
console.log(num) // 10
var num = 1; // 首次定义
...
var num = 10; // 后面再次定义
console.log(num) // 10
typeof
返回数据类型字符串表达。
var num = 1;
typeof num // 'number'
var x = 'app';
typeof x // 'string'
...
var num = 1;
typeof num // 'number'
var x = 'app';
typeof x // 'string'
...
typeof 存在比较特殊地方。
typeof null // 'object'
typeof Array // 'object'
typeof null // 'object'
typeof Array // 'object'
通过typeof能返回:number/string/boolean/object/function类型。
能判断:undefined/number/boolean/function
不能判断: null/object/Array。
对于NaN,返回number,NaN是not a number的缩写。
数据类型
包括基本数据类型与引用数据类型。
基本数据类型(栈stack) 包括:number/string/boolean/null/undefined。基本数据类型访问顺序按值访问,由高向低分配,占内存最大是8MB,其中string是特殊的栈,由程序员分配。
引用类型(堆heap) 包括:function/object/Array。引用类型在栈内存中保存的是对象在堆内存中的引用地址(指针),向高分配,系统自由分配。
空间分配方式: 栈:由操作系统自由分配释放。 堆:一般由程序员分配释放。
数据结构: 栈:先进后出的模式。 堆:可以看成一颗树。
函数
预编译发生在函数执行前一刻。 预编译过程:
- 创建AO对象:Activetion Object
- 找形参和变量声明,将变量和形参名作为AO属性名,值为:undefined
- 将实参值和形参统一
- 在函数体里面找函数声明,值赋予函数体
代码片段:
function fn(x, y) {
z = 3;
function bar() {}
}
fn(1, 2);
function fn(x, y) {
z = 3;
function bar() {}
}
fn(1, 2);
- 首次创建Activetion Object
x: undefined
y: undefined
z: undefined
bar: undefined
arguments: undefined
x: undefined
y: undefined
z: undefined
bar: undefined
arguments: undefined
- 然后实参值与形参统一
x: 1
y: 2
z: 3
bar: undefined
arguments: <1, 2>
x: 1
y: 2
z: 3
bar: undefined
arguments: <1, 2>
- 函数体中如果有函数声明,值赋予函数体
x: 1
y: 2
z: 3
bar: <function>
arguments: <1, 2>
x: 1
y: 2
z: 3
bar: <function>
arguments: <1, 2>
Global Object(GO)
简称全局对象,与widow关系是全等(Global Object === window)。
代码片段
function a() {
function b() {
function c() {}
c();
}
b();
}
a();
function a() {
function b() {
function c() {}
c();
}
b();
}
a();
通过上述代码片段,函数a在全局定义,同时创建自己的scope属性,指向它父函数作用域链Global Object;函数a执行,scope属性指向自身Activetion Object;通过全局Global Object返回,拿到函数a结果。
a defined a.[[scope]] --> 0: GO
a doing a.[[scope]] --> 0: a AO
--> 1: GO
a defined a.[[scope]] --> 0: GO
a doing a.[[scope]] --> 0: a AO
--> 1: GO
函数b定义,是在函数a执行结果上定义,同时创建自己的scope;函数b执行,scope属性指向自身Activetion Object,经过函数a的Activetion Object,传递给全局Global Object返回,拿到函数b结果。
// ...省略函数a
b defined b.[[scope]] --> 0: aAO
--> 1: GO
b doing b.[[scope]] --> 0: bAO
--> 1: aAO
--> 2: GO
// ...省略函数a
b defined b.[[scope]] --> 0: aAO
--> 1: GO
b doing b.[[scope]] --> 0: bAO
--> 1: aAO
--> 2: GO
函数c定义,是在函数b执行结果上定义,同时创建自己的scope;函数c执行,scope属性指向自身Activetion Object,经过函数b的Activetion Object,经过函数a的Activetion Object,通过全局Global Object返回,拿到函数c结果。
// ...省略函数a
// ...省略函数b
c defined c.[[scope]] --> 0: bAO
--> 1: aAO
--> 2: GO
c doing c.[[scope]] --> 0: cAO
--> 1: bAO
--> 2: aAO
--> 3: GO
// ...省略函数a
// ...省略函数b
c defined c.[[scope]] --> 0: bAO
--> 1: aAO
--> 2: GO
c doing c.[[scope]] --> 0: cAO
--> 1: bAO
--> 2: aAO
--> 3: GO
通过上述分析可以看到,函数是定义在它父劳动成果上。
闭包
内部的函数被保存到外部,将生成闭包。闭包会导致原作用域链不释放,造成内存泄露。通俗理解里面的函数比外面的函数活着还长。
规则:
- 一定是嵌套函数
- 内层函数一定操作了外层函数的局部变量
- 外层函数一定将内层函数返回外部, 并且被全局变量保存
代码片段:
function fn() {
function bar() {
var y = 2;
console.log(x);
}
var x = 1;
return bar;
}
var f = fn();
f();
function fn() {
function bar() {
var y = 2;
console.log(x);
}
var x = 1;
return bar;
}
var f = fn();
f();
闭包运用
function fn() {
var arr = [];
for(var i=0; i<10; i++) {
arr[i] = function() {
console.log(i) // 输出10次10
}
}
return arr;
}
var myArr = fn();
for(var j=0; j<10; j++) {
myArr[i]();
}
function fn() {
var arr = [];
for(var i=0; i<10; i++) {
arr[i] = function() {
console.log(i) // 输出10次10
}
}
return arr;
}
var myArr = fn();
for(var j=0; j<10; j++) {
myArr[i]();
}
打印输出10次10,是因为声明i变量是全局变量。 for语句可以这样
for(var i=0; i<10; i++) {
// ...
}
// 修改下面是等效
var i = 0;
for(; i<10; i++) {
// ...
}
for(var i=0; i<10; i++) {
// ...
}
// 修改下面是等效
var i = 0;
for(; i<10; i++) {
// ...
}
对于上面代码打印0-10,可通过闭包修改,设置局部变量
function fn() {
var arr = [];
for(var i=0; i<10; i++) {
(function(j) { // 形参
arr[j] = function() {
console.log(j) // 输出0-10
}
})(i)
}
return arr;
}
var myArr = fn();
for(var j=0; j<10; j++) {
myArr[i]();
}
function fn() {
var arr = [];
for(var i=0; i<10; i++) {
(function(j) { // 形参
arr[j] = function() {
console.log(j) // 输出0-10
}
})(i)
}
return arr;
}
var myArr = fn();
for(var j=0; j<10; j++) {
myArr[i]();
}
作用域
ES5中提出全局作用域和函数作用域。
全局作用域忘记,可以往上翻翻开篇提到的部分;这里补充函数作用域,函数作用域是在函数内部的变量。
代码片段:
function fn() {
var num = 10;
console.log(num); // 10
function bar() {
console.log(num) // 10
}
bar();
}
console.log(num) // 抛出 error
fn();
function fn() {
var num = 10;
console.log(num); // 10
function bar() {
console.log(num) // 10
}
bar();
}
console.log(num) // 抛出 error
fn();
通过上述结果可以理解,函数会创建自己的作用域'{}',并且作用域分层,函数内层域能访问函数外层域,反之不行。 值得注意的是在if/for/switch/while语句后面的“{}”,不会创建自己的作用域,定义的变量保存在已经定义的作用域中。
if(true) {
var num = 10;
}
console.log(num) // 10
if(true) {
var num = 10;
}
console.log(num) // 10
变量num虽然在'{ }'中,但是if不会创建自己的作用域,变量num相当于定义在全局。
作用域链
链可以理解为一层一层的向上寻找。在作用域中呢? 看看相关代码:
var num = 10;
function fn() {
console.log(num);
}
fn(); // 输出结果 10
var num = 10;
function fn() {
console.log(num);
}
fn(); // 输出结果 10
函数fn创建自己的域,在函数内层域中寻找变量num,发现当前域没有定义变量num,继续向函数外层域寻找变量,最后在外层域中找到变量num,输出10。
目前说明作用域链是一层一层向上寻找,直到找到所需变量或者最顶层也没找到就停止寻找并返回结果。
那么能不能理解成一层一层向函数父域中寻找呢?看下面相关代码:
var num = 10;
function fn() {
console.log(num);
}
function bar() {
var num = 20;
(function() {
fn();
})()
}
bar(); // 输出结果10
var num = 10;
function fn() {
console.log(num);
}
function bar() {
var num = 20;
(function() {
fn();
})()
}
bar(); // 输出结果10
上述代码输出结果10。通过这个可以分析函数会在创建函数的这个域中寻找值,不是调用这个函数域,说向父域中不准确。
立即执行函数
可以理解成执行完就释放。 代码片段:
(function(x, y) { // x, y是形参
console.log(x + y);
})(1, 2) // 1, 2 是实参
(function(x, y) { // x, y是形参
console.log(x + y);
})(1, 2) // 1, 2 是实参
可以定义一个变量接收。
var num = (function(x, y) {
var z = x + y;
return z;
})(1, 2)
console.log(num) // 3
var num = (function(x, y) {
var z = x + y;
return z;
})(1, 2)
console.log(num) // 3
注意:只有表达式才能被执行符号“( )”执行。
var fn = function() {
console.log('hello');
}()
// 首次调用输出 hello
// 再次调用console.log(fn) 输出 undefined
var fn = function() {
console.log('hello');
}()
// 首次调用输出 hello
// 再次调用console.log(fn) 输出 undefined
函数调用。
function fn() {
console.log(this);
}
fn();
function fn() {
console.log(this);
}
fn();
函数使用call方法调用。
function foo() {
console.log(this);
}
foo.call();
function foo() {
console.log(this);
}
foo.call();
这两个函数打印值是相等。
函数后面添加运算符"( )",调用函数什么也不输出。
function fn(x, y) {
console.log(x, y); // 无输出
}(1, 2)
function fn(x, y) {
console.log(x, y); // 无输出
}(1, 2)
函数前面支持正负非符号。
+|-|! function fn() {
console.log('hello'); // hello
}
fn();
+|-|! function fn() {
console.log('hello'); // hello
}
fn();
“( )”运算符。
var num = (1 - 1, 1 + 1);
console.log(num) // 2 计算前面结果,再计算后面运算结果,返回后面结果
var num = (1 - 1, 1 + 1);
console.log(num) // 2 计算前面结果,再计算后面运算结果,返回后面结果
if语句有函数。
var x = 1;
if(function() {}) {
x += typeof f;
}
console.log(x) // 1undefined 此时函数转成表达式,在执行
var x = 1;
if(function() {}) {
x += typeof f;
}
console.log(x) // 1undefined 此时函数转成表达式,在执行
对象模型
由属性和属性对应值组成。
var obj = {
// 属性:值
name: 'sunny',
age: 18,
say: function() {
console.log('hello')
}
}
var obj = {
// 属性:值
name: 'sunny',
age: 18,
say: function() {
console.log('hello')
}
}
可以操作对象:
- 访问属性:obj.name
- 删除属性:delete obj.age
- 修改属性:obj.age = 28
- 添加属性:obj.sex = 'male'
创建对象
- 使用字面量创建对象
var obj = {}
var obj = {}
- 使用系统自带的构造函数
var obj = new Object()
var obj = new Object()
- 自定义
function Person(){}
var person = new Person();
function Person(){}
var person = new Person();
注意: 构造函数和普通函数结构上没区别,存在new才会返回一个对象。 构造函数命名规则,大驼峰命名
new关键字
在构造函数前面添加new关键字,执行以下操作:
- 在函数体最前面隐式的加上this =
- 执行 this.xxx = xxx
- 隐式的返回 this 相关代码:
function Student(name, age) {
// 隐式添加 this = {
// name: name,
// ...
// }
this.name = name;
this.age = age;
this.sex = 'male';
// 隐式添加 return this
}
var student = new Student('sunny', 18);
function Student(name, age) {
// 隐式添加 this = {
// name: name,
// ...
// }
this.name = name;
this.age = age;
this.sex = 'male';
// 隐式添加 return this
}
var student = new Student('sunny', 18);
构造函数显示return空对象或者原始值;输出的结果不一样。
return对象
function Student(name, age) {
this.name = name;
this.age = age;
this.sex = 'male';
return {}; // 返回一个空对象
}
var student = new Student('sunny', 18);
console.log(student) // {}
function Student(name, age) {
this.name = name;
this.age = age;
this.sex = 'male';
return {}; // 返回一个空对象
}
var student = new Student('sunny', 18);
console.log(student) // {}
return原始值
function Student(name, age) {
this.name = name;
this.age = age;
this.sex = 'male';
// 或者return 原始值; 相当于return this,返回一个对象
return 123;
}
var student = new Student('sunny', 18);
console.log(student) // {name: "sunny", age: 18, sex: "male"}
function Student(name, age) {
this.name = name;
this.age = age;
this.sex = 'male';
// 或者return 原始值; 相当于return this,返回一个对象
return 123;
}
var student = new Student('sunny', 18);
console.log(student) // {name: "sunny", age: 18, sex: "male"}
自定义对象好处
function Car(color) { // color参数
this.color = color; // color 接受参数
this.health = 100;
this.run = function() {
this.health --;
}
}
// car变量写成Car走预编译环节,进行覆盖
var car1 = new Car('red'); // {color: "red", health: 100, run: ƒ}
var car2 = new Car('green');// {color: "green", health: 100, run: ƒ}
function Car(color) { // color参数
this.color = color; // color 接受参数
this.health = 100;
this.run = function() {
this.health --;
}
}
// car变量写成Car走预编译环节,进行覆盖
var car1 = new Car('red'); // {color: "red", health: 100, run: ƒ}
var car2 = new Car('green');// {color: "green", health: 100, run: ƒ}
通过上述代码结果可以看出car1与car2属性相互各自独立互不影响。
包装类
原始值没有属性和方法。
// eg
var num = 1;
var num = new Number(num);
num.a = 'a';
console.log(num) // Number {1, a: "a"}
console.log(num * 2) // 2 能计算,又返回原类型
// eg
var num = 1;
var num = new Number(num);
num.a = 'a';
console.log(num) // Number {1, a: "a"}
console.log(num * 2) // 2 能计算,又返回原类型
String添加属性:
var str = 'a';
var str = new String(str);
str.a = 'a';
console.log(str.a) // a
var str = 'a';
var str = new String(str);
str.a = 'a';
console.log(str.a) // a
Number添加属性:
// eg:
var num = 4;
num.len = 3;
console.log(num.len) // undefined
// eg:
var num = 4;
num.len = 3;
console.log(num.len) // undefined
执行过程:
- 执行num.len时,发生new Number(num).len = 3新建一个数字对象,该对象len赋值为3,然后delete销毁。
- 再次访问重新创建数字对象,添加一个len属性,与上一步new Number不同。
- 输出结果undefined。
boolean添加new。
var bol = new Boolean('true');
// eg
var str = 'abc';
str +=1;
var test = typeof str;
if(test.length == 6) {
test.sign = 'typdof返回的结果可能是string';
}
console.log(test.sign);
var bol = new Boolean('true');
// eg
var str = 'abc';
str +=1;
var test = typeof str;
if(test.length == 6) {
test.sign = 'typdof返回的结果可能是string';
}
console.log(test.sign);
在if里面创建 new String('test').sign = 'typdof返回的结果可能是string'。 在最外层再次访问,重新创建new String('test').sign。 最终输出结果 undefined。
解析片段:
var str = 'abc';
str +=1;
var test = typeof str;
if(test.length == 6) {
// 创建new String('test').sign = 'typdof返回的结果可能是string'
test.sign = 'typdof返回的结果可能是string';
}
// 再次访问重新创建new String('test').sign
console.log(test.sign);
var str = 'abc';
str +=1;
var test = typeof str;
if(test.length == 6) {
// 创建new String('test').sign = 'typdof返回的结果可能是string'
test.sign = 'typdof返回的结果可能是string';
}
// 再次访问重新创建new String('test').sign
console.log(test.sign);
注:undefined / null 没有String。
原型
是function对象的一个属性,它定义了构造函数制造出的对象的公共祖先。通过该构造函数产生的对象,可以继承该原型的属性和方法,原型也是对象。
相关代码:
// Person.prototype 看做原型
// Person.prototype = {} 看做祖先
Person.prototype.name = 'sunny';
function Person() {}
var p = new Person();
// p.__proto__.constructor 指向 functon Person(){}
// p.__proto__ === Person.prototype
console.log(p.name) // sunny
// Person.prototype 看做原型
// Person.prototype = {} 看做祖先
Person.prototype.name = 'sunny';
function Person() {}
var p = new Person();
// p.__proto__.constructor 指向 functon Person(){}
// p.__proto__ === Person.prototype
console.log(p.name) // sunny
通过constructor更改指向。
function Person() {}
function Car() {}
Car.prototype = {
// constructor更改了指向
constructor: Person;
}
var car = new Car();
function Person() {}
function Car() {}
Car.prototype = {
// constructor更改了指向
constructor: Person;
}
var car = new Car();
new过程构造函数内部做了什么。
- 内部隐式添加this对象;
- 改变this执向(指向原构造函数原型);
- 返回一个新对象。
// eg
Person.prototype.name = 'sunny';
function Person() {
// var this = {
// __proto__: Person.prototype
// }
}
var person = new Person();
var obj = {
name: 'cherry'
}
person.__proto__ = obj;
console.log(person.name) // cherry
// eg
Person.prototype.name = 'sunny';
function Person() {
// var this = {
// __proto__: Person.prototype
// }
}
var person = new Person();
var obj = {
name: 'cherry'
}
person.__proto__ = obj;
console.log(person.name) // cherry
prototype在new前
Person.prototype.name = 'sunny';
function Person() {}
Person.prototype.name = 'cherry';
var p = new Person();
console.log(p.name) // cherry
Person.prototype.name = 'sunny';
function Person() {}
Person.prototype.name = 'cherry';
var p = new Person();
console.log(p.name) // cherry
通过上述代码看出prototype有提升作用,后面定义能把之前定义覆盖。
prototype在new后
Person.prototype.name = 'sunny';
function Person() {}
var p = new Person();
Person.prototype.name = 'cherry';
console.log(p.name) // cherry
Person.prototype.name = 'sunny';
function Person() {}
var p = new Person();
Person.prototype.name = 'cherry';
console.log(p.name) // cherry
可以看出prototype定义在new之后,值没有变化;prototype有提升作用,后面重新定义能把之前定义覆盖。
prototype对象形式 写法 在new前
Person.prototype.name = 'sunny';
function Person() {}
Person.prototype = {
name: 'cherry'
}
var p = new Person();
console.log(p.name) // cherry
Person.prototype.name = 'sunny';
function Person() {}
Person.prototype = {
name: 'cherry'
}
var p = new Person();
console.log(p.name) // cherry
通过上述看出Person原型给修改了,没有new生成就改变了。
在new之后
Person.prototype.name = 'sunny';
function Person() {}
var p = new Person();
person.prototype = {
name: 'cherry'
}
console.log(p.name) // sunny
Person.prototype.name = 'sunny';
function Person() {}
var p = new Person();
person.prototype = {
name: 'cherry'
}
console.log(p.name) // sunny
Person函数里面__proto__没有修改,只是在原基础上修改Person.prototype属性。
原型链
Object.prototype是所有对象的最终原型。
// eg
Person.prototype.__proto__ = Object.prototype
function Person() {}
Person.prototype.name = 'sunny';
var p = new Person()
// eg
Person.prototype.__proto__ = Object.prototype
function Person() {}
Person.prototype.name = 'sunny';
var p = new Person()
引用值可以自己操作自己 (原型链继承模式)
function Father() {
this.name = 'sunny';
this.age = 46;
}
var f = new Father();
Son.prototype = f;
function Son() {}
var s = new Son();
s.age++
console.log(f.age) // 46
function Father() {
this.name = 'sunny';
this.age = 46;
}
var f = new Father();
Son.prototype = f;
function Son() {}
var s = new Son();
s.age++
console.log(f.age) // 46
揭秘原型与原型链取值
相关代码:
Person.prototype = {
name: 'sunny',
say: function() {
console.log(this.name)
}
}
function Person() {}
var p = new Person();
console.log(p.say) // sunny
Person.prototype = {
name: 'sunny',
say: function() {
console.log(this.name)
}
}
function Person() {}
var p = new Person();
console.log(p.say) // sunny
修改上面代码:
Person.prototype = {
name: 'sunny',
say: function() {
console.log(this.name)
}
}
function Person() {
this.name = 'cherry';
}
var p = new Person();
console.log(p.name) // cherry
Person.prototype = {
name: 'sunny',
say: function() {
console.log(this.name)
}
}
function Person() {
this.name = 'cherry';
}
var p = new Person();
console.log(p.name) // cherry
通过上述代码片段发现实例对象就近取值,如果没有找到值,沿着原型链继续往上寻找。
Object.create
语法:Object.create(原型)。
var obj = {
name: 'sunny'
}
var obj1 = Object.create(obj);
var obj = {
name: 'sunny'
}
var obj1 = Object.create(obj);
Person.prototype.name = 'sunny';
function Person() {}
var p = Object.create(Person.prototype);
Person.prototype.name = 'sunny';
function Person() {}
var p = Object.create(Person.prototype);
绝大多数对象最终都会继承自Object.create,null除外。
call (继承)
function Person(name, age) {
this.name = name;
this.age = age;
}
function Son(name, age, sex) {
Person.call(this, name, age);
this.sex = sex;
}
var son = new Son('sunny', 18, 'male');
console.log(son) // {name: "sunny", age: 18, sex: "male"}
function Person(name, age) {
this.name = name;
this.age = age;
}
function Son(name, age, sex) {
Person.call(this, name, age);
this.sex = sex;
}
var son = new Son('sunny', 18, 'male');
console.log(son) // {name: "sunny", age: 18, sex: "male"}
上述代码看出:能共用父类属性,支持传参。
apply (继承)
function Person() {
this.name = name;
this.age = age;
}
function Son(name, age, sex) {
Person.apply(this, [name, age]);
this.sex = sex;
}
var son = new Son('sunny', 18, 'male')
console.log(son) // {name: "sunny", age: 18, sex:"male"}
function Person() {
this.name = name;
this.age = age;
}
function Son(name, age, sex) {
Person.apply(this, [name, age]);
this.sex = sex;
}
var son = new Son('sunny', 18, 'male')
console.log(son) // {name: "sunny", age: 18, sex:"male"}
上述代码看出:能共用父类属性,支持传参,参数是数组形式。 对比可以总结归纳:call与apply传参序列不同,都能改变this指向。 call或者apply 参数为null/undefined时,执行JS全局对象浏览器中的window。
function fn() {
foo.apply(null, arguments);
}
function foo() {
console.log(arguments)
}
foo(1, 2, 3) // 1, 2, 3
function fn() {
foo.apply(null, arguments);
}
function foo() {
console.log(arguments)
}
foo(1, 2, 3) // 1, 2, 3
继承
- 原型链继承,结合子类原型与父类实例
// 代码片段
Person.prototype.sleep = function() {}
function Person(name, age) {
this.name = name;
this.age = age;
}
function Son(sex) {
this.sex = sex;
}
Son.prototype = new Person(); // 子类的原型为父类实例对象
var son = New Son('male');
console.log(son)
// 代码片段
Person.prototype.sleep = function() {}
function Person(name, age) {
this.name = name;
this.age = age;
}
function Son(sex) {
this.sex = sex;
}
Son.prototype = new Person(); // 子类的原型为父类实例对象
var son = New Son('male');
console.log(son)
如果在子类原型添加方法,能继承吗? 修改代码:
在new Person之前:
Person.prototype.sleep = function() {}
function Person(name, age) {
this.name = name;
this.age = age;
}
function Son(sex) {
this.sex = sex;
}
Son.prototype.eat = function() {} // 在这里...
Son.prototype = new Person();
var son = new Son('male');
console.log(son)
Person.prototype.sleep = function() {}
function Person(name, age) {
this.name = name;
this.age = age;
}
function Son(sex) {
this.sex = sex;
}
Son.prototype.eat = function() {} // 在这里...
Son.prototype = new Person();
var son = new Son('male');
console.log(son)
在new Person()之后:
Person.prototype.sleep = function() {}
function Person(name, age) {
this.name = name;
this.age = age;
}
function Son(sex) {
this.sex = sex;
}
Son.prototype = new Person();
SOn.prototype.eat = function() {}; // 在这里...
var son = new Son('male');
console.log(son)
Person.prototype.sleep = function() {}
function Person(name, age) {
this.name = name;
this.age = age;
}
function Son(sex) {
this.sex = sex;
}
Son.prototype = new Person();
SOn.prototype.eat = function() {}; // 在这里...
var son = new Son('male');
console.log(son)
上述代码在new Person之前子类原型添加方法失败,原因是更改了原型的指向。 该继承缺点:无法实现多继承; 在子类原型添加方法和属性,需在Son.prototype=new Person之后。
call/apply构造函数继承,如果忘记可以查看上面call/apply介绍。 这里补充父类上原型属性和方法能否被call/apply继承。 相关代码:
Person.prototype.sleep = function() {}
function Person(name, age) {
this.name = name;
this.age = age;
}
Son.prototype.eat = function() {}
function Son(name, age, sex) {
Person.call(this, name, age); // ...能不能继承sleep函数
this.sex = sex;
}
var son = new Son('sunny', 18, 'male');
console.log(son);
Person.prototype.sleep = function() {}
function Person(name, age) {
this.name = name;
this.age = age;
}
Son.prototype.eat = function() {}
function Son(name, age, sex) {
Person.call(this, name, age); // ...能不能继承sleep函数
this.sex = sex;
}
var son = new Son('sunny', 18, 'male');
console.log(son);
实例son输出说明,call方法不能继承父类原型上的方法和属性;父类构造函数调用两次:一次在子类构造函数内调用,一次在创建子类原型调用。
原型链与构造函数组合 相关代码:
Person.prototype.sleep = function() {}
function Person(name, age) {
this.name = name;
this.age = age;
}
function Son(name, age, sex){
Person.call(this, name, age);
this.sex = sex;
}
Son.prototype = new Person(); // 第一步
Son.prototype.constructor = Son; // 第二步
Son.prototype.eat = function() {};
var son = new Son('sunny', 18, 'male');
console.log(son)
Person.prototype.sleep = function() {}
function Person(name, age) {
this.name = name;
this.age = age;
}
function Son(name, age, sex){
Person.call(this, name, age);
this.sex = sex;
}
Son.prototype = new Person(); // 第一步
Son.prototype.constructor = Son; // 第二步
Son.prototype.eat = function() {};
var son = new Son('sunny', 18, 'male');
console.log(son)
实例son输出说明,子类可以继承父类属性以及父类原型属性和方法;缺点父类的构造函数生成两份实例。
组合继承 通过子类原型和父类原型指向同一个对象。
Person.prototype.sleep = function() {}
function Person(name, age) {
this.name = name;
this.age = age;
}
function Son(name, age, sex) {
Person.call(this, name, age);
this.sex = sex;
}
Son.prototype = Person.prototype; // ...在这里
Son.prototype.eat = function() {};
var son = new Son('sunny', 18, 'male');
console.log(son)
Person.prototype.sleep = function() {}
function Person(name, age) {
this.name = name;
this.age = age;
}
function Son(name, age, sex) {
Person.call(this, name, age);
this.sex = sex;
}
Son.prototype = Person.prototype; // ...在这里
Son.prototype.eat = function() {};
var son = new Son('sunny', 18, 'male');
console.log(son)
代码son输出说明,子类可以继承父类原型上方法和属性。
组合继承优化
Person.prototype.sleep = function() {}
function Person(name, age) {
this.name = name;
this.age = age;
}
function Son(name, age, sex) {
Person.call(this, name, age);
this.sex = sex;
}
Son.prototype = Object.create(Person.prototype);
Son.prototype.constructor = Son;
Son.prototype.eat = function() {};
var son = new Son('sunny', 18, 'male');
console.log(son);
Person.prototype.sleep = function() {}
function Person(name, age) {
this.name = name;
this.age = age;
}
function Son(name, age, sex) {
Person.call(this, name, age);
this.sex = sex;
}
Son.prototype = Object.create(Person.prototype);
Son.prototype.constructor = Son;
Son.prototype.eat = function() {};
var son = new Son('sunny', 18, 'male');
console.log(son);
实例son继承借助创建对象,子类继承父类所有属性和方法。
函数共享原型 对于指向同一个对象继承。 相关代码:
Person.prototype.name = 'sunny';
function Person() {}
function Son() {}
Son.prototype = Person.prototype;
var son = new Son();
console.log(son.name); // sunny
Person.prototype.name = 'sunny';
function Person() {}
function Son() {}
Son.prototype = Person.prototype;
var son = new Son();
console.log(son.name); // sunny
可以将代码整合优化,编写一个公共函数共享原型,一定要在new之前使用。
Person.prototype.name = 'sunny';
function Person() {}
function Son() {}
// 编写inherit函数共享原型
function inherit(target, origin) {
target.prototype = origin.prototype;
}
inherit(Son, Person); // new之前先继承后使用
var son = new Son();
console.log(son.name); // sunny
Person.prototype.name = 'sunny';
function Person() {}
function Son() {}
// 编写inherit函数共享原型
function inherit(target, origin) {
target.prototype = origin.prototype;
}
inherit(Son, Person); // new之前先继承后使用
var son = new Son();
console.log(son.name); // sunny
继续对上面代码进行修改,在函数inherit内部使用一个构造函数中转proto指向。
Person.prototype.name = 'sunny';
function Person() {}
function Son() {};
function inherit(target, origin) {
function F(){
F.prototype = origin.prototype;
target.prototype = new F();
target.prototype.constructor = target;
target.prototype.uber = origin.prototype;
}
}
inherit(Son, Person);
var person = new Person();
var son = new Son();
console.log(son.name) // undefined
console.log(person.name) // sunny
// Son原型添加属性,不影响原构造函数与原 原型
Son.prototype.sex = 'male';
console.log(son.sex) // male
console.log(Person.sex) // undefined
Person.prototype.name = 'sunny';
function Person() {}
function Son() {};
function inherit(target, origin) {
function F(){
F.prototype = origin.prototype;
target.prototype = new F();
target.prototype.constructor = target;
target.prototype.uber = origin.prototype;
}
}
inherit(Son, Person);
var person = new Person();
var son = new Son();
console.log(son.name) // undefined
console.log(person.name) // sunny
// Son原型添加属性,不影响原构造函数与原 原型
Son.prototype.sex = 'male';
console.log(son.sex) // male
console.log(Person.sex) // undefined
上述代码块中沿着目标实例son.__proto__找到构造函数F.prototype,指给原构造函数Person.prototype,此方法称为圣杯模式继承。
抽离核心代码:
var inherit = (function() {
var F = function() {}
return function(target, origin) {
F.prototype = origin.prototype;
target.prototype = new F();
target.prototype.constructor = target;
target.prototype.uber = origin.prototype;
}
})()
var inherit = (function() {
var F = function() {}
return function(target, origin) {
F.prototype = origin.prototype;
target.prototype = new F();
target.prototype.constructor = target;
target.prototype.uber = origin.prototype;
}
})()
命名空间
管理变量,防止污染全局,适用于模块化开发。 之前开发中使用对象形式。
var obj = {
name: 'sunny',
sayName: function() {
console.log(this.name)
}
}
obj.sayName(); // 打印结果: sunny
var obj = {
name: 'sunny',
sayName: function() {
console.log(this.name)
}
}
obj.sayName(); // 打印结果: sunny
有了立即执行函数结合闭包,演变出以下改进。
var name = 'sunny';
var init = (function(){
var name = 'cherry';
function sayName() {
console.log(name); // cherry
}
return function() {
sayName();
}
})()
init();
var name = 'sunny';
var init = (function(){
var name = 'cherry';
function sayName() {
console.log(name); // cherry
}
return function() {
sayName();
}
})()
init();
上述代码使用闭包形式通过私有属性执行。
链式调用
模仿jQuery链式调用。
obj.eat().drink.slee();
obj.eat().drink.slee();
浏览器控制台输出函数结果,会默认值返回undefined。利用对象方法中this指向当前对象,模仿jQuery链式调用,默认返回this即可。
var obj = {
eat: function() {
console.log('eat');
return this;
},
drink: function() {
console.log('drink');
return this;
},
sleep: function() {
console.log('sleep');
return this;
}
}
obj.eat().drink().sleep(); // eat, drink, sleep
var obj = {
eat: function() {
console.log('eat');
return this;
},
drink: function() {
console.log('drink');
return this;
},
sleep: function() {
console.log('sleep');
return this;
}
}
obj.eat().drink().sleep(); // eat, drink, sleep
对象枚举
打印对象属性对应的值一般使用for...in 。
var obj = {
name: 'sunny',
age: 18
}
for(var prop in obj) {
console.log(obj.prop); // undefined
}
var obj = {
name: 'sunny',
age: 18
}
for(var prop in obj) {
console.log(obj.prop); // undefined
}
在接收对象值应该写成这样obj[prop],因为在对象内部会隐式转换成obj.prop。
hasOwnProperty:检测该属性在该对象中是否存在。
var obj = {
name: 'sunny',
age: 18,
__proto__: {
lastname: 'cherry',
__proto__: Object.prototype
}
}
for(var prop in obj) {
if(obj.hasOwnProperty(prop)) {
console.log(obj[prop]) // sunny, 18, cherry ...
}
}
var obj = {
name: 'sunny',
age: 18,
__proto__: {
lastname: 'cherry',
__proto__: Object.prototype
}
}
for(var prop in obj) {
if(obj.hasOwnProperty(prop)) {
console.log(obj[prop]) // sunny, 18, cherry ...
}
}
- 只有对象能用hasOwnProperty。
- 是对象自己的属性返回true,反之false。
- 能返回原型及原型链上的属性,一旦延伸到原型链顶端不会找到该属性,返回false(系统自带的返回true,自己设置的返回true)。
in:能不能访问对象属性,包括原型。
var obj = {
name: 'sunny',
age: 18,
__proto__:{
lastname: 'cherry',
__proto__: Object.prototype
}
}
console.log('name' in obj) // true
var obj = {
name: 'sunny',
age: 18,
__proto__:{
lastname: 'cherry',
__proto__: Object.prototype
}
}
console.log('name' in obj) // true
instanceof 官方解释: A instanceof B,A对象是不是B构造函数构造出来的。 个人理解换种说法:A对象的原型上有没有B的原型。 相关代码:
function Person() {}
var person = new Person();
var obj = {};
person instanceof Person // true
person instanceof Object // true
[] instanceof Object // true
[] instanceof Array // true
obj instanceof Person // false
obj instanceof Array // false
function Person() {}
var person = new Person();
var obj = {};
person instanceof Person // true
person instanceof Object // true
[] instanceof Object // true
[] instanceof Array // true
obj instanceof Person // false
obj instanceof Array // false
通过上述代码发现数组与对象在instanceof中返回值,可以使用instanceof区分数组与对象。
数组与对象的区分还可以使用constructor和toString.call()。
var arr = [];
var obj = {};
console.log(arr.constructor) // ƒ Array() { [native code] }
console.log(obj.constructor) // ƒ Object() { [native code] }
// toString.call() 在Object原型上属性
console.log(Object.prototype.toString.call(arr)) // [object Array]
console.log(Object.prototype.toString.call(obj)) // [object Object]
var arr = [];
var obj = {};
console.log(arr.constructor) // ƒ Array() { [native code] }
console.log(obj.constructor) // ƒ Object() { [native code] }
// toString.call() 在Object原型上属性
console.log(Object.prototype.toString.call(arr)) // [object Array]
console.log(Object.prototype.toString.call(obj)) // [object Object]
toString.call()可以实现深度克隆。
var obj = {
name: 'sunny',
age: 18,
car: ['visa', 'BMW'],
wife: {
name: 'cherry',
son: {
name: 's'
}
}
}
var targetObj = {};
var obj = {
name: 'sunny',
age: 18,
car: ['visa', 'BMW'],
wife: {
name: 'cherry',
son: {
name: 's'
}
}
}
var targetObj = {};
实现targetObj对象拥有对象obj所有属性。 思路:
- 首先使用for...in遍历对象。
- 判断是否属于原始值,是数组还是对象。
- 建立相应数组与对象。
// ...
function deepClone(target, origin) {
var target = target || {};
var toStr = Object.prototype.toString;
var arrStr = '[object Array]';
for(var prop in origin) {
if(origin hasOwnProperty(prop)) {
if(origin[prop] != 'null' &&
typeof(origin[prop]) == 'object') {
target[prop] = (toStr.call(origin[prop]) == arrStr)
? []
: {};
deepClone(target[prop], origin[prop]);
}else{
target[prop] = origin[prop];
}
}
}
return target;
}
deepClone(targetObj, obj);
// ...
function deepClone(target, origin) {
var target = target || {};
var toStr = Object.prototype.toString;
var arrStr = '[object Array]';
for(var prop in origin) {
if(origin hasOwnProperty(prop)) {
if(origin[prop] != 'null' &&
typeof(origin[prop]) == 'object') {
target[prop] = (toStr.call(origin[prop]) == arrStr)
? []
: {};
deepClone(target[prop], origin[prop]);
}else{
target[prop] = origin[prop];
}
}
}
return target;
}
deepClone(targetObj, obj);
相反,对象拥有简单属性类型,可以实现浅度克隆。
var obj = {
name: 'sunny',
age: 18
}
var targetObj = {};
function clone(target, origin) {
var target = target || {};
for(var prop in origin) {
if(origin.hasOwnProperty(prop)) {
target[prop] = origin[prop]
}
}
return target;
}
clone(targetObj, obj);
var obj = {
name: 'sunny',
age: 18
}
var targetObj = {};
function clone(target, origin) {
var target = target || {};
for(var prop in origin) {
if(origin.hasOwnProperty(prop)) {
target[prop] = origin[prop]
}
}
return target;
}
clone(targetObj, obj);
this
- this规则:
- 由new调用,绑定到新创建的对象。
- 由call/apply调用,绑定到指定的对象。
- 由上下文对象调用,绑定到那个上下文对象。
- 默认在严格模式下绑定到undefined,否则绑定到全局。
这里补充this案例
var name = 'sunny';
var obj = {
name: 'cherry',
say: function() {
console.log(this.name)
}
}
var fn = obj.say;
fn(); // sunny
obj.say(); // cherry
var name = 'sunny';
var obj = {
name: 'cherry',
say: function() {
console.log(this.name)
}
}
var fn = obj.say;
fn(); // sunny
obj.say(); // cherry
fn运行过程可以理解下面这样,把对象obj中函数say方法交给函数fn拥有,当前this就指向所在全局window。
// ...变量省略
var fn = say function () {
console.log(this.name)
}
// ...输出语句省略
// ...变量省略
var fn = say function () {
console.log(this.name)
}
// ...输出语句省略
案例2
var foo = 123;
function print() {
var foo = 456;
this.foo = 789;
console.log(foo);
}
print();
var foo = 123;
function print() {
var foo = 456;
this.foo = 789;
console.log(foo);
}
print();
可以使用上述规则④,this绑定到全局,因此运行函数输出 456。
案例3
var foo = 123;
function Print() {
this.foo = 234;
console.log(foo)
}
new Print();
var foo = 123;
function Print() {
this.foo = 234;
console.log(foo)
}
new Print();
函数Print运行,使用上述规则④,this绑定到全局,改变了全局foo值,当前构造函数Print没有this,沿着原型链向往寻找,最终在window上找到,因此new print()输出234。
arguments.callee
function foo() {
console.log(arguments);
//Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
console.log(arguments.callee);
// ƒ foo() {
// console.log(arguments.callee)
// }
console.log(arguments.callee == foo) // true
}
foo(1, 2);
function foo() {
console.log(arguments);
//Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
console.log(arguments.callee);
// ƒ foo() {
// console.log(arguments.callee)
// }
console.log(arguments.callee == foo) // true
}
foo(1, 2);
上述输出值,arguments.callee指运行时的函数。
fn.caller
function foo() {
console.log(foo.caller) // foo函数
}
foo();
function foo() {
console.log(foo.caller) // foo函数
}
foo();
上述代码输出值,fn.caller指函数自身foo。 callee和caller在es5严格模式下都不能使用。
数组
改变原数组
- push() 向数组最后一位添加
- pop() 从最后一位开始剔出去
- shift() 从数组前面删除
- unshift() 在数组前面添加
- reserver() 逆转数组
- sort() 排序
- splice(从第几位开始, 截取长度, 在切口处添加新的数据)
不改变数组
- concat() //合并数组
- slice(从该位置开始截, 截取到该位)
- split() 返回字符串
类数组
类数组必须有length属性,能像数组/对象一样使用。 原始类型的值不能拥有属性和方法。
如何使用: 自动创建(看生命周期),自动销毁(调用完函数之后)
典型类数组arguments
function foo() {
console.log(arguments)
}
foo(1, 2, 3);
function foo() {
console.log(arguments)
}
foo(1, 2, 3);
向类数组push
var obj = {
'2': 'a',
'3': 'b',
'length': 2,
'push': Array.prototype.push,
'splice': Array.prototype.splice
}
obj.push('d');
console.log(obj)
// {2: "d", 3: "b", length: 3, push: ƒ}
obj.push('f');
console.log(obj);
// {2: "d", 3: "f", length: 4, push: ƒ}
var obj = {
'2': 'a',
'3': 'b',
'length': 2,
'push': Array.prototype.push,
'splice': Array.prototype.splice
}
obj.push('d');
console.log(obj)
// {2: "d", 3: "b", length: 3, push: ƒ}
obj.push('f');
console.log(obj);
// {2: "d", 3: "f", length: 4, push: ƒ}
通过输出,push后类对象length递增,类对象属性为索引,索引最大length-1,根据当前length-1取索引值。
try...catch
规则
- try里面发生错误,不会执行错误后try里面的代码。
- 如果try里面没有报错,不会执行catch里面代码;反之会执行catch里面代码。
try{
console.log('ok')
}catch(e) {
console.log('error');
}
try{
console.log('ok')
}catch(e) {
console.log('error');
}
归纳六种error信息
- EvalError:eval的使用与定义不一致。
- RangeRrror:数值越界。
- ReferenceError:非法或不能识别的引用数组。
- SyntaxError:发生语法解析错误。
- TypeError:操作数据类似错误。
- URIRrror:URI处理函数使用不当。
ES5严格模式 使用'use strict'(字符串,反之代表是JS语句,JS语句没有规定这种写法)启动严格模式 遵循es5.0模式,不再兼容es3一些不规则语法,推荐局部。
不支持 with/arguments/callee/caller,变量赋值前必须声明。
局部this必须被赋值,赋值什么就是什么,拒绝重复属性和参数,不能使用eval()。
var obj = {};
var name = 'window';
function foo() {
var name = 'scope';
with(obj) { // with改变作用域链
console.log(name);
}
}
foo();
var obj = {};
var name = 'window';
function foo() {
var name = 'scope';
with(obj) { // with改变作用域链
console.log(name);
}
}
foo();
es5.0与es3.0规则
- 浏览器基于es3.0方法 + es5.0新增方法。
- es3.0与es5.0发生冲突部分,使用es5.0模式,反之使用es3.0模式。