/ JavaScript

《 jQuery 基础教程》第4版习题解答

最近在学《jQuery 基础教程》(第4版),Google 了一圈没找到完整的习题解答,所以放在这里作个参考。

Chapter 2 选择器

1.给位于嵌套列表第二个层次的所有 <li> 元素添加 special 类

    $('li').children().find('li').addClass('special');

2.给位于表格第三列的所有单元格添加 year 类

    $('tr').find('td:eq(2)').addClass('year');

3.为表格中包含文本 Tragedy 的第一行添加 special 类

    $('td:contains(\"Tragedy\")').first().parent().addClass('special');

4.(挑战)选择包含链接(<a> 元素)的所有列表项(<li> 元素),为每个选中的列表项的同辈列表项元素添加 afterlink 类

    $('a').parent().nextAll().addBack().addClass('afterlink');

5.(挑战)为与 .pdf 链接最接近的祖先元素 <ul> 添加 tragedy 类

    $('a[href$=\"pdf\"]').closest('ul').addClass('tragedy');

Chapter 3 事件

1.在 Charles Dickens 被单击时,应用 selected 样式

    $('div.author').click(function () {
        if ($(this).hasClass('selected')) {
            $(this).removeClass('selected');
        } else {
            $(this).addClass('selected');
        }
    });

2.双击章节标题(h3)时,切换本章文字的可见性

    $('h3.chapter-title').dblclick(function () {
        $(this).parent().addClass('hidden');
    });

3.当用户按下向右方向键时,切换到下一个 body 类,右方向键的键码是39

    var bodyClasses = [
        'default',
        'narrow',
        'large'
    ];
    $(document).keyup(function (event) {
        if (event.which === 39) {
            for (i = 0; i < 2; i++) {
                if ($('body').hasClass(bodyClasses[i])) {
                    $('body').removeClass().addClass(bodyClasses[i + 1]);
                    break;
                }
            }
            if (i == 2) {
                setBodyClass(bodyClasses[0]);
            }
        }
    });

上面这种写法比较“笨拙“。这里有更灵巧的写法,妙用switch case语句。

4.(挑战)使用 console.log() 函数记录在段落中移动的鼠标的坐标位置

    $('p').mousemove(function (event) {
        console.log(\"eventXY: (\" + event.pageX + \",\" + event.pageY + \")\");
    });

5.(挑战)使用 .mousedown().mouseup() 跟踪页面中的鼠标事件。如果鼠标按键在按下的地方被释放,则为所有段落添加 hidden 类。如果是在按下的地方之下被释放,删除所有段落的 hidden 类

    var mouseDownX, mouseDownY;
    $(document).mousedown(function (event) {
        mouseDownX = event.pageX;
        mouseDownY = event.pageY;
    });
    $(document).mouseup(function (event) {
        if (event.pageX === mouseDownX && event.pageY === mouseDownY) {
            $('p').addClass('hidden');
        } else if (event.pageY > mouseDownY) {
            $('p').removeClass('hidden');
        }

    });

Chapter 4 样式与动画

1.修改样式表,一开始先页面内容,当页面加载后,慢慢地淡入内容

$('#container').hide().fadeIn('slow'); // 其实不需要修改样式表

2.在鼠标悬停到段落上面时,给段落应用黄色背景

    var oldBackgroundColor;
    $('div p').hover(
        function () {
            // hover 中使用 animate() 有问题???
            /*
            $(this).animate({
                backgroundColor: 'yellow'
            }, 'fast');
            */
            oldBackgroundColor = $(this).css('backgroundColor');
            $(this).css({
                backgroundColor: 'yellow'
            });
        }, function () {
            /*
            //var backgroundColor = $('p').css('backgroundColor')
            $(this).animate({
                bacgroundColor: 'transparent'
            }, 'fast')
            */
            $(this).css({
                backgroundColor: oldBackgroundColor
            });
        });

3.单击标题 <h2> 时将不透明度变为 25%,同时增加 20px 的左外边距,这两个效果完成后把讲话文本变成 50% 不透明度

    $('h2').click(function () {
        $(this).animate({
            opacity: .25,
            paddingLeft: '+=20px'
        }, 'slow', function () {
        	// 不同要素的动画默认是同步进行的
            // 使用回调函数,保证上面的动画完成后再设置 div.speech 动画
            $('div.speech').animate({
                opacity: .5
            }, 'slow')
        })
    });

4.按下方向键时,使样式转换器 #switcher向相应的方向平滑移动20像素。
注意:这里使用 .filter(':not(:animated)') 可以防止按键过快导致动画“堆积”。

$(window).keydown(function (event) {
        var switcher = $('#switcher');
        switch (event.which) {
        case 37:
            switcher.css('position', 'relative').filter(':not(:animated)').animate({
                left: '-=20px'
            }, 'fast');
            break;
        case 38:
            switcher.css('position', 'relative').filter(':not(:animated)').animate({
                top: '-=20px'
            }, 'fast');
            break;
        case 39:
            switcher.css('position', 'relative').filter(':not(:animated)').animate({
                left: '+=20px'
            }, 'fast');
            break;
        case 40:
            switcher.css('position', 'relative').filter(':not(:animated)').animate({
                top: '+=20px'
            }, 'fast');
            break;
        default:
        }
    });

Chapter 5 操作 DOM

1.修改添加 back to top 的代码,以便这些链接只从第四段后面才开始出现。

	$('div.chapter p').each(function (index) {
        if (index >= 3) {
            $(this).after($('<a href=\"#top\">back to top</a>'));
        }
    });

2.在单击 back to top 链接时,为每个链接后面添加一个新段落,其中包含 you were here 字样

	// 给提示文字添加了一个类,以便在需要的时候清除
    $('a[href*=\"#top\"]').click(function (event) {
        var clickedLink = event.target;
        $('.scrollLocationTip').remove();
        $('<p class=\"scrollLocationTip\">you were here</p>').insertAfter(clickedLink);
        event.stopPropagation();
    });
    // 不在这些链接上点击时,将提示文字清除
    $('body').click(function () {
        $('.scrollLocationTip').remove();
    });

3.单击作者的名字时,把文本改为粗体(通过添加一个标签,而不是操作类或 CSS 属性)
4.(挑战)在随后单击粗体作者名字时,之前添加的 <b> 元素(也就是在文本与正常文本之间切换)

	// 题干中用的是 <b> 标签,实际上为了语义与样式分离,用 <strong> 更合适
	$('#f-author').click(function () {
        if ($(this).children('strong').length == 0) {
        	// wrapInner(),不改变外部的 DOM 结构
            // 判断 length 的目的是,防止多次 wrapInner()
            $(this).wrapInner('<strong></strong>');
        } else {
            $(this).children('strong').contents().unwrap('<strong></strong>');
        }
    });

这两个练习还有另外一种写法,在两种状态之间“切换”可以使用 toggle() 方法来实现。

	$('#f-author').toggle(
        function () {
            $(this).wrapAll('<b></b>');
        },
        function () {
            $(this).unwrap()
        }
    );

5.(挑战)为正文中的每个段落添加一个 inhabitants 类,但不能调用 addClass() 方法。确保不影响现有的类

    // $('.chapter p').addClass('inhabitants');
    // 用 $(this).className += 'inhabitants 为什么不行???
    $('.chapter p').each(function () {
        if (!$(this).hasClass('inhabitants')) {
            $(this).attr({
                class: $(this).attr('class') + ' inhabitants'
            });
        }
    });

Chapter 6 通过 Ajax 发送数据

1.页面加载后,把 exercises-content.html 的主体内容(body)提取到页面的内容区域

	// .load() 中的选择器为什么不能是 body???
	$('#dictionary').load('exercises-content.html .letter');

2.不要一次显示整个文档,为左侧的字母列表创建“提示条”,当用户鼠标放到字母上时从 exercises-content.html 中加载与该字母有关的内容

    /*
    $('.letter a').hover(function () {
        $('#dictionary').load('exercises-content.html #' + $(this).closest('.letter').attr('id'));
    }, function () {
        $('#dictionary').html('');
    });
    */
    $('.letter').hover(function () {
        $('#dictionary').load('exercises-content.html #' + $(this).attr('id'));
    }, function () {
        $('#dictionary').html('');
    });

3.为上面的页面加载添加错误处理功能,在页面的内容区显示错误消息。修改脚本,请求 does-not-exist.html 而不是 exercises-content.html,以测试错误处理功能。

    $('.letter').hover(function () {
        $('#dictionary').load('does-not-exist.html #' + $(this).attr('id'), function (response, status, xhr) {
            if (status === 'error') {
                $('#dictionary').html('错误:' + xhr.status).append(xhr.responseText);
            } else {
                $('#dictonary').html(xhr.responseText);
            }
        });
    }, function () {
        $('#dictionary').html('');
    });

4.页面加载后,向 GitHub 发出一个 JSONP 请求,取得某用户的代码库列表。把每个代码库的名称和 URL 插入到页面的内容区。例如 jQuery 项目代码库的 URL 是 https://api.github.com/users/jquery/repos

    var repoURL = 'https://api.github.com/users/jquery/repos';
    $.getJSON(repoURL + '?callback=?', function (response) {
        var html = '';
        $.each(response.data, function (entryIndex, entry) {
            html += '<div class=\"entry\">';
            //给内容加上 term、part 类,是为了直接使用样式表中的样式
            html += '<div class=\"repoName term\">' + entry.name + '</div>';
            html += '<div class=\"repoURL part\">' + '<a href=\"' + entry.url + '\">' + entry.url + '</a>' + '</div>';
            html += '</div>';
        });
        $('#dictionary').html(html);
    });

Chapter 7 使用插件

1.把幻灯片的切换周期延长到 1.5 秒,把动画的效果修改为下一张幻灯片淡入之前,前一张幻灯片淡出。

    $books.cycle({
        timeout: 2000,
        speed: 1500,
        pause: true,
        fx: 'fade', // 添加这一行
        before: function () {
            $('#slider').slider('value', $('#books li').index(this));
        }
    });

2.设置名为 cyclePaused 的 cookie,将它的有效期设置为30天

    $.cookie('cyclePaused', null, {
        expires: 7
    });

3.限制书名区域,每次 resize 只允许以10像素为单位

    $books.find('.title').resizable({
        handles: 's',
        grid: [10, 10] // 添加这一行 | 好像不起作用
    });

4.修改 slider 的动画,让滑块平滑地移动到下一个位置。

    $('<div id=\"slider\"></div>').slider({
        min: 0,
        max: $('#books li').length - 1,
        animate: true, // 添加这一行
        slide: function (event, ui) {
            $books.cycle(ui.value);
        }
    }).appendTo($controls);

5.幻灯片播放完最后一张后停止。停止播放时,也禁用相应的按钮和滑动条。

    $books.cycle({
        timeout: 2000,
        speed: 1500,
        pause: true,
        fx: 'fade',
        nowrap: true, // 添加这一行
        before: function () {
            $('#slider').slider('value', $('#books li').index(this));
        }
    });

7.修改 mobile.html 中的 HTML 代码,让列表视图根据书名的第一个字母分隔开来。
可以在 html 中添加几个带有 data-role=\"list-divider\" 属性的标签:

<div data-role=\"content\">
    <ul data-role=\"listview\" data-inset=\"true\" data-filter=\"true\">
      <li data-role=\"list-divider\">D</li>
      <li><a href=\"#drupal-7\">Drupal 7 Development by Example</a></li>
      <li data-role=\"list-divider\">J</li>
      <li><a href=\"#jq-game\">jQuery Game Development Essentials</a></li>
      <li><a href=\"#jqmobile-cookbook\">jQuery Mobile Cookbook</a></li>
      <li><a href=\"#jquery-designers\">jQuery for Designers</a></li>
      <li><a href=\"#jquery-hotshot\">jQuery Hotshot</a></li>
      <li><a href=\"#jqui-cookbook\">jQuery UI Cookbook</a></li>
      <li data-role=\"list-divider\">M</li>
      <li><a href=\"#mobile-apps\">Creating Mobile Apps with jQuery Mobile</a></li>
      <li data-role=\"list-divider\">W</li>
      <li><a href=\"wp-mobile-apps.html\">WordPress Mobile Applications with PhoneGap</a></li>
    </ul>
</div>

Chapter 8 开发插件

1.创建 .slideFadeIn().slideFadeOut() 的插件方法,把不透明度动画方法 .fadeIn().fadeOut() 以及高度动画方法 .slideDown().slideUp() 结合起来。

    (function ($) {
        $.fn.slideFadeIn = function () {
            return this.each(function () {
                // 用 fadeIn() 效果不明显
                $(this).slideDown('slow').fadeTo('slow', 1.0);
            });
        };
        $.fn.slideFadeOut = function () {
            return this.each(function () {
                // 用 fadeOut() 效果不明显
                $(this).fadeTo('slow', 0.5).slideUp('slow');
            });
        };
    })(jQuery);
    
    // 练习1 测试代码
    $('#container h1').click(function () {
        $('#inventory').slideFadeOut();
        $('#inventory').slideFadeIn();
    });

2.扩展 .shadow() 方法的可定制能力,让插件用户可以指定元素副本的 z 轴索引。为提示条控件添加一个 .isOpen() 子方法,在提示条正在显示的时候返回 true,而在其他时候返回 false

(function ($) {
    $.fn.shadow = function (opts) {
        var options = $.extend($.fn.shadow.defaults, opts);
        return this.each(function () {
            var $originalElement = $(this);
            for (var i = 0; i < options.copies; i++) {
                var offset = options.copyOffset(i);
                $originalElement
                    .clone()
                    .css({
                        position: 'absolute',
                        left: $originalElement.offset().left + offset.x,
                        top: $originalElement.offset().top + offset.y,
                        margin: 0,
                        // ========== 练习2
                        zIndex: options.zIndex,
                        // ==========
                        opacity: options.opacity
                    })
                    .appendTo('body');
            }
        });
    }
})(jQuery);

$.fn.shadow.defaults = {
    copies: 5,
    opacity: 0.1,
    copyOffset: function (index) {
        return {
            x: index,
            y: index
        };
    },
    // ========== 练习2添加
    zIndex: -1
    // ==========
};

(function ($) {
    $.widget('ljq.tooltip', {
        options: {
            offsetX: 10,
            offsetY: 10,
            content: function () {
                return $(this).data('tooltip-text');
            }
        },
        _create: function () {
            // ========== 练习2
            this.isTooltipOpen = false;
            // ==========
            this._tooltipDiv = $('<div></div>')
                .addClass('ljq-tooltip-text ' + 'ui-widget ui-state-highlight ui-corner-all')
                .hide().appendTo('body');
            this.element
                .addClass('ljq-tooltip-trigger')
                .on('mouseenter.ljq-tooltip', $.proxy(this._open, this))
                .on('mouseleave.ljq-tooltip', $.proxy(this._close, this));
        },
        destroy: function () {
            this._tooltipDiv.remove();
            // this.element 不就是 $(this) 吗?
            this.element
                .removeClass('ljq-tooltip-trigger')
                .off('.ljq-tooltip');
            $.Widget.prototype.destroy.apply(this, arguments);

        },
        // ========== 练习2
        isOpen: function () {
            return this.isTooltipOpen;
        },
        // ==========
        _open: function () {
            if (!this.options.disabled) {
                var elementOffset = this.element.offset();
                this._tooltipDiv.css({
                    position: 'absolute',
                    left: elementOffset.left + this.options.offsetX,
                    top: elementOffset.top + this.element.height() + this.options.offsetY
                }).text(this.options.content.call(this.element[0]));
                this._tooltipDiv.show();
                // ========== 练习2
                this.isTooltipOpen = true;
                // ==========
                this._trigger('open');
            }
        },
        open: function () {
            this._open();

        },
        _close: function () {
            this._tooltipDiv.hide();
            this._trigger('close');
            // ========== 练习2
            this.isTooltipOpen = false;
            // ==========
        },
        close: function () {
            this._close();
        }

    });
})(jQuery);

// 练习2 测试代码
    $('a').tooltip().hover(function () {
        console.log('Is Tooltip Open? : ' + $(this).tooltip('isOpen').toString());
    });

3.添加代码监听 tooltipOpen 事件,并在控制台中记录一条消息

$('a').bind('tooltipopen', function() {
	console.log('tooltipopen event triggered');
});

4.**(挑战)**自定义提示条部件的 content 选项,通过 AJAX 取得链接的 href 属性指向的页面的内容,将取得的内容作为提示条的文本。

    $('a').tooltip({
        content: function () {
            var url = $(this).attr('href');
            var result = null;
            $.ajax({
                url: url,
                type: 'get',
                dataType: 'html',
                async: false,
                success: function (data) {
                    result = data;
                }
            });
            return result;
        }
    });

5.**(挑战)**为提示条部件提供一个新的 effect 选项,如果指定该选项,则用该名字指定的 jQuery UI 效果显示或隐藏提示条。

// 在 _open 方法末尾加上:
// ========== 练习5
if (this.options.effect) {
	this._tooltipDiv.effect(this.options.effect, {distance: 10, duration: 80});
}
// ==========

//练习5 测试代码
$('a').tooltip({effect: 'shake'});