说明
这篇文章将包含如下的几个内容:
- 对象的理解
- 函数的理解
- 封装与继承
- 常见的问题
一. 对象的理解
- 字面量
通过字面量可以很容易的定义对象。在大括号中直接定义,属性:值
var o = {
x : 1 ,
y : 2 ,
method : function() {
console.info("this is a method");
}
}
- 对象的检索
可以通过属性名直接得到属性值,也可以使用[]来获得
console.info(o.x);
console.info(o["y"]);
- 对象的更新
可以直接对对象的属性进行设置操作,更新对象属性;可以直接添加属性,并赋值
o.x = 10
console.info(o.x); // 10
o.z = 20
console.info(o.z); // 20
- 对象的枚举
可以通过for..in来进行枚举;方法也会被枚举出来
for (name in o) {
if (o.hasOwnProperty(name)) {
console.info("property = " + name + " and value = " + o[name]);
}
}
这里注意:为什么有一个hasOwnProperty,是为了防止枚举出原型链上的属性。下面举例说明
Object.prototype.basex = 12
var o = {
x : 100
}
// 这里会打印x
for (name in o) {
if (o.hasOwnProperty(name)) {
console.info(name);
}
}
// 这里会打印x, basex
for (name in o) {
console.info(name);
}
如果hasOwnProperty被改写,可以使用如下方法进行调用
var o = {
x : 100,
// 被改写
hasOwnProperty : 111
}
for (name in o) {
// 改变调用方法
if (Object.prototype.hasOwnProperty.call(o,name)) {
console.info(name);
}
}
- 删除对象属性
delete o.x
- 原型链
对象可以使用原型链上的属性
Object.prototype.x = 100
var o = {}
console.info(o.x); // 100
delete o.x
// 不能删除原型链的属性
console.info(o.x); // 100
// 自己定义属性
o.x = 200
// 显示自己定义的属性,覆盖原型链属性
console.info(o.x); // 200
delete o.x
// 删除自己定义的属性,漏出原型链属性
console.info(o.x); // 100
二. 函数的理解
函数是JS的一等公民,是JS中最灵活的部分,也是最难理解的部分
- 函数对象
函数也是对象,也可以存在属性和方法
var F = function() {
}
// 使用对象一样使用函数
F.x = 12;
console.info(F.x); // 12
- 函数字面量
函数的字面量包含四个部分:保留字function;函数名(可以省略);函数参数,以逗号分隔;花括号(函数主体)。
函数的字面量可以出现在任何允许出现表达式的地方 - 函数的调用
在进行函数调用的时候,会隐含的传入this和arguments。其中arguments是一个伪数组,表示传入的参数。this可以理解为调用的上下文。函数有几种调用方式,下面将分别说明
作为方法调用
var o = {
x : 100,
method : function() {
// 方法中的调用,this表示方法的对象,这里是o
console.info(this.x);
}
}
o.method() // 100
函数调用
function f(x) {
// this指代全局变量
this.x = x
console.info(x);
}
try{
console.info(x); // not defined
}catch(e){
console.info(e); // not defined error
}
// 调用函数
f(100)
console.info(x); // 100
构造器调用,将函数作为一个构造器
// 构造器
var F = function(x) {
this.x = x
}
// 构造器中定义方法
F.prototype.read = function() {
return this.x
}
// 构造一个对象
var ins = new F(200)
// 调用方法(this指代对象本身)
console.info(ins.read()); // 200
console.info(ins.x); // 200
call apply
在之前的对象说明中,我们已经展示了使用call调用方法的例子。这种调用方法,会主动改变上下文,也就是说this会由我们设定
// 定义一个对象
var o = {
x : 100
}
// 定义一个函数
var f = function() {
// 使用this
console.info(this.x);
}
// 两种调用方法的区别
f() // undefined
f.call(o) // 100
- 高阶函数
当一个函数接受另一个函数作为参数的时候,就是高阶函数。我们可以自定义高阶函数,下面是两个使用Array中两个高阶函数的例子
Array.map
// 定义一个数值
var arr = [1,2,3,4,5,6]
// 通过高阶函数运算
var arrD = arr.map(function(x) {
return x*x
})
console.info(arrD); // [ 1, 4, 9, 16, 25, 36 ]
Array.reduce
// 定义一个数值
var arr = [1,2,3,4,5,6]
// 通过高阶函数进行求和运算
var d = arr.reduce(function(x,y) {
return x+y
})
console.info(d); // 21
- 闭包
内层函数可以使用外层函数的特点叫做闭包。下面会讲述使用闭包解决一些常见的错误
三. 封装与继承
javascript是面向对象的,但是没有语法层面的继承和封装语法。
- 定义类
js中我们可以有类似java的使用方法:定义对象,通过new实例化一个对象
// 定义一个类
var F = function() {}
// 定义类中的方法
F.prototype.test = function(o) {
console.info(o);
}
F.prototype.display = function() {
console.info("this is a display function");
}
// 实例化一个类
var ins = new F;
ins.test('lxm')
ins.display()
- 封装
通过字面量定义的对象是无法保护属性的,可以随意的修改和增减。下面介绍一个数据封装的方法
// 创建构造器
var F = function() {
// 私有属性
var x;
// this是必须的
this.getX = function() {
return x;
}
this.setX = function(x1) {
x = x1;
}
}
// 通过构造器创建对象
var instance = new F
// 使用对象
instance.setX(100)
console.info(instance.x); // undefined
console.info(instance.getX()); // 100
- 继承
// 创建一个继承函数
if(typeof Object.inhert !== 'function'){
Object.inhert = function(o) {
var F = function() {}
F.prototype = o
return new F
}
}
// 父对象
var o = {
x : 1,
y : 100
}
// 创建子对象
var child = Object.inhert(o)
console.info(child.x); // 1
console.info(o.x); // 1
// 设置x
child.x = 200
console.info(child.x); // 200
console.info(o.x); // 1
四. 常见的问题
整理一下在实际工作中经常发生的误用
- 全局作用域
myglobal = "hello"; // 不推荐写法
console.log(myglobal); // "hello"
console.log(window.myglobal); // "hello"
console.log(window["myglobal"]); // "hello"
console.log(this.myglobal); // "hello"
使用显示声明的变量不能被delete删除
var sss = 'abc';
ddd = 'abc';
delete sss; // false
delete ddd; // true
- 函数内使用var避免产生全局变量
function abc(a,b){
ret = a + b;
return ret;
}
console.info(abc(1,2));
console.info(this.ret);
- 定义变量产生的全局变量
function abc(a,b){
var ret = a + b;
var x = y = 3;
return ret;
}
console.info(abc(1,2));
console.info(this.ret);
console.info(y);
- 变量提升
function abc(){
alert(myname);
var mynam ='lxm';
alert(myname);
}
第一阶段是变量,函数声明,以及正常格式的参数创建,这是一个解析和进入上下文 的阶段。
第二个阶段是代码执行,函数表达式和不合格的标识符(为声明的变量)被创建
- 自动添加分行
function abc(){
var s = 12;
return
s
}
abc();
- for循环中产生全局变量
for(var i=0;i < 12;i++){
}
console.info("gloabl i = " + i);
注意缓存数组的长度,尤其是进行dom操作的时候
for(var i=0, max=document.getElementsByName().length;i<max;i++){
}
- 循环中常见的错误
var funArr = [];
for(var i=0;i<10;i++){
funArr[i] = function(){
console.info(i);
}
}
funArr[2]();
通过闭包来解决
var funArr = [];
for(var i=0;i<10;i++){
(function(i){
funArr[i] = function(){
console.info(i);
}
})(i);
}
funArr[2]();
- 使用for..in进行属性遍历
进行枚举,但是不保证顺序;通常需要过滤原型链上的属性
// 对象
var man = {
hands: 2,
legs: 2,
heads: 1
};
for(var t in man){
if(man.hasOwnProperty(t))
console.info(t);
}
另外的一种调用方式,可以避免对象将hasOwnProperty重新定义
for (var i in man) {
if (Object.prototype.hasOwnProperty.call(man, i)) { // 过滤
console.log(i, ":", man[i]);
}
}
- 关于扩展内置属性的问题
由于有了prototype的存在,可以很方便的进行属性的扩充
最好不增加内置原型
Object.prototype.testMethod = function(){
//console.info('这是我弄的!');
return "这是我弄的";
}
var sss = {};
sss.testMethod();
- 尽可能避免隐式类型转换
使用 === !== 来进行判断 - eval尽可能不要用
这个处理可以接收任何字符串,当成js代码来执行 - 关于类型转换
parseInt尽可能少用,使用Number()来替换 - 内嵌方法中的this错误
通常我们在Java等面向对象的语音中都见过this,指代当前的对象,在编译期间就已经确定下来,是编译期绑定的。而javascript是解释性语言,this是动态的,是运行期绑定的,这就导致了this关键字具有多重含义,不好理解。下面是一个js设计中的问题
var x = 'global'
var o = {
x : 'property',
dis : function() {
var x = 'local'
console.info("dis x = " + this.x); // property
// 定义一个内嵌方法
var local = function() {
console.info("local x = " + this.x); // global
}
// 执行内嵌方法
local()
}
}
o.dis()
这个测试结果是在浏览器中执行,如果在Node.js环境下,会有点不同
网友评论